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:
Miikka Heikkinen
2022-05-20 15:24:22 +03:00
parent fc2f3983bc
commit c789186418
9 changed files with 161 additions and 19 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -1561,6 +1561,10 @@ void NodeInstanceServer::handleInstanceHidden(const ServerNodeInstance &/*instan
{
}
void NodeInstanceServer::handlePickTarget(const ServerNodeInstance &/*instance*/)
{
}
void NodeInstanceServer::setupState(qint32 stateInstanceId)
{
if (hasInstanceForId(stateInstanceId)) {

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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;
}