forked from qt-creator/qt-creator
QmlDesigner: Handle picking of models under View3D component properly
If a model defined inside the View3D component is picked on 3D editor, the parent View3D is selected instead as there is no instance for the model itself. This is similar to how Node based component picking works. Fixes: QDS-6934 Change-Id: I4f273972da8cb1c55f03cab323dd9804a5d10def Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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<QQuick3DNode *>();
|
||||
if (componentNode)
|
||||
return componentNode;
|
||||
auto componentObj = componentVar.value<QObject *>();
|
||||
if (componentObj)
|
||||
return componentObj;
|
||||
}
|
||||
}
|
||||
return pickNode;
|
||||
|
@@ -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;
|
||||
|
@@ -1561,6 +1561,10 @@ void NodeInstanceServer::handleInstanceHidden(const ServerNodeInstance &/*instan
|
||||
{
|
||||
}
|
||||
|
||||
void NodeInstanceServer::handlePickTarget(const ServerNodeInstance &/*instance*/)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeInstanceServer::setupState(qint32 stateInstanceId)
|
||||
{
|
||||
if (hasInstanceForId(stateInstanceId)) {
|
||||
|
@@ -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;
|
||||
|
@@ -553,7 +553,18 @@ void Qt5InformationNodeInstanceServer::handleSelectionChanged(const QVariant &ob
|
||||
auto obj = object.value<QObject *>();
|
||||
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<QQuick3DNode *>())
|
||||
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<QObject *>())
|
||||
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<void(QQuick3DNode *)> 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<QQuick3DRepeater *>(checkNode);
|
||||
auto checkLoader = qobject_cast<QQuick3DLoader *>(checkNode);
|
||||
@@ -2432,13 +2457,102 @@ void Qt5InformationNodeInstanceServer::handleInstanceHidden(const ServerNodeInst
|
||||
};
|
||||
if (auto childNode = qobject_cast<QQuick3DNode *>(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<QQuick3DObject *> childItems;
|
||||
if (auto node = qobject_cast<QQuick3DNode *>(obj)) {
|
||||
childItems = node->childItems();
|
||||
} else if (auto view = qobject_cast<QQuick3DViewport *>(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<QQuick3DNode *>(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<void(QQuick3DNode *)> checkChildren;
|
||||
checkChildren = [&](QQuick3DNode *checkNode) {
|
||||
const auto childItems = checkNode->childItems();
|
||||
for (auto child : childItems) {
|
||||
if (auto childNode = qobject_cast<QQuick3DNode *>(child))
|
||||
checkChildren(childNode);
|
||||
}
|
||||
if (auto checkModel = qobject_cast<QQuick3DModel *>(checkNode)) {
|
||||
// Specify the actual pick target with dynamic property
|
||||
checkModel->setProperty("_pickTarget", QVariant::fromValue(obj));
|
||||
} else {
|
||||
auto checkRepeater = qobject_cast<QQuick3DRepeater *>(checkNode);
|
||||
auto checkLoader = qobject_cast<QQuick3DLoader *>(checkNode);
|
||||
#if defined(QUICK3D_ASSET_UTILS_MODULE)
|
||||
auto checkRunLoader = qobject_cast<QQuick3DRuntimeLoader *>(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<QQuick3DNode *>(childItem))
|
||||
checkChildren(childNode);
|
||||
}
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(instance)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user