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