// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs import QQEMLib 1.0 Item { id: rootItem property alias uniformModel: uniformTable.model property bool showSettings: false property real showSettingsAnimated: showSettings function truncateString(str, n) { return (str.length > n) ? str.substr(0, n - 3) + '...' : str; } Behavior on showSettingsAnimated { NumberAnimation { duration: 500 easing.type: Easing.InOutQuad } } FileDialog { id: textureSourceDialog property int modelIndex: -1 title: "Select an Image File" nameFilters: [ effectManager.getSupportedImageFormatsFilter() ] onAccepted: { if (textureSourceDialog.selectedFile !== null) { effectManager.uniformModel.setImage(modelIndex, textureSourceDialog.selectedFile); } } } ColorDialog { id: colorPickerDialog property var propertyItem: null property color initialColor: "#000000" options: ColorDialog.ShowAlphaChannel onSelectedColorChanged: { if (propertyItem) propertyItem.updateValue(selectedColor); } onAccepted: { if (propertyItem) propertyItem.updateValue(selectedColor); } onRejected: { if (propertyItem) propertyItem.updateValue(initialColor); } } Rectangle { anchors.fill: parent color: mainView.backgroundColor1 } ColumnLayout { anchors.fill: parent clip: true spacing: 0 Item { Layout.fillWidth: true height: childrenRect.height + 10 Row { id: columnsHeader Layout.fillWidth: true spacing: 10 Item { id: addPropertyButton width: 10 + (1.0 - designModeAnimated) * uniformTable.columnWidth(0) height: parent.height opacity: (1.0 - designModeAnimated) CustomIconButton { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right height: 24 width: height icon: "images/icon_add.png" enabled: !designMode && effectManager.nodeView.effectNodeSelected description: "Add property" onClicked: { mainWindow.addPropertyAction(); } } } Label { anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(1) text: "TYPE" verticalAlignment: Text.AlignVCenter font.pixelSize: 12 font.bold: true } Label { anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(2) text: "NAME" verticalAlignment: Text.AlignVCenter font.pixelSize: 12 font.bold: true } Label { anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(3) text: "VALUE" verticalAlignment: Text.AlignVCenter font.pixelSize: 12 font.bold: true } Item { width: uniformTable.columnWidth(4) height: 1 } CustomIconButton { anchors.verticalCenter: parent.verticalCenter height: 24 width: height icon: "images/icon_settings.png" toggledIcon: "images/icon_settings_on.png" toggleButton: true toggled: showSettings description: "Show / hide property settings" onClicked: { showSettings = !showSettings; } } } } Rectangle { Layout.fillWidth: true height: 1 z: -1 color: mainView.backgroundColor2 } ListView { id: uniformTable Layout.fillHeight: true Layout.fillWidth: true flickableDirection: Flickable.VerticalFlick model: effectManager.uniformModel clip: true cacheBuffer: 10000 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOn } property var typeStrings: [ "bool", "int", "float", "vec2", "vec3", "vec4", "color", "image", "define" ] function convertValueToString(value, type) { if (type === 0) { // bool return String(value); } if (type === 1) { // int return String(value.toFixed(0)); } if (type === 2) { // float return String(value.toFixed(3)); } if (type === 3) { // vec2 return "(" + value.x.toFixed(3) + ", " + value.y.toFixed(3) + ")" } if (type === 4) { // vec3 return "(" + value.x.toFixed(3) + ", " + value.y.toFixed(3) + ", " + value.z.toFixed(3) + ")" } if (type === 5) { // vec4 return "(" + value.x.toFixed(3) + ", " + value.y.toFixed(3) + ", " + value.z.toFixed(3) + ", " + value.w.toFixed(3) + ")" } if (type === 6) { // color return "(" + value.r.toFixed(3) + ", " + value.g.toFixed(3) + ", " + value.b.toFixed(3) + ", " + value.a.toFixed(3) + ")" } if (type === 7) { // sampler, show filename. var text = String(value).slice(value.lastIndexOf('/') + 1); return text; } if (type === 8) { // define return String(value); } } function columnWidth(column) { var w = uniformTable.width - (120 - 20 * designModeAnimated); if (column === 0) return 20; if (column === 1) return w * 0.10; if (column === 2) return w * 0.40; if (column === 3) return w * 0.20; if (column === 4) return w * 0.30; if (column === 5) return 20; return 0; } delegate: Item { width: uniformTable.width height: visible ? controlsItem.height : 0 visible: model.visible Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.leftMargin: 10 width: parent.width - 40 height: 1 z: -1 color: mainView.backgroundColor2 } Row { id: propertyListItemRow x: 10 spacing: 10 height: parent.height Item { id: editPropertyButton anchors.verticalCenter: parent.verticalCenter width: 1 + opacity * uniformTable.columnWidth(0) height: 20 opacity: (1.0 - designModeAnimated) Image { anchors.fill: parent anchors.margins: 2 fillMode: Image.PreserveAspectFit source: "images/icon_edit.png" mipmap: true } MouseArea { anchors.fill: parent onClicked: { uniformTable.currentIndex = index; // Pressing the edit button propertyEditDialog.propertyIndex = model.index; propertyEditDialog.initialType = model.type; propertyEditDialog.initialName = model.name; propertyEditDialog.initialDefaultValue = model.defaultValue; propertyEditDialog.initialMinValue = model.minValue; propertyEditDialog.initialMaxValue = model.maxValue; propertyEditDialog.initialDescription = model.description; propertyEditDialog.initialCustomValue = model.customValue; propertyEditDialog.initialUseCustomValue = model.useCustomValue; propertyEditDialog.initialEnableMipmap = model.enableMipmap; propertyEditDialog.initialExportImage = model.exportImage; propertyEditDialog.open(); } } } Label { id: typeLabel anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(1) text: uniformTable.typeStrings[model.type] color: mainView.foregroundColor2 font.pixelSize: 14 } Label { id: nameLabel anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(2) text: name color: mainView.foregroundColor2 elide: Text.ElideMiddle font.pixelSize: 14 MouseArea { id: nameLabelMouseArea anchors.fill: parent hoverEnabled: true } ToolTip { parent: nameLabel visible: nameLabelMouseArea.containsMouse && model.description !== "" text: truncateString(model.description, 100); } Rectangle { id: colorPickerButton anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right width: 40 height: 40 border.width: 1 border.color: foregroundColor2 color: visible ? model.value : "#000000" visible: (model.type === 6) MouseArea { anchors.fill: parent onClicked: { colorPickerDialog.propertyItem = controlsLoader.item; colorPickerDialog.selectedColor = model.value; colorPickerDialog.initialColor = model.value; colorPickerDialog.open(); } } } } Item { id: controlsItem width: uniformTable.columnWidth(3) + uniformTable.columnWidth(4) height: controlsLoader.item ? controlsLoader.item.height : 30 // This will update sliders, checkbox etc. to match current value function updateUIValues() { var controlsItem = controlsLoader.item; if (controlsItem) controlsItem.updateValue(model.value); } Loader { id: controlsLoader sourceComponent: { if (model.useCustomValue) return customControlsComponent; if (model.type === 0) return boolControlsComponent; if (model.type === 1 || model.type === 2) return floatControlsComponent; if (model.type === 3) return vec2ControlsComponent if (model.type === 4) return vec3ControlsComponent if (model.type === 5) return vec4ControlsComponent if (model.type === 6) return colorControlsComponent if (model.type === 7) return imageControlsComponent if (model.type === 8) return defineControlsComponent } } // Component with a text field and a slider component FloatSliderComponent : Item { property alias sliderValue: valueSlider.value property alias from: valueSlider.from property alias to: valueSlider.to function updatePropertyValue(newValue) { sliderValue = newValue; } width: controlsItem.width height: 30 CustomTextField { id: valueTextInput property string originalText: "" property bool editCancelled: false // Show int as int and others as float readonly property int valueFormat: (model.type === 1) ? 1 : 2 anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(3) height: parent.height - 2 text: uniformTable.convertValueToString(valueSlider.value, valueFormat) validator: RegularExpressionValidator { regularExpression: model.type === 1 ? /[-]?[0-9]+/ : /[-]?(\d{1,3})([.,]\d{1,3})?$/ } Keys.onEscapePressed: { editCancelled = true; focus = false; } onActiveFocusChanged: { if (activeFocus) { originalText = text; editCancelled = false; selectAll(); } } onEditingFinished: { if (editCancelled) { text = originalText; editCancelled = false; } else { // Update slider, which updates model.value sliderValue = valueTextInput.text; if (model.exportProperty === false) { // Non-exported properties (const variables) need shader baking effectManager.bakeShaders(true); } } valueTextInput.focus = false; } } Slider { id: valueSlider anchors.left: valueTextInput.right anchors.leftMargin: 20 anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(4) - anchors.leftMargin height: 30 stepSize: model.type === 1 ? 1.0 : 0.0 } } Component { id: boolControlsComponent Row { spacing: 20 function updateValue(newValue) { valueCheckBox.checked = newValue; model.value = newValue; } Label { id: valueTextLabel anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(3) text: uniformTable.convertValueToString(model.value, model.type) font.pixelSize: 14 color: mainView.foregroundColor2 wrapMode: Text.Wrap } CheckBox { id: valueCheckBox checked: model.value onToggled: model.value = checked; } } } Component { id: floatControlsComponent FloatSliderComponent { id: valueSlider1 function updateValue(newValue) { valueSlider1.sliderValue = newValue; model.value = newValue; } from: model.minValue to: model.maxValue onSliderValueChanged: { if (status === Component.Ready) model.value = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value); } } } Component { id: vec2ControlsComponent Column { function updateValue(newValue) { valueSlider1.sliderValue = newValue.x; valueSlider2.sliderValue = newValue.y; model.value = newValue; } FloatSliderComponent { id: valueSlider1 from: model.minValue.x to: model.maxValue.x onSliderValueChanged: { if (status === Component.Ready) model.value.x = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.x); } } FloatSliderComponent { id: valueSlider2 from: model.minValue.y to: model.maxValue.y onSliderValueChanged: { if (status === Component.Ready) model.value.y = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.y); } } } } Component { id: vec3ControlsComponent Column { function updateValue(newValue) { valueSlider1.sliderValue = newValue.x; valueSlider2.sliderValue = newValue.y; valueSlider3.sliderValue = newValue.z; model.value = newValue; } FloatSliderComponent { id: valueSlider1 from: model.minValue.x to: model.maxValue.x onSliderValueChanged: { if (status === Component.Ready) model.value.x = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.x); } } FloatSliderComponent { id: valueSlider2 from: model.minValue.y to: model.maxValue.y onSliderValueChanged: { if (status === Component.Ready) model.value.y = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.y); } } FloatSliderComponent { id: valueSlider3 from: model.minValue.z to: model.maxValue.z onSliderValueChanged: { if (status === Component.Ready) model.value.z = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.z); } } } } Component { id: vec4ControlsComponent Column { function updateValue(newValue) { valueSlider1.sliderValue = newValue.x; valueSlider2.sliderValue = newValue.y; valueSlider3.sliderValue = newValue.z; valueSlider4.sliderValue = newValue.w; model.value = newValue; } FloatSliderComponent { id: valueSlider1 from: model.minValue.x to: model.maxValue.x onSliderValueChanged: { if (status === Component.Ready) model.value.x = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.x); } } FloatSliderComponent { id: valueSlider2 from: model.minValue.y to: model.maxValue.y onSliderValueChanged: { if (status === Component.Ready) model.value.y = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.y); } } FloatSliderComponent { id: valueSlider3 from: model.minValue.z to: model.maxValue.z onSliderValueChanged: { if (status === Component.Ready) model.value.z = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.z); } } FloatSliderComponent { id: valueSlider4 from: model.minValue.w to: model.maxValue.w onSliderValueChanged: { if (status === Component.Ready) model.value.w = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.w); } } } } Component { id: colorControlsComponent Column { function updateValue(newColor) { valueSliderR.sliderValue = newColor.r; valueSliderG.sliderValue = newColor.g; valueSliderB.sliderValue = newColor.b; valueSliderA.sliderValue = newColor.a; model.value = newColor; } FloatSliderComponent { id: valueSliderR from: model.minValue.r to: model.maxValue.r onSliderValueChanged: { if (status === Component.Ready) model.value.r = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.r); } } FloatSliderComponent { id: valueSliderG from: model.minValue.g to: model.maxValue.g onSliderValueChanged: { if (status === Component.Ready) model.value.g = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.g); } } FloatSliderComponent { id: valueSliderB from: model.minValue.b to: model.maxValue.b onSliderValueChanged: { if (status === Component.Ready) model.value.b = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.b); } } FloatSliderComponent { id: valueSliderA from: model.minValue.a to: model.maxValue.a onSliderValueChanged: { if (status === Component.Ready) model.value.a = sliderValue; } Component.onCompleted: { updatePropertyValue(model.value.a); } } } } Component { id: imageControlsComponent Row { function updateValue(newValue) { model.value = newValue; } spacing: 20 Label { id: valueTextLabel anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(3) text: uniformTable.convertValueToString(model.value, model.type) font.pixelSize: 14 color: mainView.foregroundColor2 wrapMode: Text.Wrap } Item { anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(4) - 20 height: 40 Rectangle { anchors.fill: parent anchors.margins: 4 color: mainView.foregroundColor2 Image { anchors.fill: parent anchors.margins: 1 fillMode: Image.PreserveAspectCrop source: model.value } Text { anchors.centerIn: parent text: "CHANGE" font.bold: true font.pixelSize: 12 color: mainView.foregroundColor2 style: Text.Outline styleColor: "#000000" } MouseArea { anchors.fill: parent onClicked: { textureSourceDialog.modelIndex = index; if (model.value) { textureSourceDialog.currentFolder = effectManager.getDirectory(model.value); textureSourceDialog.selectedFile = effectManager.addFileToURL(model.value); } else { textureSourceDialog.currentFolder = effectManager.getDefaultImagesDirectory(); } textureSourceDialog.open(); } } } } } } Component { id: defineControlsComponent CustomTextField { id: valueTextInput property string originalText: "" property bool editCancelled: false function updateValue(newValue) { model.value = newValue; } anchors.verticalCenter: parent.verticalCenter width: controlsItem.width height: 28 text: model.value Keys.onEscapePressed: { editCancelled = true; focus = false; } onActiveFocusChanged: { if (activeFocus) { originalText = text; editCancelled = false; selectAll(); } } onEditingFinished: { if (editCancelled) { text = originalText; editCancelled = false; } else { model.value = valueTextInput.text; // Defines need shader baking effectManager.bakeShaders(true); } valueTextInput.focus = false; } } } Component { id: customControlsComponent Label { id: valueTextLabel anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter width: controlsItem.width height: 28 text: "[custom]" font.pixelSize: 14 color: mainView.foregroundColor2 wrapMode: Text.Wrap } } } Item { id: resetPropertyButton anchors.verticalCenter: parent.verticalCenter width: uniformTable.columnWidth(5) height: 20 enabled: model.value !== model.defaultValue Image { anchors.fill: parent anchors.margins: 2 fillMode: Image.PreserveAspectFit source: "images/icon_reset.png" opacity: enabled ? 1.0 : 0.5 mipmap: true } MouseArea { anchors.fill: parent onClicked: { uniformTable.currentIndex = index; effectManager.uniformModel.resetValue(index); controlsItem.updateUIValues(); } } } } Item { id: settingsOverlay anchors.fill: parent opacity: showSettingsAnimated visible: opacity > 0 Rectangle { anchors.fill: parent opacity: 0.6 color: mainView.backgroundColor1 MouseArea { anchors.fill: parent onPressed: { } } } Rectangle { property real sideMargin: 20 anchors.right: parent.right anchors.rightMargin: sideMargin - (1.0 - showSettingsAnimated) * (width + sideMargin) height: parent.height width: settingsArea.width color: mainView.backgroundColor2 Row { id: settingsArea height: parent.height Item { width: 10 height: 1 } CustomIconButton { anchors.verticalCenter: parent.verticalCenter height: 24 width: height icon: "images/icon_api.png" toggledIcon: "images/icon_api_on.png" toggleButton: true toggled: model.exportProperty // Samplers & Defines can't be made to non-exportable enabled: (model.type <= 6) description: "Export as effect API" onClicked: { model.exportProperty = !model.exportProperty; } } CustomIconButton { anchors.verticalCenter: parent.verticalCenter height: 24 width: height icon: "images/icon_arrow_up.png" enabled: model.canMoveUp description: "Move up" onClicked: { effectManager.uniformModel.moveIndex(model.index, -1); } } CustomIconButton { anchors.verticalCenter: parent.verticalCenter height: 24 width: height icon: "images/icon_arrow_down.png" enabled: model.canMoveDown description: "Move down" onClicked: { effectManager.uniformModel.moveIndex(model.index, 1); } } Item { width: 10 height: 1 } } } } } } } }