2023-08-16 12:57:41 +03:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
import QtQuick
|
|
|
|
|
import HelperWidgets as HelperWidgets
|
|
|
|
|
import StudioControls as StudioControls
|
2024-01-16 12:30:29 +01:00
|
|
|
import StudioTheme as StudioTheme
|
2024-01-26 14:55:50 +02:00
|
|
|
import EffectComposerBackend
|
2023-08-16 12:57:41 +03:00
|
|
|
|
|
|
|
|
Column {
|
|
|
|
|
id: root
|
|
|
|
|
|
2023-11-06 14:41:31 +02:00
|
|
|
property real animatedTime: previewFrameTimer.elapsedTime
|
|
|
|
|
property int animatedFrame: previewFrameTimer.currentFrame
|
|
|
|
|
property bool timeRunning: previewAnimationRunning
|
2023-10-19 16:39:21 +03:00
|
|
|
|
2023-09-19 14:10:39 +03:00
|
|
|
required property Item mainRoot
|
2024-01-26 14:55:50 +02:00
|
|
|
property var effectComposerModel: EffectComposerBackend.effectComposerModel
|
2023-10-02 11:33:15 +03:00
|
|
|
property alias source: source
|
2023-10-03 10:34:50 +03:00
|
|
|
// The delay in ms to wait until updating the effect
|
2023-10-11 18:59:03 +03:00
|
|
|
readonly property int updateDelay: 100
|
2023-10-02 11:33:15 +03:00
|
|
|
|
2024-02-02 12:27:51 +02:00
|
|
|
readonly property int previewMargin: 5
|
|
|
|
|
|
2023-10-09 13:46:08 +03:00
|
|
|
// Create a dummy parent to host the effect qml object
|
|
|
|
|
function createNewComponent() {
|
2023-11-13 16:31:03 +02:00
|
|
|
// If we have a working effect, do not show preview image as it shows through
|
|
|
|
|
// transparent parts of the final image
|
|
|
|
|
source.visible = false;
|
|
|
|
|
|
2023-10-09 13:46:08 +03:00
|
|
|
var oldComponent = componentParent.children[0];
|
|
|
|
|
if (oldComponent)
|
|
|
|
|
oldComponent.destroy();
|
|
|
|
|
try {
|
|
|
|
|
const newObject = Qt.createQmlObject(
|
2024-02-02 13:36:37 +02:00
|
|
|
effectComposerModel.qmlComponentString(),
|
2023-10-09 13:46:08 +03:00
|
|
|
componentParent,
|
|
|
|
|
""
|
|
|
|
|
);
|
2024-01-26 14:55:50 +02:00
|
|
|
effectComposerModel.resetEffectError(0);
|
2023-10-09 13:46:08 +03:00
|
|
|
} catch (error) {
|
|
|
|
|
let errorString = "QML: ERROR: ";
|
|
|
|
|
let errorLine = -1;
|
|
|
|
|
if (error.qmlErrors.length > 0) {
|
|
|
|
|
// Show the first QML error
|
|
|
|
|
let e = error.qmlErrors[0];
|
|
|
|
|
errorString += e.lineNumber + ": " + e.message;
|
|
|
|
|
errorLine = e.lineNumber;
|
|
|
|
|
}
|
2024-01-26 14:55:50 +02:00
|
|
|
effectComposerModel.setEffectError(errorString, 0, errorLine);
|
2023-11-13 16:31:03 +02:00
|
|
|
source.visible = true;
|
2023-10-09 13:46:08 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 12:57:41 +03:00
|
|
|
Rectangle { // toolbar
|
|
|
|
|
width: parent.width
|
|
|
|
|
height: StudioTheme.Values.toolbarHeight
|
|
|
|
|
color: StudioTheme.Values.themeToolbarBackground
|
|
|
|
|
|
2023-11-09 19:52:58 +02:00
|
|
|
Row {
|
2023-08-16 12:57:41 +03:00
|
|
|
spacing: 5
|
2023-08-29 23:08:43 +03:00
|
|
|
anchors.leftMargin: 5
|
2023-11-09 19:52:58 +02:00
|
|
|
anchors.left: parent.left
|
2023-11-10 16:17:43 +02:00
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
2023-08-29 23:08:43 +03:00
|
|
|
|
2023-09-19 14:10:39 +03:00
|
|
|
PreviewImagesComboBox {
|
|
|
|
|
id: imagesComboBox
|
|
|
|
|
|
|
|
|
|
mainRoot: root.mainRoot
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-09 19:52:58 +02:00
|
|
|
StudioControls.ColorEditor {
|
|
|
|
|
id: colorEditor
|
|
|
|
|
|
|
|
|
|
actionIndicatorVisible: false
|
|
|
|
|
showHexTextField: false
|
|
|
|
|
color: "#dddddd"
|
2023-08-29 23:08:43 +03:00
|
|
|
}
|
2023-11-09 19:52:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Row {
|
|
|
|
|
spacing: 5
|
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
2023-11-10 16:17:43 +02:00
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
2023-08-16 12:57:41 +03:00
|
|
|
|
|
|
|
|
HelperWidgets.AbstractButton {
|
2024-02-02 14:20:37 +02:00
|
|
|
enabled: sourceImage.scale < 3
|
2023-08-16 12:57:41 +03:00
|
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
2023-12-13 12:11:06 +02:00
|
|
|
buttonIcon: StudioTheme.Constants.zoomIn_medium
|
|
|
|
|
tooltip: qsTr("Zoom In")
|
2023-08-16 12:57:41 +03:00
|
|
|
|
2023-08-29 23:08:43 +03:00
|
|
|
onClicked: {
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.enableAnim(true)
|
2023-12-13 12:11:06 +02:00
|
|
|
sourceImage.scale += .2
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.enableAnim(false)
|
2024-02-02 14:51:21 +02:00
|
|
|
zoomIndicator.show()
|
2023-08-29 23:08:43 +03:00
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HelperWidgets.AbstractButton {
|
2023-12-13 12:11:06 +02:00
|
|
|
enabled: sourceImage.scale > .4
|
2023-08-16 12:57:41 +03:00
|
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
2023-12-13 12:11:06 +02:00
|
|
|
buttonIcon: StudioTheme.Constants.zoomOut_medium
|
|
|
|
|
tooltip: qsTr("Zoom out")
|
2023-08-16 12:57:41 +03:00
|
|
|
|
2023-08-29 23:08:43 +03:00
|
|
|
onClicked: {
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.enableAnim(true)
|
2023-12-13 12:11:06 +02:00
|
|
|
sourceImage.scale -= .2
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.enableAnim(false)
|
2024-02-02 14:51:21 +02:00
|
|
|
zoomIndicator.show()
|
2023-08-29 23:08:43 +03:00
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HelperWidgets.AbstractButton {
|
2024-02-02 12:27:51 +02:00
|
|
|
enabled: sourceImage.scale !== 1 || sourceImage.x !== root.previewMargin
|
|
|
|
|
|| sourceImage.y !== root.previewMargin
|
2023-08-16 12:57:41 +03:00
|
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
2023-09-05 18:03:05 +03:00
|
|
|
buttonIcon: StudioTheme.Constants.fitAll_medium
|
2024-02-02 12:27:51 +02:00
|
|
|
tooltip: qsTr("Reset View")
|
2023-08-16 12:57:41 +03:00
|
|
|
|
2023-08-29 23:08:43 +03:00
|
|
|
onClicked: {
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.resetTransforms()
|
2023-08-29 23:08:43 +03:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-09 19:52:58 +02:00
|
|
|
}
|
2023-08-29 23:08:43 +03:00
|
|
|
|
2023-11-09 19:52:58 +02:00
|
|
|
Row {
|
|
|
|
|
spacing: 5
|
|
|
|
|
anchors.rightMargin: 5
|
|
|
|
|
anchors.right: parent.right
|
2023-11-10 16:17:43 +02:00
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
2023-08-16 12:57:41 +03:00
|
|
|
|
|
|
|
|
Column {
|
|
|
|
|
Text {
|
2023-11-06 14:41:31 +02:00
|
|
|
text: animatedTime >= 100
|
|
|
|
|
? animatedTime.toFixed(1) + " s" : animatedTime.toFixed(3) + " s"
|
2023-08-16 12:57:41 +03:00
|
|
|
color: StudioTheme.Values.themeTextColor
|
|
|
|
|
font.pixelSize: 10
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Text {
|
2023-11-06 14:41:31 +02:00
|
|
|
text: (animatedFrame).toString().padStart(6, '0')
|
2023-08-16 12:57:41 +03:00
|
|
|
color: StudioTheme.Values.themeTextColor
|
|
|
|
|
font.pixelSize: 10
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HelperWidgets.AbstractButton {
|
|
|
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
|
|
|
buttonIcon: StudioTheme.Constants.toStartFrame_medium
|
|
|
|
|
tooltip: qsTr("Restart Animation")
|
|
|
|
|
|
2023-11-06 14:41:31 +02:00
|
|
|
onClicked: {
|
|
|
|
|
previewFrameTimer.reset()
|
|
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HelperWidgets.AbstractButton {
|
|
|
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
2023-11-08 16:03:23 +02:00
|
|
|
buttonIcon: previewAnimationRunning ? StudioTheme.Constants.pause_medium
|
|
|
|
|
: StudioTheme.Constants.playOutline_medium
|
2023-08-16 12:57:41 +03:00
|
|
|
tooltip: qsTr("Play Animation")
|
|
|
|
|
|
2023-11-06 14:41:31 +02:00
|
|
|
onClicked: {
|
|
|
|
|
previewAnimationRunning = !previewAnimationRunning
|
|
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Rectangle { // preview image
|
2023-10-02 11:33:15 +03:00
|
|
|
id: preview
|
2023-08-16 12:57:41 +03:00
|
|
|
|
2023-11-09 19:52:58 +02:00
|
|
|
color: colorEditor.color
|
2023-08-16 12:57:41 +03:00
|
|
|
width: parent.width
|
2024-01-23 18:57:01 +02:00
|
|
|
height: root.height - y
|
2023-08-29 23:08:43 +03:00
|
|
|
clip: true
|
2023-08-16 12:57:41 +03:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
MouseArea {
|
|
|
|
|
id: mouseArea
|
|
|
|
|
|
2023-08-16 12:57:41 +03:00
|
|
|
anchors.fill: parent
|
2024-02-02 14:20:37 +02:00
|
|
|
acceptedButtons: Qt.LeftButton
|
2023-10-02 11:33:15 +03:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
property real pressX: 0
|
|
|
|
|
property real pressY: 0
|
|
|
|
|
property bool panning: false
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
onPressed: {
|
|
|
|
|
pressX = mouseX - sourceImage.x
|
|
|
|
|
pressY = mouseY - sourceImage.y
|
|
|
|
|
panning = true
|
|
|
|
|
}
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
onReleased: {
|
|
|
|
|
panning = false
|
|
|
|
|
}
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
onWheel: (wheel) => {
|
|
|
|
|
let prevScale = sourceImage.scale
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
if (wheel.angleDelta.y > 0) {
|
|
|
|
|
if (sourceImage.scale < 3)
|
|
|
|
|
sourceImage.scale += .2
|
|
|
|
|
} else {
|
|
|
|
|
if (sourceImage.scale > .4)
|
|
|
|
|
sourceImage.scale -= .2
|
2024-02-02 12:27:51 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
let dScale = sourceImage.scale - prevScale
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
sourceImage.x += (sourceImage.x + sourceImage.width * .5 - wheel.x) * dScale;
|
|
|
|
|
sourceImage.y += (sourceImage.y + sourceImage.height * .5 - wheel.y) * dScale;
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
sourceImage.checkBounds()
|
2024-02-02 14:51:21 +02:00
|
|
|
zoomIndicator.show()
|
2024-02-02 14:20:37 +02:00
|
|
|
}
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
Timer { // pan timer
|
|
|
|
|
running: parent.panning
|
|
|
|
|
interval: 16
|
|
|
|
|
repeat: true
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2024-02-02 14:20:37 +02:00
|
|
|
onTriggered: {
|
|
|
|
|
sourceImage.x = mouseArea.mouseX - mouseArea.pressX
|
|
|
|
|
sourceImage.y = mouseArea.mouseY - mouseArea.pressY
|
2024-02-02 12:27:51 +02:00
|
|
|
sourceImage.checkBounds()
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-02 14:20:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Item { // Source item as a canvas (render target) for effect
|
|
|
|
|
id: source
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
layer.enabled: true
|
|
|
|
|
layer.mipmap: true
|
|
|
|
|
layer.smooth: true
|
2024-02-02 12:27:51 +02:00
|
|
|
|
2023-10-02 11:33:15 +03:00
|
|
|
Image {
|
|
|
|
|
id: sourceImage
|
2024-02-02 12:27:51 +02:00
|
|
|
|
|
|
|
|
function checkBounds() {
|
|
|
|
|
let edgeMargin = 10
|
|
|
|
|
// correction factor to account for an observation that edgeMargin decreases
|
|
|
|
|
// with increased zoom
|
|
|
|
|
let corrFactor = 10 * sourceImage.scale
|
|
|
|
|
let imgW2 = sourceImage.paintedWidth * sourceImage.scale * .5
|
|
|
|
|
let imgH2 = sourceImage.paintedHeight * sourceImage.scale * .5
|
|
|
|
|
let srcW2 = source.width * .5
|
|
|
|
|
let srcH2 = source.height * .5
|
|
|
|
|
|
|
|
|
|
if (sourceImage.x < -srcW2 - imgW2 + edgeMargin + corrFactor)
|
|
|
|
|
sourceImage.x = -srcW2 - imgW2 + edgeMargin + corrFactor
|
|
|
|
|
else if (x > srcW2 + imgW2 - edgeMargin - corrFactor)
|
|
|
|
|
sourceImage.x = srcW2 + imgW2 - edgeMargin - corrFactor
|
|
|
|
|
|
|
|
|
|
if (sourceImage.y < -srcH2 - imgH2 + edgeMargin + corrFactor)
|
|
|
|
|
sourceImage.y = -srcH2 - imgH2 + edgeMargin + corrFactor
|
|
|
|
|
else if (y > srcH2 + imgH2 - edgeMargin - corrFactor)
|
|
|
|
|
sourceImage.y = srcH2 + imgH2 - edgeMargin - corrFactor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetTransforms() {
|
|
|
|
|
sourceImage.enableAnim(true)
|
|
|
|
|
sourceImage.scale = 1
|
|
|
|
|
sourceImage.x = root.previewMargin
|
|
|
|
|
sourceImage.y = root.previewMargin
|
|
|
|
|
sourceImage.enableAnim(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function enableAnim(flag) {
|
|
|
|
|
xBehavior.enabled = flag
|
|
|
|
|
yBehavior.enabled = flag
|
|
|
|
|
scaleBehavior.enabled = flag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onSourceChanged: sourceImage.resetTransforms()
|
|
|
|
|
|
2023-10-02 11:33:15 +03:00
|
|
|
fillMode: Image.PreserveAspectFit
|
2024-02-02 12:27:51 +02:00
|
|
|
|
|
|
|
|
x: root.previewMargin
|
|
|
|
|
y: root.previewMargin
|
|
|
|
|
width: parent.width - root.previewMargin * 2
|
|
|
|
|
height: parent.height - root.previewMargin * 2
|
2023-10-02 11:33:15 +03:00
|
|
|
source: imagesComboBox.selectedImage
|
|
|
|
|
smooth: true
|
|
|
|
|
|
2024-02-02 12:27:51 +02:00
|
|
|
Behavior on x {
|
|
|
|
|
id: xBehavior
|
|
|
|
|
|
|
|
|
|
enabled: false
|
|
|
|
|
NumberAnimation {
|
|
|
|
|
duration: 200
|
|
|
|
|
easing.type: Easing.OutQuad
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Behavior on y {
|
|
|
|
|
id: yBehavior
|
|
|
|
|
|
|
|
|
|
enabled: false
|
|
|
|
|
NumberAnimation {
|
|
|
|
|
duration: 200
|
|
|
|
|
easing.type: Easing.OutQuad
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 11:33:15 +03:00
|
|
|
Behavior on scale {
|
2024-02-02 12:27:51 +02:00
|
|
|
id: scaleBehavior
|
|
|
|
|
|
|
|
|
|
enabled: false
|
2023-10-02 11:33:15 +03:00
|
|
|
NumberAnimation {
|
|
|
|
|
duration: 200
|
|
|
|
|
easing.type: Easing.OutQuad
|
|
|
|
|
}
|
2023-08-29 23:08:43 +03:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
2023-10-02 14:20:11 +03:00
|
|
|
|
2023-12-07 18:32:55 +02:00
|
|
|
BlurHelper {
|
|
|
|
|
id: blurHelper
|
2023-12-22 16:56:11 +02:00
|
|
|
source: source
|
2023-12-07 18:32:55 +02:00
|
|
|
property int blurMax: g_propertyData.blur_helper_max_level ? g_propertyData.blur_helper_max_level : 64
|
|
|
|
|
property real blurMultiplier: g_propertyData.blurMultiplier ? g_propertyData.blurMultiplier : 0
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 14:20:11 +03:00
|
|
|
Item {
|
|
|
|
|
id: componentParent
|
|
|
|
|
width: source.width
|
|
|
|
|
height: source.height
|
2023-10-11 18:59:03 +03:00
|
|
|
anchors.centerIn: parent
|
2023-10-02 14:20:11 +03:00
|
|
|
// Cache the layer. This way heavy shaders rendering doesn't
|
|
|
|
|
// slow down code editing & rest of the UI.
|
|
|
|
|
layer.enabled: true
|
|
|
|
|
layer.smooth: true
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 14:51:21 +02:00
|
|
|
Rectangle {
|
|
|
|
|
id: zoomIndicator
|
|
|
|
|
|
|
|
|
|
width: 40
|
|
|
|
|
height: 20
|
|
|
|
|
color: StudioTheme.Values.themeDialogBackground
|
|
|
|
|
visible: false
|
|
|
|
|
|
|
|
|
|
function show() {
|
|
|
|
|
zoomIndicator.visible = true
|
|
|
|
|
zoomIndicatorTimer.start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Text {
|
|
|
|
|
text: Math.round(sourceImage.scale * 100) + "%"
|
|
|
|
|
color: StudioTheme.Values.themeTextColor
|
|
|
|
|
anchors.centerIn: parent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Timer {
|
|
|
|
|
id: zoomIndicatorTimer
|
|
|
|
|
interval: 1000
|
|
|
|
|
onTriggered: zoomIndicator.visible = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 10:34:50 +03:00
|
|
|
Connections {
|
2024-01-26 14:55:50 +02:00
|
|
|
target: effectComposerModel
|
2023-10-03 10:34:50 +03:00
|
|
|
function onShadersBaked() {
|
|
|
|
|
console.log("Shaders Baked!")
|
2023-10-11 18:59:03 +03:00
|
|
|
updateTimer.restart()
|
2023-10-03 10:34:50 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 14:20:11 +03:00
|
|
|
Timer {
|
|
|
|
|
id: updateTimer
|
2023-10-11 18:59:03 +03:00
|
|
|
interval: updateDelay
|
2023-10-02 14:20:11 +03:00
|
|
|
onTriggered: {
|
2024-01-26 14:55:50 +02:00
|
|
|
effectComposerModel.updateQmlComponent()
|
2023-10-11 18:59:03 +03:00
|
|
|
createNewComponent()
|
2023-10-02 14:20:11 +03:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-16 12:57:41 +03:00
|
|
|
}
|
|
|
|
|
}
|