diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml index 904a062a623..cb9ece58b2d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml @@ -329,6 +329,15 @@ Item { function handleObjectClicked(object, multi) { + if (object instanceof View3D) { + // View3D can be the resolved pick target in case the 3D editor is showing content + // of a component that has View3D as root. In that case locking is resolved on C++ side + // and we ignore multiselection. + selectObjects([]); + selectionChanged([object]); + return; + } + var clickedObject; // Click on locked object is treated same as click on empty space @@ -740,7 +749,7 @@ Item { handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), mouse.modifiers & Qt.ControlModifier); - if (pickResult.objectHit) { + if (pickResult.objectHit && pickResult.objectHit instanceof Node) { if (transformMode === EditView3D.TransformMode.Move) freeDraggerArea = moveGizmo.freeDraggerArea; else if (transformMode === EditView3D.TransformMode.Rotate) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index 25297f8fc67..3431a1ce3c0 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -329,6 +329,15 @@ Item { function handleObjectClicked(object, multi) { + if (object instanceof View3D) { + // View3D can be the resolved pick target in case the 3D editor is showing content + // of a component that has View3D as root. In that case locking is resolved on C++ side + // and we ignore multiselection. + selectObjects([]); + selectionChanged([object]); + return; + } + var clickedObject; // Click on locked object is treated same as click on empty space @@ -892,7 +901,7 @@ Item { handleObjectClicked(resolvedResult, mouse.modifiers & Qt.ControlModifier); - if (pickResult.objectHit) { + if (pickResult.objectHit && pickResult.objectHit instanceof Node) { if (transformMode === EditView3D.TransformMode.Move) freeDraggerArea = moveGizmo.freeDraggerArea; else if (transformMode === EditView3D.TransformMode.Rotate) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 7e843012be6..dd21622b5dc 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -438,15 +438,15 @@ QQuick3DPickResult GeneralHelper::pickViewAt(QQuick3DViewport *view, float posX, return QQuick3DPickResult(); } -QQuick3DNode *GeneralHelper::resolvePick(QQuick3DNode *pickNode) +QObject *GeneralHelper::resolvePick(QQuick3DNode *pickNode) { if (pickNode) { - // Check if the picked node actually specifies another node as the pick target + // Check if the picked node actually specifies another object as the pick target QVariant componentVar = pickNode->property("_pickTarget"); if (componentVar.isValid()) { - auto componentNode = componentVar.value(); - if (componentNode) - return componentNode; + auto componentObj = componentVar.value(); + if (componentObj) + return componentObj; } } return pickNode; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 98974cfda9f..5bb1fa1662f 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -85,7 +85,7 @@ public: Q_INVOKABLE void delayedPropertySet(QObject *obj, int delay, const QString &property, const QVariant& value); Q_INVOKABLE QQuick3DPickResult pickViewAt(QQuick3DViewport *view, float posX, float posY); - Q_INVOKABLE QQuick3DNode *resolvePick(QQuick3DNode *pickNode); + Q_INVOKABLE QObject *resolvePick(QQuick3DNode *pickNode); Q_INVOKABLE bool isLocked(QQuick3DNode *node) const; Q_INVOKABLE bool isHidden(QQuick3DNode *node) const; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index 8400c3d4474..41c67f70b68 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -1561,6 +1561,10 @@ void NodeInstanceServer::handleInstanceHidden(const ServerNodeInstance &/*instan { } +void NodeInstanceServer::handlePickTarget(const ServerNodeInstance &/*instance*/) +{ +} + void NodeInstanceServer::setupState(qint32 stateInstanceId) { if (hasInstanceForId(stateInstanceId)) { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h index ba51f5e5888..42f345ee4e5 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h @@ -226,6 +226,7 @@ public: virtual void handleInstanceLocked(const ServerNodeInstance &instance, bool enable, bool checkAncestors); virtual void handleInstanceHidden(const ServerNodeInstance &instance, bool enable, bool checkAncestors); + virtual void handlePickTarget(const ServerNodeInstance &instance); virtual QImage grabWindow() = 0; virtual QImage grabItem(QQuickItem *item) = 0; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index c3fc4d4e076..975a6ded6b9 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -553,7 +553,18 @@ void Qt5InformationNodeInstanceServer::handleSelectionChanged(const QVariant &ob auto obj = object.value(); if (obj) { ServerNodeInstance instance = instanceForObject(obj); - instanceList << instance; + // If instance is a View3D, make sure it is not locked + bool locked = false; + if (instance.isSubclassOf("QQuick3DViewport")) { + locked = instance.internalInstance()->isLockedInEditor(); + auto parentInst = instance.parent(); + while (!locked && parentInst.isValid()) { + locked = parentInst.internalInstance()->isLockedInEditor(); + parentInst = parentInst.parent(); + } + } + if (!locked) + instanceList << instance; #ifdef QUICK3D_PARTICLES_MODULE if (!skipSystemDeselect) { auto particleSystem = parentParticleSystem(instance.internalObject()); @@ -1448,8 +1459,9 @@ void Qt5InformationNodeInstanceServer::handleSelectionChangeTimeout() void Qt5InformationNodeInstanceServer::handleDynamicAddObjectTimeout() { -#ifdef QUICK3D_MODULE for (auto obj : std::as_const(m_dynamicObjectConstructors)) { +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 1) +#ifdef QUICK3D_MODULE auto handleHiding = [this](QQuick3DNode *node) -> bool { if (node && hasInstanceForObject(node)) { ServerNodeInstance instance = instanceForObject(node); @@ -1464,8 +1476,22 @@ void Qt5InformationNodeInstanceServer::handleDynamicAddObjectTimeout() if (auto pickTarget = obj->property("_pickTarget").value()) handleHiding(pickTarget); } - } #endif +#else + auto handlePicking = [this](QObject *object) -> bool { + if (object && hasInstanceForObject(object)) { + ServerNodeInstance instance = instanceForObject(object); + handlePickTarget(instance); + return true; + } + return false; + }; + if (!handlePicking(obj)) { + if (auto pickTarget = obj->property("_pickTarget").value()) + handlePicking(pickTarget); + } +#endif + } m_dynamicObjectConstructors.clear(); } @@ -2328,9 +2354,9 @@ void Qt5InformationNodeInstanceServer::handleInstanceLocked(const ServerNodeInst } } #else - Q_UNUSED(instance); - Q_UNUSED(enable); - Q_UNUSED(checkAncestors); + Q_UNUSED(instance) + Q_UNUSED(enable) + Q_UNUSED(checkAncestors) #endif } @@ -2384,6 +2410,7 @@ void Qt5InformationNodeInstanceServer::handleInstanceHidden(const ServerNodeInst // Don't override explicit hide in children handleInstanceHidden(quick3dInstance, edit3dHidden || isInstanceHidden, false); } else { +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 1) // Children of components do not have instances, but will still need to be pickable std::function checkChildren; checkChildren = [&](QQuick3DNode *checkNode) { @@ -2398,9 +2425,7 @@ void Qt5InformationNodeInstanceServer::handleInstanceHidden(const ServerNodeInst value = QVariant::fromValue(node); // Specify the actual pick target with dynamic property checkModel->setProperty("_pickTarget", value); -#if QT_VERSION < QT_VERSION_CHECK(6, 2, 1) checkModel->setPickable(!edit3dHidden); -#endif } else { auto checkRepeater = qobject_cast(checkNode); auto checkLoader = qobject_cast(checkNode); @@ -2432,13 +2457,102 @@ void Qt5InformationNodeInstanceServer::handleInstanceHidden(const ServerNodeInst }; if (auto childNode = qobject_cast(childItem)) checkChildren(childNode); +#endif } } } #else - Q_UNUSED(instance); - Q_UNUSED(enable); - Q_UNUSED(checkAncestors); + Q_UNUSED(instance) + Q_UNUSED(enable) + Q_UNUSED(checkAncestors) +#endif +} + +void Qt5InformationNodeInstanceServer::handlePickTarget(const ServerNodeInstance &instance) +{ +#if defined(QUICK3D_MODULE) && (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1)) + // Picking is dependent on hidden status prior to global picking support (<6.2.1), so it is + // handled in handleInstanceHidden() method in those builds + + if (!ViewConfig::isQuick3DMode()) + return; + + QObject *obj = instance.internalObject(); + QList childItems; + if (auto node = qobject_cast(obj)) { + childItems = node->childItems(); + } else if (auto view = qobject_cast(obj)) { + // We only need to handle views that are components + // View is a component if its scene is not an instance and scene has node children that + // have no instances + QQuick3DNode *node = view->scene(); + if (node) { + if (hasInstanceForObject(node)) + return; + childItems = node->childItems(); + bool allHaveInstance = true; + for (const auto &childItem : childItems) { + if (qobject_cast(childItem) && !hasInstanceForObject(childItem)) { + allHaveInstance = false; + break; + } + } + if (allHaveInstance) + return; + } + } else { + return; + } + + for (auto childItem : qAsConst(childItems)) { + if (!hasInstanceForObject(childItem)) { + // Children of components do not have instances, but will still need to be pickable + // and redirect their pick to the component + std::function checkChildren; + checkChildren = [&](QQuick3DNode *checkNode) { + const auto childItems = checkNode->childItems(); + for (auto child : childItems) { + if (auto childNode = qobject_cast(child)) + checkChildren(childNode); + } + if (auto checkModel = qobject_cast(checkNode)) { + // Specify the actual pick target with dynamic property + checkModel->setProperty("_pickTarget", QVariant::fromValue(obj)); + } else { + auto checkRepeater = qobject_cast(checkNode); + auto checkLoader = qobject_cast(checkNode); +#if defined(QUICK3D_ASSET_UTILS_MODULE) + auto checkRunLoader = qobject_cast(checkNode); + if (checkRepeater || checkLoader || checkRunLoader) { +#else + if (checkRepeater || checkLoader) { +#endif + // Repeaters/loaders may not yet have created their children, so we set + // _pickTarget on them and connect the notifier. + if (checkNode->property("_pickTarget").isNull()) { + if (checkRepeater) { + QObject::connect(checkRepeater, &QQuick3DRepeater::objectAdded, + this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); +#if defined(QUICK3D_ASSET_UTILS_MODULE) + } else if (checkRunLoader) { + QObject::connect(checkRunLoader, &QQuick3DRuntimeLoader::statusChanged, + this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); +#endif + } else { + QObject::connect(checkLoader, &QQuick3DLoader::loaded, + this, &Qt5InformationNodeInstanceServer::handleDynamicAddObject); + } + } + checkNode->setProperty("_pickTarget", QVariant::fromValue(obj)); + } + } + }; + if (auto childNode = qobject_cast(childItem)) + checkChildren(childNode); + } + } +#else + Q_UNUSED(instance) #endif } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index b6375076b66..291b2c3eecd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -80,6 +80,7 @@ public: void handleInstanceLocked(const ServerNodeInstance &instance, bool enable, bool checkAncestors) override; void handleInstanceHidden(const ServerNodeInstance &instance, bool enable, bool checkAncestors) override; + void handlePickTarget(const ServerNodeInstance &instance) override; bool isInformationServer() const override; void handleDynamicAddObject(); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp index a4fdde97ec2..2f4be3e8e9b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp @@ -323,8 +323,12 @@ ServerNodeInstance ServerNodeInstance::create(NodeInstanceServer *nodeInstanceSe instance.internalInstance()->initialize(instance.m_nodeInstance, instanceContainer.metaFlags()); +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 1) // Handle hidden state to initialize pickable state nodeInstanceServer->handleInstanceHidden(instance, false, false); +#else + nodeInstanceServer->handlePickTarget(instance); +#endif return instance; }