Files
qt-creator/share/qtcreator/qmldesigner/effectComposerQmlSources/AddPropertyForm.qml
Miikka Heikkinen 01b8a26fa5 EffectComposer: Move open code button
Fixes: QDS-14218
Change-Id: I3f622ef649438475f85bd902d3b2547d64dffdde
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
2024-11-28 09:00:45 +00:00

765 lines
27 KiB
QML

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Controls
import StudioControls as StudioControls
import StudioTheme as StudioTheme
import HelperWidgets as HelperWidgets
import EffectComposerBackend
Item {
id: root
height: column.implicitHeight + (root.verticalSpacing * 4)
readonly property real horizontalSpacing: 10
readonly property real verticalSpacing: 5
readonly property real labelWidth: 140
readonly property real controlWidth: (root.width - labelWidth - root.horizontalSpacing
- (StudioTheme.Values.popupMargin * 2))
property var backendModel: EffectComposerBackend.effectComposerModel
property string effectNodeName
property int typeIndex: 1
property string displayName
property string uniName
property string description
property var minValue: 0
property var maxValue: 1
property var defaultValue: 0
property bool showMinMax: false
property var minPossibleValue: 0
property var maxPossibleValue: 0
property bool userAdded: false
property bool uniNameEdited: false
property real minFloat: root.backendModel.valueLimit("float", false)
property real maxFloat: root.backendModel.valueLimit("float", true)
property int minInt: root.backendModel.valueLimit("int", false)
property int maxInt: root.backendModel.valueLimit("int", true)
property var reservedDispNames: []
property var reservedUniNames: []
property bool propNameError: root.reservedDispNames.includes(nameText.text)
property bool uniNameError: root.reservedUniNames.includes(uniNameText.text)
property var typeList: [
{"type": "int", "controlType": "int"},
{"type": "float", "controlType": "float"},
{"type": "bool", "controlType": "bool"},
{"type": "vec2", "controlType": "vec2"},
{"type": "vec3", "controlType": "vec3"},
{"type": "vec4", "controlType": "vec4"},
{"type": "color", "controlType": "color"},
{"type": "int", "controlType": "channel"},
{"type": "sampler2D", "controlType": "sampler2D"},
{"type": "define", "controlType": "int"},
{"type": "define", "controlType": "bool"}
]
property var displayTypes: [
qsTr("Integer"),
qsTr("Float"),
qsTr("Boolean"),
qsTr("Vector 2D"),
qsTr("Vector 3D"),
qsTr("Vector 4D"),
qsTr("Color"),
qsTr("Color channel"),
qsTr("Texture sampler"),
qsTr("Define (integer)"),
qsTr("Define (boolean)")
]
property vector4d compareMatches: Qt.vector4d(0, 0, 0, 0)
// Backend value for sampler controls (UrlChooser needs this to function)
property var urlChooserBackendValue: QtObject {
id: urlChooserValue
property bool isBound: false
property var expression
property string valueToString
property var value
signal valueChangedQml()
onValueChanged: {
urlChooserValue.valueToString = value.toString()
urlChooserValue.valueChangedQml()
}
}
signal accepted()
signal canceled()
// If vector is assigned to variant, it just creates reference by default, which breaks the
// min/max logic. This function creates a deep copy of vectors.
function copyValue(v)
{
if (root.typeList[root.typeIndex].type === "vec2")
return Qt.vector2d(v.x, v.y)
else if (root.typeList[root.typeIndex].type === "vec3")
return Qt.vector3d(v.x, v.y, v.z)
else if (root.typeList[root.typeIndex].type === "vec4")
return Qt.vector4d(v.x, v.y, v.z, v.w)
return v
}
// For vectors, copies values of previous comparison matched subcomponents only
function copyMatchedSubcomponents(v, orig)
{
let isVec2 = root.typeList[root.typeIndex].type === "vec2"
let isVec3 = root.typeList[root.typeIndex].type === "vec3"
let isVec4 = root.typeList[root.typeIndex].type === "vec4"
if (isVec2 || isVec3 || isVec4) {
let vx = root.compareMatches.x > 0 ? v.x : orig.x
let vy = root.compareMatches.y > 0 ? v.y : orig.y
if (isVec3 || isVec4) {
let vz = root.compareMatches.z > 0 ? v.z : orig.z
if (isVec4) {
let vw = root.compareMatches.w > 0 ? v.w : orig.w
return Qt.vector4d(vx, vy, vz, vw)
} else {
return Qt.vector3d(vx, vy, vz)
}
} else {
return Qt.vector2d(vx, vy)
}
}
return v
}
function showForAdd()
{
root.uniNameEdited = false
if (typeCombo.currentIndex === 0)
reloadType()
else
typeCombo.currentIndex = 0
root.displayName = root.backendModel.getUniqueDisplayName(root.reservedDispNames)
root.uniName = root.backendModel.generateUniformName(root.effectNodeName, root.displayName, "")
root.description = ""
root.userAdded = true
titleLabel.text = qsTr("Add property")
root.visible = true
}
function showForEdit(type, controlType, dispName, name, desc, defVal, min, max, user)
{
root.uniNameEdited = true
let targetIndex = typeList.findIndex(function(element) {
return element.type === type && element.controlType === controlType;
})
if (targetIndex < 0)
return;
root.userAdded = user
if (typeCombo.currentIndex === targetIndex)
reloadType()
else
typeCombo.currentIndex = targetIndex
root.displayName = dispName
root.description = desc
root.uniName = name
if (root.showMinMax) {
root.minValue = root.copyValue(root.minPossibleValue)
root.maxValue = root.copyValue(root.maxPossibleValue)
root.maxValue = root.copyValue(max)
root.minValue = root.copyValue(min)
minValueLoader.uniformValue = root.copyValue(min)
maxValueLoader.uniformValue = root.copyValue(max)
}
root.defaultValue = root.copyValue(defVal)
if (root.urlChooserBackendValue)
root.urlChooserBackendValue.value = root.copyValue(root.defaultValue)
defaultValueLoader.uniformValue = root.copyValue(defVal)
titleLabel.text = qsTr("Edit property")
root.visible = true
}
function propertyData()
{
let propData = {
"type": root.typeList[root.typeIndex].type,
"controlType": root.typeList[root.typeIndex].controlType,
"displayName": root.displayName,
"name": root.uniName,
"description": root.description,
"minValue": root.minValue,
"maxValue": root.maxValue,
"defaultValue": root.defaultValue,
"userAdded": root.userAdded
}
return propData;
}
// Returns true if variant a is considered larger than variant b.
// In case of vectors, any subcomponent being larger qualifies.
// root.compareMatches subproperties are set to non-zero to indicate which subproperties
// were matched.
function compareValues(a, b)
{
let isVec2 = root.typeList[root.typeIndex].type === "vec2"
let isVec3 = root.typeList[root.typeIndex].type === "vec3"
let isVec4 = root.typeList[root.typeIndex].type === "vec4"
if (isVec2 || isVec3 || isVec4) {
root.compareMatches = Qt.vector4d(0, 0, 0, 0)
let match = false
if (a.x > b.x) {
match = true
root.compareMatches.x = 1
}
if (a.y > b.y) {
match = true
root.compareMatches.y = 1
}
if (isVec3 || isVec4) {
if (a.z > b.z) {
match = true
root.compareMatches.z = 1
}
if (isVec4) {
if (a.w > b.w) {
match = true
root.compareMatches.w = 1
}
}
}
return match
} else {
return a > b
}
}
function reloadType()
{
defaultValueLoader.source = ""
minValueLoader.source = ""
maxValueLoader.source = ""
defaultValueLoader.uniformBackendValue = null
var sourceQml
let hasMinMax = false
if (root.typeList[root.typeIndex].controlType === "int") {
root.minPossibleValue = root.minInt
root.maxPossibleValue = root.maxInt
root.defaultValue = 0
root.minValue = 0
root.maxValue = 100
hasMinMax = true
sourceQml = "ValueInt.qml"
} else if (root.typeList[root.typeIndex].controlType === "channel") {
root.minPossibleValue = root.minInt
root.maxPossibleValue = root.maxInt
root.defaultValue = 3
root.minValue = 0
root.maxValue = 3
hasMinMax = false // Color channels have hardcoded min/max, so don't show controls
sourceQml= "ValueChannel.qml"
} else if (root.typeList[root.typeIndex].controlType === "vec2") {
root.minPossibleValue = Qt.vector2d(root.minFloat, root.minFloat)
root.maxPossibleValue = Qt.vector2d(root.maxFloat, root.maxFloat)
root.defaultValue = Qt.vector2d(0, 0)
root.minValue = Qt.vector2d(0, 0)
root.maxValue = Qt.vector2d(1, 1)
hasMinMax = true
sourceQml = "ValueVec2.qml"
} else if (root.typeList[root.typeIndex].controlType === "vec3") {
root.minPossibleValue = Qt.vector3d(root.minFloat, root.minFloat, root.minFloat)
root.maxPossibleValue = Qt.vector3d(root.maxFloat, root.maxFloat, root.maxFloat)
root.defaultValue = Qt.vector3d(0, 0, 0)
root.minValue = Qt.vector3d(0, 0, 0)
root.maxValue = Qt.vector3d(1, 1, 1)
hasMinMax = true
sourceQml = "ValueVec3.qml"
} else if (root.typeList[root.typeIndex].controlType === "vec4") {
root.minPossibleValue = Qt.vector4d(root.minFloat, root.minFloat, root.minFloat, root.minFloat)
root.maxPossibleValue = Qt.vector4d(root.maxFloat, root.maxFloat, root.maxFloat, root.maxFloat)
root.defaultValue = Qt.vector4d(0, 0, 0, 0)
root.minValue = Qt.vector4d(0, 0, 0, 0)
root.maxValue = Qt.vector4d(1, 1, 1, 1)
hasMinMax = true
sourceQml = "ValueVec4.qml"
} else if (root.typeList[root.typeIndex].controlType === "bool") {
root.defaultValue = false
sourceQml = "ValueBool.qml"
} else if (root.typeList[root.typeIndex].controlType === "color") {
root.defaultValue = Qt.rgba(0, 0, 0, 1)
sourceQml = "ValueColor.qml"
} else if (root.typeList[root.typeIndex].controlType === "sampler2D") {
root.defaultValue = ""
sourceQml = "ValueImage.qml"
defaultValueLoader.uniformBackendValue = root.urlChooserBackendValue
root.urlChooserBackendValue.value = root.copyValue(root.defaultValue)
} else {
root.minPossibleValue = root.minFloat
root.maxPossibleValue = root.maxFloat
root.defaultValue = 0
root.minValue = 0
root.maxValue = 1
hasMinMax = true
sourceQml = "ValueFloat.qml"
}
if (hasMinMax) {
minValueLoader.uniformValue = root.copyValue(root.minValue)
maxValueLoader.uniformValue = root.copyValue(root.maxValue)
minValueLoader.source = sourceQml
maxValueLoader.source = sourceQml
}
defaultValueLoader.uniformValue = root.copyValue(root.defaultValue)
defaultValueLoader.source = sourceQml
root.showMinMax = hasMinMax
}
onMinValueChanged: {
if (!root.showMinMax)
return
if (root.compareValues(root.minValue, root.maxValue)) {
root.maxValue = root.copyMatchedSubcomponents(root.minValue, root.maxValue)
maxValueLoader.uniformValue = root.copyValue(root.maxValue)
}
if (root.compareValues(root.minValue, root.defaultValue)) {
root.defaultValue = root.copyMatchedSubcomponents(root.minValue, root.defaultValue)
defaultValueLoader.uniformValue = root.copyValue(root.defaultValue)
}
}
onMaxValueChanged: {
if (!root.showMinMax)
return
if (root.compareValues(root.minValue, root.maxValue)) {
root.minValue = root.copyMatchedSubcomponents(root.maxValue, root.minValue)
minValueLoader.uniformValue = root.copyValue(root.minValue)
}
if (root.compareValues(root.defaultValue, root.maxValue)) {
root.defaultValue = root.copyMatchedSubcomponents(root.maxValue, root.defaultValue)
defaultValueLoader.uniformValue = root.copyValue(root.defaultValue)
}
}
onTypeIndexChanged: reloadType()
Rectangle {
anchors.fill: parent
anchors.topMargin: 8
anchors.rightMargin: 8
border.width: 1
border.color: StudioTheme.Values.themeControlOutline
color: StudioTheme.Values.themeSectionHeadBackground
Column {
id: column
anchors.fill: parent
anchors.topMargin: root.verticalSpacing
anchors.bottomMargin: root.verticalSpacing
spacing: root.verticalSpacing
Text {
id: titleLabel
color: StudioTheme.Values.themeControlOutlineInteraction
font.bold: true
font.pixelSize: StudioTheme.Values.baseFontSize
width: parent.width
height: 30
horizontalAlignment: Text.AlignHCenter
}
Row {
spacing: root.horizontalSpacing
width: parent.width
Text {
id: nameLabel
color: StudioTheme.Values.themeTextColor
text: qsTr("Display Name")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the display name of the property.")
}
}
StudioControls.TextField {
id: nameText
width: root.controlWidth
anchors.verticalCenter: parent.verticalCenter
actionIndicatorVisible: false
translationIndicatorVisible: false
text: root.displayName
KeyNavigation.tab: uniNameText
onEditingFinished: {
root.displayName = nameText.text
if (!root.uniNameEdited) {
root.uniName = root.backendModel.generateUniformName(
root.effectNodeName, root.displayName, root.uniName)
}
}
}
}
// Error line (for invalid name)
Row {
width: parent.width
spacing: root.horizontalSpacing
Item { // Spacer
width: root.labelWidth
height: 1
}
Text {
text: qsTr("Display name of the property has to be unique.")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.controlWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
visible: root.propNameError
color: StudioTheme.Values.themeWarning
}
}
Row {
spacing: root.horizontalSpacing
width: parent.width
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Uniform Name")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the uniform name of the property.")
}
}
StudioControls.TextField {
id: uniNameText
width: root.controlWidth
anchors.verticalCenter: parent.verticalCenter
actionIndicatorVisible: false
translationIndicatorVisible: false
text: root.uniName
KeyNavigation.tab: descriptionEdit
onEditingFinished:{
if (root.uniName !== uniNameText.text) {
root.uniName = uniNameText.text
root.uniNameEdited = true
}
}
}
}
// Error line (for invalid uniform name)
Row {
width: parent.width
spacing: root.horizontalSpacing
Item { // Spacer
width: root.labelWidth
height: 1
}
Text {
text: qsTr("Uniform name has to be unique.")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.controlWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
visible: root.uniNameError
color: StudioTheme.Values.themeWarning
}
}
Row {
spacing: root.horizontalSpacing
width: parent.width
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Description")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the property description.")
}
}
Rectangle {
color: StudioTheme.Values.controlStyle.background.idle
border.color: StudioTheme.Values.controlStyle.border.idle
border.width: StudioTheme.Values.controlStyle.borderWidth
height: descriptionEdit.height
width: root.controlWidth
clip: true
TextEdit {
id: descriptionEdit
padding: StudioTheme.Values.controlStyle.inputHorizontalPadding
width: parent.width
height: descriptionEdit.contentHeight + descriptionEdit.topPadding
+ descriptionEdit.bottomPadding
font.pixelSize: StudioTheme.Values.baseFontSize
color: StudioTheme.Values.themeTextColor
wrapMode: TextEdit.WordWrap
text: root.description
KeyNavigation.tab: typeCombo
onEditingFinished: root.description = descriptionEdit.text
}
}
}
Row {
width: parent.width
spacing: root.horizontalSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Type")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the property type.")
}
}
StudioControls.ComboBox {
id: typeCombo
width: root.controlWidth
model: root.displayTypes
actionIndicatorVisible: false
onCurrentIndexChanged: root.typeIndex = currentIndex
}
}
Row {
width: parent.width
spacing: root.horizontalSpacing
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Default Value")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the default value of the property.")
}
}
Loader {
id: defaultValueLoader
property var uniformValue
// No need to deep copy min/max as these bindings do not change the value
property var uniformMinValue: root.minValue
property var uniformMaxValue: root.maxValue
// These are needed for sampler controls (ValueImage type)
property var uniformBackendValue
property string uniformName: root.uniName
property var uniformDefaultValue
width: root.controlWidth
Connections {
target: defaultValueLoader.item
function onValueChanged() {
root.defaultValue = root.copyValue(defaultValueLoader.uniformValue)
}
}
}
}
Row {
width: parent.width
spacing: root.horizontalSpacing
visible: root.showMinMax
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Min Value")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the minimum value of the property.")
}
}
Loader {
id: minValueLoader
property var uniformValue
// No need to deep copy min/max as these bindings do not change the value
property var uniformMinValue: root.minPossibleValue
property var uniformMaxValue: root.maxValue
width: root.controlWidth
Connections {
target: minValueLoader.item
function onValueChanged() {
root.minValue = root.copyValue(minValueLoader.uniformValue)
}
}
onLoaded: {
if (root.typeList[root.typeIndex].controlType === "int"
|| root.typeList[root.typeIndex].controlType === "float") {
item.hideSlider = true
}
}
}
}
Row {
width: parent.width
spacing: root.horizontalSpacing
visible: root.showMinMax
Text {
color: StudioTheme.Values.themeTextColor
text: qsTr("Max Value")
font.pixelSize: StudioTheme.Values.baseFontSize
anchors.verticalCenter: parent.verticalCenter
width: root.labelWidth
elide: Text.ElideRight
horizontalAlignment: Text.AlignRight
StudioControls.ToolTipArea {
anchors.fill: parent
text: qsTr("Sets the maximum value of the property.")
}
}
Loader {
id: maxValueLoader
property var uniformValue
// No need to deep copy min/max as these bindings do not change the value
property var uniformMinValue: root.minValue
property var uniformMaxValue: root.maxPossibleValue
width: root.controlWidth
Connections {
target: maxValueLoader.item
function onValueChanged() {
root.maxValue = root.copyValue(maxValueLoader.uniformValue)
}
}
onLoaded: {
if (root.typeList[root.typeIndex].controlType === "int"
|| root.typeList[root.typeIndex].controlType === "float") {
item.hideSlider = true
}
}
}
}
Row {
id: buttonRow
width: acceptButton.width + root.horizontalSpacing + cancelButton.width
spacing: root.horizontalSpacing
x: (parent.width - buttonRow.width) / 2 + 6
height: 30
HelperWidgets.Button {
id: cancelButton
width: 100
height: 30
text: qsTr("Cancel")
padding: 4
onClicked: {
root.canceled()
root.visible = false
}
}
HelperWidgets.Button {
id: acceptButton
width: 100
height: 30
text: qsTr("Apply")
padding: 4
enabled: !root.propNameError && !root.uniNameError
onClicked: {
root.accepted()
root.visible = false
}
}
}
}
}
}