/****************************************************************************
**
** Copyright (C) 2024 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick Designer Components.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick
import QtQuick.Layouts
import HelperWidgets
import StudioTheme as StudioTheme
import StudioControls as StudioControls
Column {
id: root
property var model
// Draggging
property Item draggedSec: null
property var secsY: []
property int moveFromIdx: 0
property int moveToIdx: 0
anchors.left: parent.left
anchors.right: parent.right
function invalidate() {
root.model = null
root.model = modelNodeBackend.allChildren() // ids for all effects
}
function handleDragMove() {
root.dragTimer.stop()
if (root.secsY.length === 0) {
for (let i = 0; i < repeater.count; ++i)
root.secsY[i] = repeater.itemAt(i).y
}
let scrollView = Controller.mainScrollView
let oldContentY = scrollView.contentY
if (root.draggedSec.y < scrollView.dragScrollMargin + scrollView.contentY
&& scrollView.contentY > 0) {
scrollView.contentY -= scrollView.dragScrollMargin / 2
} else if (root.draggedSec.y > scrollView.contentY + scrollView.height - scrollView.dragScrollMargin
&& scrollView.contentY < scrollView.contentHeight - scrollView.height) {
scrollView.contentY += scrollView.dragScrollMargin / 2
if (scrollView.contentY > scrollView.contentHeight - scrollView.height)
scrollView.contentY = scrollView.contentHeight - scrollView.height
}
if (scrollView.contentY < 0)
scrollView.contentY = 0
if (oldContentY !== scrollView.contentY) {
// Changing dragged section position in drag handler doesn't seem to stick
// when triggered by mouse move, so do it again async
root.dragTimer.targetY = root.draggedSec.y - oldContentY + scrollView.contentY
root.dragTimer.restart()
root.dragConnection.enabled = false
root.draggedSec.y = root.dragTimer.targetY
root.dragConnection.enabled = true
}
root.moveToIdx = root.moveFromIdx
for (let i = 0; i < repeater.count; ++i) {
let currItem = repeater.itemAt(i)
if (i > root.moveFromIdx) {
if (root.draggedSec.y > currItem.y) {
currItem.y = root.secsY[i] - root.draggedSec.height - nodesCol.spacing
root.moveToIdx = i
} else {
currItem.y = root.secsY[i]
}
} else if (i < root.moveFromIdx) {
if (root.draggedSec.y < currItem.y) {
currItem.y = root.secsY[i] + root.draggedSec.height + nodesCol.spacing
root.moveToIdx = Math.min(root.moveToIdx, i)
} else {
currItem.y = root.secsY[i]
}
}
}
}
property Connections connection: Connections {
target: modelNodeBackend
function onSelectionChanged() { root.invalidate() }
function onSelectionToBeChanged() { root.model = [] }
}
property Connections dragConnection: Connections {
target: root.draggedSec
function onYChanged() { root.handleDragMove() }
}
property Timer dragTimer: Timer {
running: false
interval: 16
repeat: false
property real targetY: -1
onTriggered: {
// Ensure we get position change triggers even if user holds mouse still to
// make scrolling smooth
root.draggedSec.y = targetY
root.handleDragMove()
}
}
Section {
caption: qsTr('Design Effect [beta]').arg(StudioTheme.Values.themeInteraction)
anchors.left: parent.left
anchors.right: parent.right
leftPadding: 0
SectionLayout {
x: StudioTheme.Values.sectionLeftPadding
PropertyLabel {
text: qsTr("Visible")
tooltip: qsTr("Toggles the visibility of visual effects on the component.")
}
SecondColumnLayout {
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.visible
}
ExpandingSpacer {}
}
}
Item {
width: 1
height: StudioTheme.Values.sectionHeadSpacerHeight
}
Column {
id: nodesCol
anchors.left: parent.left
anchors.right: parent.right
spacing: 1
Section {
sectionHeight: 37
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("Layer Blur")
labelCapitalization: Font.MixedCase
category: "DesignEffects"
expanded: false
SectionLayout {
PropertyLabel {
text: qsTr("Visible")
tooltip: qsTr("Toggles the visibility of the Layer Blur on the component.")
}
SecondColumnLayout {
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.layerBlurVisible
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Blur")
tooltip: qsTr("Sets the intensity of the Layer Blur on the component.")
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.layerBlurRadius
minimumValue: 0
maximumValue: 250
}
ExpandingSpacer {}
}
}
}
Section {
sectionHeight: 37
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("Background Blur")
labelCapitalization: Font.MixedCase
category: "DesignEffects"
expanded: false
SectionLayout {
PropertyLabel {
text: qsTr("Visible")
tooltip: qsTr("Toggles the visibility of blur on the selected background component.")
}
SecondColumnLayout {
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.backgroundBlurVisible
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Blur")
tooltip: qsTr("Sets the intensity of blur on the selected background component.\n"
+ "The foreground component should be transparent, and the background "
+ "component should be fully opaque.")
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: backendValues.backgroundBlurRadius
minimumValue: 0
maximumValue: 250
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Background")
tooltip: qsTr("Sets a component as the background of a transparent component."
+ "The Background Blur works only on this component. The component should "
+ "be solid.")
}
SecondColumnLayout {
ItemFilterComboBox {
implicitWidth: StudioTheme.Values.singleControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
width: implicitWidth
typeFilter: "QtQuick.Item"
backendValue: backendValues.backgroundLayer
}
ExpandingSpacer {}
}
}
}
Repeater {
id: repeater
model: root.model
Section {
id: delegate
property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData)
property bool wasExpanded: false
Behavior on y {
id: dragAnimation
PropertyAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
onStartDrag: function(section) {
root.draggedSec = section
root.moveFromIdx = index
// We only need to animate non-dragged sections
dragAnimation.enabled = false
delegate.wasExpanded = delegate.expanded
delegate.expanded = false
delegate.highlightBorder = true
root.secsY = []
}
onStopDrag: {
if (root.secsY.length !== 0) {
if (root.moveFromIdx === root.moveToIdx)
root.draggedSec.y = root.secsY[root.moveFromIdx]
else
modelNodeBackend.moveNode(-1, "effects", root.moveFromIdx, root.moveToIdx)
}
delegate.highlightBorder = false
root.draggedSec = null
delegate.expanded = delegate.wasExpanded
dragAnimation.enabled = true
Qt.callLater(root.invalidate)
}
sectionHeight: 37
anchors.left: parent.left
anchors.right: parent.right
category: "DesignEffects"
fillBackground: true
expanded: false
draggable: true
showCloseButton: true
content: StudioControls.ComboBox {
id: shadowComboBox
actionIndicatorVisible: false
width: 200
textRole: "text"
valueRole: "value"
model: [
{ value: "DesignDropShadow", text: qsTr("Drop Shadow") },
{ value: "DesignInnerShadow", text: qsTr("Inner Shadow") }
]
anchors.verticalCenter: parent.verticalCenter
// When an item is selected, update the backend.
onActivated: {
delegate.wrapper.properties.showBehind.resetValue()
modelNodeBackend.changeType(modelData, shadowComboBox.currentValue)
}
// Set the initial currentIndex to the value stored in the backend.
Component.onCompleted: {
shadowComboBox.currentIndex = shadowComboBox.indexOfValue(modelNodeBackend.simplifiedTypeName(modelData))
}
}
onCloseButtonClicked: {
delegate.wrapper.deleteModelNode()
Qt.callLater(root.invalidate)
}
SectionLayout {
id: controlContainer
property bool isDropShadow: shadowComboBox.currentValue === "DesignDropShadow"
PropertyLabel {
text: qsTr("Visible")
tooltip: qsTr("Toggles the visibility of the component shadow.")
}
SecondColumnLayout {
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.visible
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Blur")
tooltip: qsTr("Sets the softness of the component shadow. A larger value"
+ " causes the edges of the shadow to appear more blurry.")
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.blur
minimumValue: 0
maximumValue: 250
}
ExpandingSpacer {}
}
// Spread is disabled for now as we can't tell from here if the
// item this effect is assigned to is a Rectangle or not
PropertyLabel {
text: qsTr("Spread")
tooltip: qsTr("You must select the component itself to change this property.")
enabled: false
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.spread
enabled: false
minimumValue: -2048
maximumValue: 2048
}
ExpandingSpacer {}
}
PropertyLabel {
text: qsTr("Color")
tooltip: qsTr("Sets the color of the shadow.")
}
ColorEditor {
backendValue: delegate.wrapper.properties.color
supportGradient: false
}
PropertyLabel {
text: qsTr("Offset")
tooltip: qsTr("Moves the shadow with respect to the component in "
+ "X and Y coordinates by pixels.")
}
SecondColumnLayout {
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.offsetX
minimumValue: -0xffff
maximumValue: 0xffff
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "X"
tooltip: qsTr("X-coordinate")
}
Spacer { implicitWidth: StudioTheme.Values.controlGap }
SpinBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.offsetY
minimumValue: -0xffff
maximumValue: 0xffff
}
Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
ControlLabel {
text: "Y"
tooltip: qsTr("Y-coordinate")
}
ExpandingSpacer {}
}
PropertyLabel {
visible: controlContainer.isDropShadow
text: qsTr("Show behind")
tooltip: qsTr("Toggles the visibility of the shadow behind a transparent component.")
}
SecondColumnLayout {
visible: controlContainer.isDropShadow
CheckBox {
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
backendValue: delegate.wrapper.properties.showBehind
}
ExpandingSpacer {}
}
}
}
}
}
Item {
width: 1
height: StudioTheme.Values.sectionHeadSpacerHeight
}
SectionLayout {
x: StudioTheme.Values.sectionLeftPadding
PropertyLabel {}
SecondColumnLayout {
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
AbstractButton {
id: addShadowEffectButton
implicitWidth: StudioTheme.Values.singleControlColumnWidth
width: StudioTheme.Values.singleControlColumnWidth
buttonIcon: qsTr("Add Shadow Effect")
iconFont: StudioTheme.Constants.font
tooltip: qsTr("Adds Drop Shadow or Inner Shadow effects to a component.")
onClicked: {
modelNodeBackend.createModelNode(-1, "effects", "DesignDropShadow")
root.invalidate()
}
}
ExpandingSpacer {}
}
}
}
}