diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml index 39176ca82a9..9b23842fe80 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -291,53 +291,64 @@ Section { property int vecSize: 0 property var proxyValues: [] property var spinBoxes: [boxX, boxY, boxZ, boxW] + property bool block: false signal remove - onVecSizeChanged: updateProxyValues() + onVecSizeChanged: layoutVector.updateProxyValues() - spacing: StudioTheme.Values.sectionRowSpacing / 2 + spacing: StudioTheme.Values.sectionRowSpacing function isValidValue(v) { return !(v === undefined || isNaN(v)) } - function updateExpression() { + function updateExpressionFromExpression() { + if (layoutVector.block) + return + + layoutVector.backendValue.expression = layoutVector.proxyValues[0].expression + // Only the first proxy value has an expression editor enabled + } + + function updateExpressionFromValue() { + if (layoutVector.block) + return + for (let i = 0; i < vecSize; ++i) { - if (!isValidValue(proxyValues[i].value)) + if (!layoutVector.isValidValue(layoutVector.proxyValues[i].value)) return } - let expStr = "Qt.vector" + vecSize + "d("+proxyValues[0].value - for (let j=1; j < vecSize; ++j) - expStr += ", " + proxyValues[j].value + let expStr = "Qt.vector" + layoutVector.vecSize + "d(" + layoutVector.proxyValues[0].value + for (let j=1; j < layoutVector.vecSize; ++j) + expStr += ", " + layoutVector.proxyValues[j].value expStr += ")" layoutVector.backendValue.expression = expStr } function updateProxyValues() { - if (!backendValue) + if (!layoutVector.backendValue) return; - const startIndex = backendValue.expression.indexOf('(') - const endIndex = backendValue.expression.indexOf(')') - if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) - return - const numberStr = backendValue.expression.slice(startIndex + 1, endIndex) - const numbers = numberStr.split(",") - if (!Array.isArray(numbers) || numbers.length !== vecSize) - return + let vals = layoutVector.backendValue.getExpressionAsVector() - let vals = [] - for (let i = 0; i < vecSize; ++i) { - vals[i] = parseFloat(numbers[i]) - if (!isValidValue(vals[i])) - return + layoutVector.block = true + + if (layoutVector.vecSize === vals.length) { + for (let j = 0; j < layoutVector.vecSize; ++j) { + layoutVector.proxyValues[j].setForceBound(false) + layoutVector.proxyValues[j].value = vals[j] + } + } else { + for (let j = 0; j < layoutVector.vecSize; ++j) { + layoutVector.proxyValues[j].setForceBound(true) // Required since the backendValue is just proxied + layoutVector.proxyValues[j].expression = layoutVector.backendValue.expression + } } - for (let j = 0; j < vecSize; ++j) - proxyValues[j].value = vals[j] + layoutVector.block = false } SecondColumnLayout { @@ -357,15 +368,18 @@ Section { tooltip: "X" } - Spacer { implicitWidth: StudioTheme.Values.controlGap } + Spacer { + implicitWidth: StudioTheme.Values.controlGap + + StudioTheme.Values.actionIndicatorWidth + } SpinBox { id: boxY + actionIndicatorVisible: false minimumValue: -9999999 maximumValue: 9999999 decimals: 2 implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } @@ -386,14 +400,16 @@ Section { } SecondColumnLayout { - visible: vecSize > 2 + visible: layoutVector.vecSize > 2 + Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth } + SpinBox { id: boxZ + actionIndicatorVisible: false minimumValue: -9999999 maximumValue: 9999999 decimals: 2 implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } @@ -401,19 +417,22 @@ Section { ControlLabel { text: "Z" tooltip: "Z" - visible: vecSize > 2 + visible: layoutVector.vecSize > 2 } - Spacer { implicitWidth: StudioTheme.Values.controlGap } + Spacer { + implicitWidth: StudioTheme.Values.controlGap + + StudioTheme.Values.actionIndicatorWidth + } SpinBox { id: boxW + actionIndicatorVisible: false minimumValue: -9999999 maximumValue: 9999999 decimals: 2 implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - visible: vecSize > 3 + visible: layoutVector.vecSize > 3 } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } @@ -421,7 +440,7 @@ Section { ControlLabel { text: "W" tooltip: "W" - visible: vecSize > 3 + visible: layoutVector.vecSize > 3 } Spacer { implicitWidth: StudioTheme.Values.controlGap } @@ -430,7 +449,7 @@ Section { height: 10 implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth - visible: vecSize === 2 // Placeholder for last spinbox + visible: layoutVector.vecSize === 2 // Placeholder for last spinbox } Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap } @@ -486,9 +505,12 @@ Section { model: root.propertiesModel row: index } + PropertyLabel { text: propertyName tooltip: propertyType + Layout.alignment: Qt.AlignTop + Layout.topMargin: 6 } Loader { @@ -540,7 +562,8 @@ Section { for (let i = 0; i < vecSize; ++i) { var newProxyValue = propertyRow.createProxyBackendValue() loader.item.proxyValues.push(newProxyValue) - newProxyValue.valueChangedQml.connect(loader.item.updateExpression) + newProxyValue.valueChangedQml.connect(loader.item.updateExpressionFromValue) + newProxyValue.expressionChangedQml.connect(loader.item.updateExpressionFromExpression) loader.item.spinBoxes[i].backendValue = newProxyValue } propertyRow.backendValue.expressionChanged.connect(loader.item.updateProxyValues) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index f125c459c51..04cbb78f356 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -22,6 +22,9 @@ Item { property alias realDragRange: spinBox.realDragRange property alias pixelsPerUnit: spinBox.pixelsPerUnit + property alias actionIndicatorEnabled: spinBox.actionIndicator.enabled + property alias actionIndicatorVisible: spinBox.actionIndicatorVisible + width: 96 implicitHeight: spinBox.height diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 663ebafb659..c1d91f907a9 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -153,7 +153,8 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression) if (m_expression != expression) { setExpression(expression); m_value.clear(); - emit expressionChanged(nameAsQString()); // Note that we set the name in this case + emit expressionChanged(nameAsQString()); + emit expressionChangedQml();// Note that we set the name in this case } } @@ -180,7 +181,7 @@ bool PropertyEditorValue::isInSubState() const bool PropertyEditorValue::isBound() const { const QmlObjectNode objectNode(modelNode()); - return objectNode.isValid() && objectNode.hasBindingProperty(name()); + return m_forceBound || (objectNode.isValid() && objectNode.hasBindingProperty(name())); } bool PropertyEditorValue::isInModel() const @@ -334,6 +335,7 @@ void PropertyEditorValue::resetValue() m_expression = QString(); emit valueChanged(nameAsQString(), QVariant()); emit expressionChanged({}); + emit expressionChangedQml(); } } @@ -425,6 +427,34 @@ QStringList PropertyEditorValue::getExpressionAsList() const return generateStringList(expression()); } +QVector PropertyEditorValue::getExpressionAsVector() const +{ + const QRegularExpression rx( + QRegularExpression::anchoredPattern("Qt.vector(2|3|4)d\\((.*?)\\)")); + const QRegularExpressionMatch match = rx.match(expression()); + if (!match.hasMatch()) + return {}; + + const QStringList floats = match.captured(2).split(',', Qt::SkipEmptyParts); + + bool ok; + + const int num = match.captured(1).toInt(); + + if (num != floats.count()) + return {}; + + QVector ret; + for (const QString &number : floats) { + ret.append(number.toDouble(&ok)); + + if (!ok) + return {}; + } + + return ret; +} + bool PropertyEditorValue::idListAdd(const QString &value) { const QmlObjectNode objectNode(modelNode()); @@ -507,6 +537,15 @@ void PropertyEditorValue::openMaterialEditor(int idx) m_modelNode.view()->emitCustomNotification("select_material", {}, {idx}); } +void PropertyEditorValue::setForceBound(bool b) +{ + if (m_forceBound == b) + return; + m_forceBound = b; + + emit isBoundChanged(); +} + QStringList PropertyEditorValue::generateStringList(const QString &string) const { QString copy = string; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index 59236c4fe21..f621c9a5008 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -126,12 +126,15 @@ public: bool isIdList() const; Q_INVOKABLE QStringList getExpressionAsList() const; + Q_INVOKABLE QVector getExpressionAsVector() const; Q_INVOKABLE bool idListAdd(const QString &value); Q_INVOKABLE bool idListRemove(int idx); Q_INVOKABLE bool idListReplace(int idx, const QString &value); Q_INVOKABLE void commitDrop(const QString &dropData); Q_INVOKABLE void openMaterialEditor(int idx); + Q_INVOKABLE void setForceBound(bool b); + public slots: void resetValue(); void setEnumeration(const QString &scope, const QString &name); @@ -143,6 +146,8 @@ signals: void expressionChanged(const QString &name); // HACK - We use the same notifer for the backend and frontend. // If name is empty the signal is used for QML. + void expressionChangedQml(); + void exportPropertyAsAliasRequested(const QString &name); void removeAliasExportRequested(const QString &name); @@ -168,6 +173,7 @@ private: bool m_hasActiveDrag = false; bool m_isValid = false; // if the property value belongs to a non-existing complexProperty it is invalid PropertyEditorNodeWrapper *m_complexNode; + bool m_forceBound = false; }; } // namespace QmlDesigner