From 125cdcc3a03e1cb0ce7e8260618d4d9abe7d383e Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 12 May 2021 14:15:47 +0300 Subject: [PATCH] QmlDesigner: Block changing eulerRotation for 3D Node that has rotation QtQuick3D doesn't support both rotation and eulerRotation set on same node, so we delete existing quaternion rotation if user sets an euler rotation to the node. If node has quaternion rotation animation, we block any change to eulerRotation in the node. 3D editor also disables the rotation gizmo for blocked nodes. Task-number: QDS-4335 Change-Id: I4ce7b022c732a9547751f101548ecea948279db8 Reviewed-by: Thomas Hartmann --- .../qml/qmlpuppet/mockfiles/RotateGizmo.qml | 57 ++++++++++++----- .../qml/qmlpuppet/mockfiles/RotateRing.qml | 7 +++ .../qml2puppet/editor3d/generalhelper.cpp | 18 ++++++ .../qml2puppet/editor3d/generalhelper.h | 6 ++ .../qt5informationnodeinstanceserver.cpp | 59 +++++++++++++++++- .../qt5informationnodeinstanceserver.h | 4 ++ .../HelperWidgets/ExtendedFunctionLogic.qml | 7 +++ .../imports/HelperWidgets/SpinBox.qml | 11 ++++ .../propertyeditorcontextobject.cpp | 17 +++++ .../propertyeditorcontextobject.h | 2 + .../propertyeditor/propertyeditorvalue.cpp | 8 ++- .../propertyeditor/propertyeditorview.cpp | 51 ++++++++------- .../timelineeditor/timelinepropertyitem.cpp | 6 +- .../designercore/include/nodeinstanceview.h | 3 + .../designercore/include/qml3dnode.h | 8 +++ .../designercore/include/qmlobjectnode.h | 8 ++- .../instances/nodeinstanceview.cpp | 62 +++++++++++++++++-- .../designercore/model/qml3dnode.cpp | 62 +++++++++++++++++++ .../designercore/model/qmlobjectnode.cpp | 16 +++++ 19 files changed, 360 insertions(+), 52 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml index d691f3f0cf3..880ced6e9cd 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml @@ -40,16 +40,28 @@ Node { property real currentAngle property point currentMousePos property alias freeDraggerArea: mouseAreaFree + property bool blocked: false position: dragHelper.pivotScenePosition(targetNode) - onTargetNodeChanged: position = dragHelper.pivotScenePosition(targetNode) + onTargetNodeChanged: { + position = dragHelper.pivotScenePosition(targetNode); + blocked = _generalHelper.isRotationBlocked(targetNode); + } Connections { target: rotateGizmo.targetNode function onSceneTransformChanged() { - rotateGizmo.position = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode); + rotateGizmo.position = dragHelper.pivotScenePosition(targetNode); + } + } + + Connections { + target: _generalHelper + function onRotationBlocksChanged() + { + blocked = _generalHelper.isRotationBlocked(targetNode); } } @@ -82,11 +94,12 @@ Node { objectName: "Rotate Ring X" eulerRotation: Qt.vector3d(0, 90, 0) targetNode: rotateGizmo.targetNode - color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) - : Qt.rgba(1, 0, 0, 1) + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) : Qt.rgba(1, 0, 0, 1) priority: 40 view3D: rotateGizmo.view3D - active: rotateGizmo.visible + active: rotateGizmo.visible && !rotateGizmo.blocked dragHelper: rotateGizmo.dragHelper onRotateCommit: rotateGizmo.rotateCommit() @@ -100,13 +113,14 @@ Node { objectName: "Rotate Ring Y" eulerRotation: Qt.vector3d(90, 0, 0) targetNode: rotateGizmo.targetNode - color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) - : Qt.rgba(0, 0.6, 0, 1) + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) : Qt.rgba(0, 0.6, 0, 1) // Just a smidge smaller than higher priority rings so that it doesn't obscure them scale: Qt.vector3d(0.998, 0.998, 0.998) priority: 30 view3D: rotateGizmo.view3D - active: rotateGizmo.visible + active: rotateGizmo.visible && !rotateGizmo.blocked dragHelper: rotateGizmo.dragHelper onRotateCommit: rotateGizmo.rotateCommit() @@ -120,13 +134,14 @@ Node { objectName: "Rotate Ring Z" eulerRotation: Qt.vector3d(0, 0, 0) targetNode: rotateGizmo.targetNode - color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) - : Qt.rgba(0, 0, 1, 1) + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) : Qt.rgba(0, 0, 1, 1) // Just a smidge smaller than higher priority rings so that it doesn't obscure them scale: Qt.vector3d(0.996, 0.996, 0.996) priority: 20 view3D: rotateGizmo.view3D - active: rotateGizmo.visible + active: rotateGizmo.visible && !rotateGizmo.blocked dragHelper: rotateGizmo.dragHelper onRotateCommit: rotateGizmo.rotateCommit() @@ -154,12 +169,14 @@ Node { objectName: "cameraRing" rotation: rotateGizmo.view3D.camera.rotation targetNode: rotateGizmo.targetNode - color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) - : Qt.rgba(0.5, 0.5, 0.5, 1) + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) + : Qt.rgba(0.5, 0.5, 0.5, 1) scale: Qt.vector3d(1.1, 1.1, 1.1) priority: 10 view3D: rotateGizmo.view3D - active: rotateGizmo.visible + active: rotateGizmo.visible && !rotateGizmo.blocked dragHelper: rotateGizmo.dragHelper visible: !rotRingX.dragging && !rotRingY.dragging && !rotRingZ.dragging && !freeRotator.dragging @@ -176,7 +193,7 @@ Node { materials: DefaultMaterial { id: material diffuseColor: "black" - opacity: mouseAreaFree.hovering ? 0.15 : 0 + opacity: mouseAreaFree.hovering && !rotateGizmo.blocked ? 0.15 : 0 lighting: DefaultMaterial.NoLighting } scale: Qt.vector3d(0.15, 0.15, 0.15) @@ -200,6 +217,14 @@ Node { _startRotation = Qt.vector3d(rotateGizmo.targetNode.eulerRotation.x, rotateGizmo.targetNode.eulerRotation.y, rotateGizmo.targetNode.eulerRotation.z); + // Ensure we never set NaN values for rotation, even if target node originally has them + if (isNaN(_startRotation.x)) + _startRotation.x = 0; + if (isNaN(_startRotation.y)) + _startRotation.y = 0; + if (isNaN(_startRotation.z)) + _startRotation.z = 0; + dragging = true; } @@ -239,7 +264,7 @@ Node { height: 100 circlePickArea: Qt.point(25, 50) grabsMouse: rotateGizmo.targetNode - active: rotateGizmo.visible + active: rotateGizmo.visible && !rotateGizmo.blocked dragHelper: rotateGizmo.dragHelper onPressed: freeRotator.handlePressed(screenPos) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml index 00494a5d91b..19760b8ff9d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml @@ -90,6 +90,13 @@ Model { _startRotation = Qt.vector3d(targetNode.eulerRotation.x, targetNode.eulerRotation.y, targetNode.eulerRotation.z); + // Ensure we never set NaN values for rotation, even if target node originally has them + if (isNaN(_startRotation.x)) + _startRotation.x = 0; + if (isNaN(_startRotation.y)) + _startRotation.y = 0; + if (isNaN(_startRotation.z)) + _startRotation.z = 0; currentAngle = 0; currentMousePos = screenPos; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 133035de20c..f65e3ccc4f3 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -356,6 +356,24 @@ bool GeneralHelper::isMacOS() const #endif } +void GeneralHelper::addRotationBlocks(const QSet &nodes) +{ + m_rotationBlockedNodes.unite(nodes); + emit rotationBlocksChanged(); +} + +void GeneralHelper::removeRotationBlocks(const QSet &nodes) +{ + for (auto node : nodes) + m_rotationBlockedNodes.remove(node); + emit rotationBlocksChanged(); +} + +bool GeneralHelper::isRotationBlocked(QQuick3DNode *node) const +{ + return m_rotationBlockedNodes.contains(node); +} + bool GeneralHelper::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::DynamicPropertyChange) { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 6347520e694..212411eee4a 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -93,11 +93,16 @@ public: bool isMacOS() const; + void addRotationBlocks(const QSet &nodes); + void removeRotationBlocks(const QSet &nodes); + Q_INVOKABLE bool isRotationBlocked(QQuick3DNode *node) const; + signals: void overlayUpdateNeeded(); void toolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); void hiddenStateChanged(QQuick3DNode *node); void lockedStateChanged(QQuick3DNode *node); + void rotationBlocksChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) final; @@ -110,6 +115,7 @@ private: QHash m_toolStates; QHash m_toolStatesPending; QSet m_gizmoTargets; + QSet m_rotationBlockedNodes; }; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 1f3a662d3ec..2b2ec60da88 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -63,6 +63,7 @@ #include "inputeventcommand.h" #include "view3dactioncommand.h" #include "requestmodelnodepreviewimagecommand.h" +#include "changeauxiliarycommand.h" #include "dummycontextobject.h" #include "../editor3d/generalhelper.h" @@ -288,6 +289,57 @@ void Qt5InformationNodeInstanceServer::resolveImportSupport() #endif } +void Qt5InformationNodeInstanceServer::updateRotationBlocks(const QVector &valueChanges) +{ +#ifdef QUICK3D_MODULE + auto helper = qobject_cast(m_3dHelper); + if (helper) { + QSet blockedNodes; + QSet unblockedNodes; + const PropertyName propName = "rotBlocked@internal"; + for (const auto &container : valueChanges) { + if (container.name() == propName) { + ServerNodeInstance instance = instanceForId(container.instanceId()); + if (instance.isValid()) { + auto node = qobject_cast(instance.internalObject()); + if (node) { + if (container.value().toBool()) + blockedNodes.insert(node); + else + unblockedNodes.insert(node); + } + } + } + } + helper->addRotationBlocks(blockedNodes); + helper->removeRotationBlocks(unblockedNodes); + } +#else + Q_UNUSED(valueChanges) +#endif +} + +void Qt5InformationNodeInstanceServer::removeRotationBlocks(const QVector &instanceIds) +{ +#ifdef QUICK3D_MODULE + auto helper = qobject_cast(m_3dHelper); + if (helper) { + QSet unblockedNodes; + for (const auto &id : instanceIds) { + ServerNodeInstance instance = instanceForId(id); + if (instance.isValid()) { + auto node = qobject_cast(instance.internalObject()); + if (node) + unblockedNodes.insert(node); + } + } + helper->removeRotationBlocks(unblockedNodes); + } +#else + Q_UNUSED(instanceIds) +#endif +} + void Qt5InformationNodeInstanceServer::createEditView3D() { #ifdef QUICK3D_MODULE @@ -1492,8 +1544,10 @@ void Qt5InformationNodeInstanceServer::createScene(const CreateSceneCommand &com sendChildrenChangedCommand(instanceList); nodeInstanceClient()->componentCompleted(createComponentCompletedCommand(instanceList)); - if (isQuick3DMode()) + if (isQuick3DMode()) { setup3DEditView(instanceList, command.edit3dToolStates); + updateRotationBlocks(command.auxiliaryChanges); + } QObject::connect(&m_renderModelNodeImageViewTimer, &QTimer::timeout, this, &Qt5InformationNodeInstanceServer::doRenderModelNodeImageView); @@ -1656,6 +1710,8 @@ void Qt5InformationNodeInstanceServer::removeInstances(const RemoveInstancesComm { int nodeCount = m_3DSceneMap.size(); + removeRotationBlocks(command.instanceIds()); + Qt5NodeInstanceServer::removeInstances(command); if (nodeCount != m_3DSceneMap.size()) { @@ -1741,6 +1797,7 @@ void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const Reques void Qt5InformationNodeInstanceServer::changeAuxiliaryValues(const ChangeAuxiliaryCommand &command) { + updateRotationBlocks(command.auxiliaryChanges); Qt5NodeInstanceServer::changeAuxiliaryValues(command); render3DEditView(); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 55a07379b4e..6e882e87280 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -30,6 +30,8 @@ #include "valueschangedcommand.h" #include "changeselectioncommand.h" #include "requestmodelnodepreviewimagecommand.h" +#include "propertybindingcontainer.h" +#include "propertyabstractcontainer.h" #include #include @@ -132,6 +134,8 @@ private: void updateLockedAndHiddenStates(const QSet &instances); void handleInputEvents(); void resolveImportSupport(); + void updateRotationBlocks(const QVector &valueChanges); + void removeRotationBlocks(const QVector &instanceIds); void createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData); diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml index 57c39fcd68f..d257ccf0cca 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml @@ -105,6 +105,13 @@ Item { extendedFunctionButton.menuVisible = false } + Connections { + target: modelNodeBackend + onSelectionChanged: { + menu.close() + } + } + StudioControls.MenuItem { text: qsTr("Reset") onTriggered: { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml index 916bfc29d96..ca8f075fee4 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SpinBox.qml @@ -50,6 +50,17 @@ Item { transaction.end(); } + Component.onCompleted: { + spinBox.enabled = !isBlocked(backendValue.name); + } + + Connections { + target: modelNodeBackend + function onSelectionChanged() { + spinBox.enabled = !isBlocked(backendValue.name); + } + } + StudioControls.RealSpinBox { id: spinBox diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index a7161bbbe2f..09a2d2b0584 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -303,6 +303,9 @@ void PropertyEditorContextObject::insertKeyframe(const QString &propertyName) { QTC_ASSERT(m_model && m_model->rewriterView(), return); + if (isBlocked(propertyName)) + return; + /* Ideally we should not missuse the rewriterView * If we add more code here we have to forward the property editor view */ RewriterView *rewriterView = m_model->rewriterView(); @@ -557,6 +560,20 @@ QStringList PropertyEditorContextObject::allStatesForId(const QString &id) return {}; } +bool PropertyEditorContextObject::isBlocked(const QString &propName) const +{ + if (m_model && m_model->rewriterView()) { + const QList nodes = m_model->rewriterView()->selectedModelNodes(); + QScopedPointer objNode; + for (const auto &node : nodes) { + objNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(node)); + if (objNode->isBlocked(propName.toUtf8())) + return true; + } + } + return false; +} + void EasingCurveEditor::registerDeclarativeType() { qmlRegisterType("HelperWidgets", 2, 0, "EasingCurveEditor"); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h index 04d5d074bb8..8b7947bd527 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h @@ -99,6 +99,8 @@ public: Q_INVOKABLE QStringList allStatesForId(const QString &id); + Q_INVOKABLE bool isBlocked(const QString &propName) const; + int majorVersion() const; int majorQtQuickVersion() const; int minorQtQuickVersion() const; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index ec78ed97afd..684a1746e03 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -39,6 +39,7 @@ #include #include +#include //using namespace QmlDesigner; @@ -610,14 +611,15 @@ void PropertyEditorNodeWrapper::changeValue(const QString &propertyName) if (name.isNull()) return; if (m_modelNode.isValid()) { - QmlDesigner::QmlObjectNode qmlObjectNode(m_modelNode); + QScopedPointer qmlObjectNode{ + QmlDesigner::QmlObjectNode::getQmlObjectNodeOfCorrectType(m_modelNode)}; auto valueObject = qvariant_cast(m_valuesPropertyMap.value(QString::fromLatin1(name))); if (valueObject->value().isValid()) - qmlObjectNode.setVariantProperty(name, valueObject->value()); + qmlObjectNode->setVariantProperty(name, valueObject->value()); else - qmlObjectNode.removeProperty(name); + qmlObjectNode->removeProperty(name); } } diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 028daee4c6c..abdfc2ebd4b 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -56,6 +56,7 @@ #include #include #include +#include enum { debug = false @@ -251,7 +252,7 @@ void PropertyEditorView::changeExpression(const QString &propertyName) PropertyName underscoreName(name); underscoreName.replace('.', '_'); - QmlObjectNode qmlObjectNode(m_selectedNode); + QScopedPointer qmlObjectNode {QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode)}; PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName)); if (!value) { @@ -259,33 +260,33 @@ void PropertyEditorView::changeExpression(const QString &propertyName) return; } - if (qmlObjectNode.modelNode().metaInfo().isValid() && qmlObjectNode.modelNode().metaInfo().hasProperty(name)) { - if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "QColor") { + if (qmlObjectNode->modelNode().metaInfo().isValid() && qmlObjectNode->modelNode().metaInfo().hasProperty(name)) { + if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "QColor") { if (QColor(value->expression().remove('"')).isValid()) { - qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); + qmlObjectNode->setVariantProperty(name, QColor(value->expression().remove('"'))); return; } - } else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "bool") { + } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "bool") { if (value->expression().compare(QLatin1String("false"), Qt::CaseInsensitive) == 0 || value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) { if (value->expression().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) - qmlObjectNode.setVariantProperty(name, true); + qmlObjectNode->setVariantProperty(name, true); else - qmlObjectNode.setVariantProperty(name, false); + qmlObjectNode->setVariantProperty(name, false); return; } - } else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "int") { + } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "int") { bool ok; int intValue = value->expression().toInt(&ok); if (ok) { - qmlObjectNode.setVariantProperty(name, intValue); + qmlObjectNode->setVariantProperty(name, intValue); return; } - } else if (qmlObjectNode.modelNode().metaInfo().propertyTypeName(name) == "qreal") { + } else if (qmlObjectNode->modelNode().metaInfo().propertyTypeName(name) == "qreal") { bool ok; qreal realValue = value->expression().toDouble(&ok); if (ok) { - qmlObjectNode.setVariantProperty(name, realValue); + qmlObjectNode->setVariantProperty(name, realValue); return; } } @@ -296,8 +297,8 @@ void PropertyEditorView::changeExpression(const QString &propertyName) return; } - if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name)) - qmlObjectNode.setBindingProperty(name, value->expression()); + if (qmlObjectNode->expression(name) != value->expression() || !qmlObjectNode->propertyAffectedByCurrentState(name)) + qmlObjectNode->setBindingProperty(name, value->expression()); }); /* end of transaction */ } @@ -465,11 +466,13 @@ void PropertyEditorView::setupQmlBackend() m_stackedWidget->addWidget(currentQmlBackend->widget()); m_qmlBackendHash.insert(qmlFile.toString(), currentQmlBackend); - QmlObjectNode qmlObjectNode; + QScopedPointer qmlObjectNode; if (m_selectedNode.isValid()) { - qmlObjectNode = QmlObjectNode(m_selectedNode); - Q_ASSERT(qmlObjectNode.isValid()); - currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this); + qmlObjectNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode)); + Q_ASSERT(qmlObjectNode->isValid()); + currentQmlBackend->setup(*qmlObjectNode, currentStateName, qmlSpecificsFile, this); + } else { + qmlObjectNode.reset(new QmlObjectNode); } currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false)); if (specificQmlData.isEmpty()) @@ -480,14 +483,16 @@ void PropertyEditorView::setupQmlBackend() currentQmlBackend->setSource(qmlFile); currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(true)); } else { - QmlObjectNode qmlObjectNode; + QScopedPointer qmlObjectNode; if (m_selectedNode.isValid()) - qmlObjectNode = QmlObjectNode(m_selectedNode); + qmlObjectNode.reset(QmlObjectNode::getQmlObjectNodeOfCorrectType(m_selectedNode)); + else + qmlObjectNode.reset(new QmlObjectNode); currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false)); if (specificQmlData.isEmpty()) currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); - currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this); + currentQmlBackend->setup(*qmlObjectNode, currentStateName, qmlSpecificsFile, this); currentQmlBackend->contextObject()->setGlobalBaseUrl(qmlFile); currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData); } @@ -509,8 +514,10 @@ void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyN RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::commitVariantValueToMode"); for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) { - if (QmlObjectNode::isValidQmlObjectNode(node)) - QmlObjectNode(node).setVariantProperty(propertyName, value); + if (QmlObjectNode::isValidQmlObjectNode(node)) { + QScopedPointer{QmlObjectNode::getQmlObjectNodeOfCorrectType(node)} + ->setVariantProperty(propertyName, value); + } } transaction.commit(); } diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp index fa1d258c28a..77f0bbf34cb 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include @@ -354,8 +355,9 @@ void TimelinePropertyItem::changePropertyValue(const QVariant &value) QTimer::singleShot(0, deferredFunc); } else { - QmlObjectNode objectNode(m_frames.target()); - objectNode.setVariantProperty(m_frames.propertyName(), value); + QScopedPointer objectNode { + QmlObjectNode::getQmlObjectNodeOfCorrectType(m_frames.target())}; + objectNode->setVariantProperty(m_frames.propertyName(), value); } } diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index a76eefcc43b..f237bcf0d41 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -224,6 +224,8 @@ private: // functions void updateWatcher(const QString &path); + void updateRotationBlocks(); + private: QHash m_imageDataMap; @@ -251,6 +253,7 @@ private: QTimer m_resetTimer; QTimer m_updateWatcherTimer; QSet m_pendingUpdateDirs; + QTimer m_rotBlockTimer; }; } // namespace ProxyNodeInstanceView diff --git a/src/plugins/qmldesigner/designercore/include/qml3dnode.h b/src/plugins/qmldesigner/designercore/include/qml3dnode.h index d487666ae1a..e2e76889790 100644 --- a/src/plugins/qmldesigner/designercore/include/qml3dnode.h +++ b/src/plugins/qmldesigner/designercore/include/qml3dnode.h @@ -49,6 +49,14 @@ public: Qml3DNode(const ModelNode &modelNode) : QmlVisualNode(modelNode) {} bool isValid() const override; static bool isValidQml3DNode(const ModelNode &modelNode); + + // From QmlObjectNode + void setVariantProperty(const PropertyName &name, const QVariant &value) override; + void setBindingProperty(const PropertyName &name, const QString &expression) override; + bool isBlocked(const PropertyName &propName) const override; + +private: + void handleEulerRotationSet(); }; QMLDESIGNERCORE_EXPORT uint qHash(const Qml3DNode &node); diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 23e99e1db25..43f975e5cf5 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -69,8 +69,8 @@ public: QmlModelState currentState() const; QmlTimeline currentTimeline() const; - void setVariantProperty(const PropertyName &name, const QVariant &value); - void setBindingProperty(const PropertyName &name, const QString &expression); + virtual void setVariantProperty(const PropertyName &name, const QVariant &value); + virtual void setBindingProperty(const PropertyName &name, const QString &expression); NodeAbstractProperty nodeAbstractProperty(const PropertyName &name) const; NodeAbstractProperty defaultNodeAbstractProperty() const; NodeProperty nodeProperty(const PropertyName &name) const; @@ -123,6 +123,10 @@ public: QStringList allStateNames() const; + static QmlObjectNode *getQmlObjectNodeOfCorrectType(const ModelNode &modelNode); + + virtual bool isBlocked(const PropertyName &propName) const; + protected: NodeInstance nodeInstance() const; QmlObjectNode nodeForInstance(const NodeInstance &instance) const; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index ef76b70079d..74d2770c88f 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -54,6 +54,7 @@ #include "nodeproperty.h" #include "pixmapchangedcommand.h" #include "puppettocreatorcommand.h" +#include "qml3dnode.h" #include "qmlchangeset.h" #include "qmldesignerconstants.h" #include "qmlstate.h" @@ -103,6 +104,7 @@ #include #include #include +#include enum { debug = false @@ -176,6 +178,10 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] { m_resetTimer.start(); }); + + m_rotBlockTimer.setSingleShot(true); + m_rotBlockTimer.setInterval(0); + QObject::connect(&m_rotBlockTimer, &QTimer::timeout, this, &NodeInstanceView::updateRotationBlocks); } @@ -594,11 +600,12 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, const QVariant &value) { QTC_ASSERT(m_nodeInstanceServer, return); - if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible" || name == "locked") + const bool forceAuxChange = name == "invisible" || name == "locked" || name == "rotBlocked@internal"; + if (((node.isRootNode() && (name == "width" || name == "height")) || forceAuxChange) || name.endsWith(PropertyName("@NodeInstance"))) { if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); - if (value.isValid() || name == "invisible" || name == "locked") { + if (value.isValid() || forceAuxChange) { PropertyValueContainer container{instance.instanceId(), name, value, TypeName()}; m_nodeInstanceServer->changeAuxiliaryValues({{container}}); } else { @@ -1341,10 +1348,10 @@ void NodeInstanceView::valuesModified(const ValuesModifiedCommand &command) if (hasInstanceForId(container.instanceId())) { NodeInstance instance = instanceForId(container.instanceId()); if (instance.isValid()) { - // QmlVisualNode is needed so timeline and state are updated - QmlVisualNode node = instance.modelNode(); - if (node.modelValue(container.name()) != container.value()) - node.setVariantProperty(container.name(), container.value()); + QScopedPointer node { + QmlObjectNode::getQmlObjectNodeOfCorrectType(instance.modelNode())}; + if (node->modelValue(container.name()) != container.value()) + node->setVariantProperty(container.name(), container.value()); } } } @@ -1593,6 +1600,7 @@ void NodeInstanceView::selectedNodesChanged(const QList &selectedNode const QList & /*lastSelectedNodeList*/) { m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList)); + m_rotBlockTimer.start(); } void NodeInstanceView::sendInputEvent(QInputEvent *e) const @@ -1850,4 +1858,46 @@ void NodeInstanceView::updateWatcher(const QString &path) } } +void NodeInstanceView::updateRotationBlocks() +{ + QList qml3DNodes; + QSet rotationKeyframeTargets; + bool groupsResolved = false; + const PropertyName targetPropName {"target"}; + const PropertyName propertyPropName {"property"}; + const PropertyName rotationPropName {"rotation"}; + const QList selectedNodes = selectedModelNodes(); + for (const auto &node : selectedNodes) { + if (Qml3DNode::isValidQml3DNode(node)) { + if (!groupsResolved) { + const QList keyframeGroups = allModelNodesOfType("KeyframeGroup"); + for (const auto &kfgNode : keyframeGroups) { + if (kfgNode.isValid()) { + VariantProperty varProp = kfgNode.variantProperty(propertyPropName); + if (varProp.isValid() && varProp.value().value() == rotationPropName) { + BindingProperty bindProp = kfgNode.bindingProperty(targetPropName); + if (bindProp.isValid()) { + ModelNode targetNode = bindProp.resolveToModelNode(); + if (Qml3DNode::isValidQml3DNode(targetNode)) + rotationKeyframeTargets.insert(targetNode); + } + } + } + } + groupsResolved = true; + } + qml3DNodes.append(node); + } + } + if (!qml3DNodes.isEmpty()) { + const PropertyName auxDataProp {"rotBlocked@internal"}; + for (const auto &node : qAsConst(qml3DNodes)) { + if (rotationKeyframeTargets.contains(node)) + node.setAuxiliaryData(auxDataProp, true); + else + node.setAuxiliaryData(auxDataProp, false); + } + } +} + } diff --git a/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp b/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp index d05048fe31a..cb9e2b8bb16 100644 --- a/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qml3dnode.cpp @@ -58,6 +58,68 @@ bool Qml3DNode::isValidQml3DNode(const ModelNode &modelNode) && (modelNode.metaInfo().isSubclassOf("QtQuick3D.Node")); } +void Qml3DNode::setVariantProperty(const PropertyName &name, const QVariant &value) +{ + if (isBlocked(name)) + return; + + if (name.startsWith("eulerRotation")) + handleEulerRotationSet(); + + QmlObjectNode::setVariantProperty(name, value); +} + +void Qml3DNode::setBindingProperty(const PropertyName &name, const QString &expression) +{ + if (isBlocked(name)) + return; + + if (name.startsWith("eulerRotation")) + handleEulerRotationSet(); + + QmlObjectNode::setBindingProperty(name, expression); +} + +bool Qml3DNode::isBlocked(const PropertyName &propName) const +{ + if (modelNode().isValid() && propName.startsWith("eulerRotation")) + return modelNode().auxiliaryData("rotBlocked@internal").toBool(); + + return false; +} + +void Qml3DNode::handleEulerRotationSet() +{ + ModelNode node = modelNode(); + // The rotation property is quaternion, which is difficult to deal with for users, so QDS + // only supports eulerRotation. Since having both on the same object isn't supported, + // remove the rotation property if eulerRotation is set. + if (node.isValid() && node.isSubclassOf("QtQuick3D.Node")) { + if (!isInBaseState()) { + QmlPropertyChanges changeSet(currentState().propertyChanges(node)); + Q_ASSERT(changeSet.isValid()); + node = changeSet.modelNode(); + } + + if (node.hasProperty("rotation")) { + // We need to reset the eulerRotation values as removing rotation will zero them, + // which is not desirable if the change only targets one of the xyz subproperties. + // Get the eulerRotation value from instance, as they are not available in model. + QVector3D eulerVec = instanceValue("eulerRotation").value(); + node.removeProperty("rotation"); + if (qIsNaN(eulerVec.x())) + eulerVec.setX(0.); + if (qIsNaN(eulerVec.y())) + eulerVec.setY(0.); + if (qIsNaN(eulerVec.z())) + eulerVec.setZ(0.); + node.variantProperty("eulerRotation.x").setValue(eulerVec.x()); + node.variantProperty("eulerRotation.y").setValue(eulerVec.y()); + node.variantProperty("eulerRotation.z").setValue(eulerVec.z()); + } + } +} + QList toModelNodeList(const QList &qmlVisualNodeList) { QList modelNodeList; diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index cebe171295e..6c9acc4b6eb 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -28,6 +28,7 @@ #include "qmlstate.h" #include "qmltimelinekeyframegroup.h" #include "qmlvisualnode.h" +#include "qml3dnode.h" #include "variantproperty.h" #include "nodeproperty.h" #include @@ -738,4 +739,19 @@ QStringList QmlObjectNode::allStateNames() const return nodeInstance().allStateNames(); } +QmlObjectNode *QmlObjectNode::getQmlObjectNodeOfCorrectType(const ModelNode &modelNode) +{ + // Create QmlObjectNode of correct type for the modelNode + // Note: Currently we are only interested in differentiating 3D nodes, so no check for + // visual nodes is done for efficiency reasons + if (modelNode.isValid() && modelNode.isSubclassOf("QtQuick3D.Node")) + return new Qml3DNode(modelNode); + return new QmlObjectNode(modelNode); +} + +bool QmlObjectNode::isBlocked(const PropertyName &propName) const +{ + return false; +} + } //QmlDesigner