forked from qt-creator/qt-creator
Change-Id: I7f74ece071923208764b767a70436145888fbb54 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
399 lines
13 KiB
QML
399 lines
13 KiB
QML
// 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
|
|
import StudioTheme as StudioTheme
|
|
import EffectComposerBackend
|
|
|
|
Column {
|
|
id: root
|
|
|
|
property real animatedTime: previewFrameTimer.elapsedTime
|
|
property int animatedFrame: previewFrameTimer.currentFrame
|
|
property bool timeRunning: previewAnimationRunning
|
|
|
|
required property Item mainRoot
|
|
property var effectComposerModel: EffectComposerBackend.effectComposerModel
|
|
property alias source: source
|
|
// The delay in ms to wait until updating the effect
|
|
readonly property int updateDelay: 100
|
|
|
|
readonly property int previewMargin: 5
|
|
readonly property int extraMargin: 200
|
|
|
|
property real previewScale: 1
|
|
|
|
// Create a dummy parent to host the effect qml object
|
|
function createNewComponent() {
|
|
// If we have a working effect, do not show preview image as it shows through
|
|
// transparent parts of the final image
|
|
placeHolder.visible = false;
|
|
|
|
var oldComponent = componentParent.children[0];
|
|
if (oldComponent)
|
|
oldComponent.destroy();
|
|
try {
|
|
const newObject = Qt.createQmlObject(
|
|
effectComposerModel.qmlComponentString(),
|
|
componentParent,
|
|
""
|
|
);
|
|
effectComposerModel.resetEffectError(0);
|
|
} 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;
|
|
}
|
|
effectComposerModel.setEffectError(errorString, 0, errorLine);
|
|
placeHolder.visible = true;
|
|
}
|
|
}
|
|
|
|
Rectangle { // toolbar
|
|
width: parent.width
|
|
height: StudioTheme.Values.toolbarHeight
|
|
color: StudioTheme.Values.themeToolbarBackground
|
|
|
|
Row {
|
|
spacing: 5
|
|
anchors.leftMargin: 5
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
PreviewImagesComboBox {
|
|
id: imagesComboBox
|
|
|
|
mainRoot: root.mainRoot
|
|
}
|
|
|
|
StudioControls.ColorEditor {
|
|
id: colorEditor
|
|
|
|
actionIndicatorVisible: false
|
|
showHexTextField: false
|
|
color: "#dddddd"
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
HelperWidgets.AbstractButton {
|
|
enabled: root.previewScale < 3
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
buttonIcon: StudioTheme.Constants.zoomIn_medium
|
|
tooltip: qsTr("Zoom In")
|
|
|
|
onClicked: {
|
|
imageScaler.enableAnim(true)
|
|
root.previewScale += .2
|
|
imageScaler.enableAnim(false)
|
|
zoomIndicator.show()
|
|
}
|
|
}
|
|
|
|
HelperWidgets.AbstractButton {
|
|
enabled: root.previewScale > .4
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
buttonIcon: StudioTheme.Constants.zoomOut_medium
|
|
tooltip: qsTr("Zoom out")
|
|
|
|
onClicked: {
|
|
imageScaler.enableAnim(true)
|
|
root.previewScale -= .2
|
|
imageScaler.enableAnim(false)
|
|
zoomIndicator.show()
|
|
}
|
|
}
|
|
|
|
HelperWidgets.AbstractButton {
|
|
enabled: root.previewScale !== 1 || imageScaler.x !== root.previewMargin
|
|
|| imageScaler.y !== root.previewMargin
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
buttonIcon: StudioTheme.Constants.fitAll_medium
|
|
tooltip: qsTr("Reset View")
|
|
|
|
onClicked: {
|
|
imageScaler.resetTransforms()
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
anchors.rightMargin: 5
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Column {
|
|
Text {
|
|
text: animatedTime >= 100
|
|
? animatedTime.toFixed(1) + " s" : animatedTime.toFixed(3) + " s"
|
|
color: StudioTheme.Values.themeTextColor
|
|
font.pixelSize: 10
|
|
}
|
|
|
|
Text {
|
|
text: (animatedFrame).toString().padStart(6, '0')
|
|
color: StudioTheme.Values.themeTextColor
|
|
font.pixelSize: 10
|
|
}
|
|
}
|
|
|
|
HelperWidgets.AbstractButton {
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
buttonIcon: StudioTheme.Constants.toStartFrame_medium
|
|
tooltip: qsTr("Restart Animation")
|
|
|
|
onClicked: {
|
|
previewFrameTimer.reset()
|
|
}
|
|
}
|
|
|
|
HelperWidgets.AbstractButton {
|
|
style: StudioTheme.Values.viewBarButtonStyle
|
|
buttonIcon: previewAnimationRunning ? StudioTheme.Constants.pause_medium
|
|
: StudioTheme.Constants.playOutline_medium
|
|
tooltip: qsTr("Play Animation")
|
|
|
|
onClicked: {
|
|
previewAnimationRunning = !previewAnimationRunning
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { // preview image
|
|
id: preview
|
|
|
|
color: colorEditor.color
|
|
width: parent.width
|
|
height: root.height - y
|
|
clip: true
|
|
|
|
MouseArea {
|
|
id: mouseArea
|
|
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
|
|
property real pressX: 0
|
|
property real pressY: 0
|
|
property bool panning: false
|
|
|
|
onPressed: {
|
|
pressX = mouseX - imageScaler.x
|
|
pressY = mouseY - imageScaler.y
|
|
panning = true
|
|
}
|
|
|
|
onReleased: {
|
|
panning = false
|
|
}
|
|
|
|
onWheel: (wheel) => {
|
|
let oldPoint = imageScaler.mapFromItem(mouseArea, Qt.point(wheel.x, wheel.y))
|
|
|
|
if (wheel.angleDelta.y > 0) {
|
|
if (root.previewScale < 3)
|
|
root.previewScale += .2
|
|
} else {
|
|
if (root.previewScale > .4)
|
|
root.previewScale -= .2
|
|
}
|
|
|
|
let newPoint = imageScaler.mapFromItem(mouseArea, Qt.point(wheel.x, wheel.y))
|
|
imageScaler.x -= (oldPoint.x - newPoint.x) * imageScaler.scale
|
|
imageScaler.y -= (oldPoint.y - newPoint.y) * imageScaler.scale
|
|
|
|
imageScaler.checkBounds()
|
|
zoomIndicator.show()
|
|
}
|
|
|
|
Timer { // pan timer
|
|
running: parent.panning
|
|
interval: 16
|
|
repeat: true
|
|
|
|
onTriggered: {
|
|
imageScaler.x = mouseArea.mouseX - mouseArea.pressX
|
|
imageScaler.y = mouseArea.mouseY - mouseArea.pressY
|
|
imageScaler.checkBounds()
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
id: placeHolder
|
|
anchors.fill: parent
|
|
anchors.margins: root.previewMargin
|
|
fillMode: Image.PreserveAspectFit
|
|
source: imagesComboBox.selectedImage
|
|
smooth: true
|
|
}
|
|
|
|
Item { // Source item as a canvas (render target) for effect
|
|
id: source
|
|
width: sourceImage.sourceSize.width
|
|
height: sourceImage.sourceSize.height
|
|
layer.enabled: true
|
|
layer.mipmap: true
|
|
layer.smooth: true
|
|
layer.sourceRect: Qt.rect(-root.extraMargin, -root.extraMargin,
|
|
width + root.extraMargin * 2, height + root.extraMargin * 2)
|
|
visible: false
|
|
|
|
Image {
|
|
id: sourceImage
|
|
|
|
onSourceChanged: imageScaler.resetTransforms()
|
|
|
|
fillMode: Image.Pad
|
|
|
|
source: imagesComboBox.selectedImage
|
|
smooth: true
|
|
}
|
|
}
|
|
|
|
BlurHelper {
|
|
id: blurHelper
|
|
source: source
|
|
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
|
|
}
|
|
|
|
Item {
|
|
id: imageScaler
|
|
x: root.previewMargin
|
|
y: root.previewMargin
|
|
width: parent.width - root.previewMargin * 2
|
|
height: parent.height - root.previewMargin * 2
|
|
|
|
scale: root.previewScale * (width > height ? height / sourceImage.sourceSize.height
|
|
: width / sourceImage.sourceSize.width)
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
Behavior on scale {
|
|
id: scaleBehavior
|
|
|
|
enabled: false
|
|
NumberAnimation {
|
|
duration: 200
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
}
|
|
|
|
function checkBounds() {
|
|
let edgeMargin = 10
|
|
// correction factor to account for an observation that edgeMargin decreases
|
|
// with increased zoom
|
|
let corrFactor = 10 * imageScaler.scale
|
|
let imgW2 = sourceImage.paintedWidth * imageScaler.scale * .5
|
|
let imgH2 = sourceImage.paintedHeight * imageScaler.scale * .5
|
|
let srcW2 = width * .5
|
|
let srcH2 = height * .5
|
|
|
|
if (imageScaler.x < -srcW2 - imgW2 + edgeMargin + corrFactor)
|
|
imageScaler.x = -srcW2 - imgW2 + edgeMargin + corrFactor
|
|
else if (x > srcW2 + imgW2 - edgeMargin - corrFactor)
|
|
imageScaler.x = srcW2 + imgW2 - edgeMargin - corrFactor
|
|
|
|
if (imageScaler.y < -srcH2 - imgH2 + edgeMargin + corrFactor)
|
|
imageScaler.y = -srcH2 - imgH2 + edgeMargin + corrFactor
|
|
else if (y > srcH2 + imgH2 - edgeMargin - corrFactor)
|
|
imageScaler.y = srcH2 + imgH2 - edgeMargin - corrFactor
|
|
}
|
|
|
|
function resetTransforms() {
|
|
imageScaler.enableAnim(true)
|
|
root.previewScale = 1
|
|
imageScaler.x = root.previewMargin
|
|
imageScaler.y = root.previewMargin
|
|
imageScaler.enableAnim(false)
|
|
}
|
|
|
|
function enableAnim(flag) {
|
|
xBehavior.enabled = flag
|
|
yBehavior.enabled = flag
|
|
scaleBehavior.enabled = flag
|
|
}
|
|
|
|
Item {
|
|
id: componentParent
|
|
width: source.width
|
|
height: source.height
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: zoomIndicator
|
|
|
|
width: 40
|
|
height: 20
|
|
color: StudioTheme.Values.themeDialogBackground
|
|
visible: false
|
|
|
|
function show() {
|
|
zoomIndicator.visible = true
|
|
zoomIndicatorTimer.start()
|
|
}
|
|
|
|
Text {
|
|
text: Math.round(root.previewScale * 100) + "%"
|
|
color: StudioTheme.Values.themeTextColor
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
Timer {
|
|
id: zoomIndicatorTimer
|
|
interval: 1000
|
|
onTriggered: zoomIndicator.visible = false
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: effectComposerModel
|
|
function onShadersBaked() {
|
|
updateTimer.restart()
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: updateTimer
|
|
interval: updateDelay
|
|
onTriggered: {
|
|
effectComposerModel.updateQmlComponent()
|
|
createNewComponent()
|
|
}
|
|
}
|
|
}
|
|
}
|