Revision control
++ // 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
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ }