From 1285b67a113cd2eb4fc03ec3e4ddd4dfdbe8ae76 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 9 Mar 2021 21:41:06 +0100 Subject: [PATCH] Allow pointer handlers to be added to QQuick3DModel objects Mainly it's a matter of removing the assumption that parent() is always a QQuickItem. But handlers that have a target property do not know how to manipulate it when it's not an item; so for example you can use DragHandler's translation property to manipulate the object, but it doesn't drag a 3D object by default. Delivery logic for now is implemented in QQuick3DViewport, because it's intimately tied to picking, and QQuickDeliveryAgent doesn't really know anything about QQ3D objects, and the conventional delivery to handlers in Qt Quick depends on QQuickItemPrivate::handlePointerEvent() which isn't available in that use case. Hover events are interfering with DragHnadler (wantsPointerEvent() returns false, therefore the handler gets deactivated right away). HoverHandler detects hover but does not detect leave, but that's probably a matter for the delivery logic to fix. Change-Id: Id0ec385ce8df3a003f72a6666d16632cef72bbd6 Reviewed-by: Volker Hilsheimer --- src/quick/handlers/qquickdraghandler.cpp | 8 ++- src/quick/handlers/qquickhoverhandler.cpp | 6 +- .../handlers/qquickmultipointhandler.cpp | 3 +- src/quick/handlers/qquickpointerhandler.cpp | 15 ++++- src/quick/handlers/qquicktaphandler.cpp | 7 ++- src/quick/util/qquickdeliveryagent.cpp | 58 +++++++++++++------ 6 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index 36e51eb423..809adfd043 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -99,8 +99,10 @@ QQuickDragHandler::QQuickDragHandler(QQuickItem *parent) QPointF QQuickDragHandler::targetCentroidPosition() { QPointF pos = centroid().position(); - if (target() != parentItem()) - pos = parentItem()->mapToItem(target(), pos); + if (auto par = parentItem()) { + if (target() != par) + pos = par->mapToItem(target(), pos); + } return pos; } @@ -111,7 +113,7 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDe // In case the grab got handed over from another grabber, we might not get the Press. auto isDescendant = [](QQuickItem *parent, QQuickItem *target) { - return (target != parent) && !target->isAncestorOf(parent); + return parent && (target != parent) && !target->isAncestorOf(parent); }; if (m_snapMode == SnapAlways || (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget) diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp index b934940126..020afc647a 100644 --- a/src/quick/handlers/qquickhoverhandler.cpp +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -101,8 +101,10 @@ bool QQuickHoverHandler::event(QEvent *event) void QQuickHoverHandler::componentComplete() { - parentItem()->setAcceptHoverEvents(true); - QQuickItemPrivate::get(parentItem())->setHasHoverInChild(true); + if (auto par = parentItem()) { + par->setAcceptHoverEvents(true); + QQuickItemPrivate::get(par)->setHasHoverInChild(true); + } } bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event) diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index 85040a1a12..8c13c84914 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -104,7 +104,8 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QPointerEvent *event) d->currentPoints.resize(c); for (int i = 0; i < c; ++i) { d->currentPoints[i].reset(event, candidatePoints[i]); - d->currentPoints[i].localize(parentItem()); + if (auto par = parentItem()) + d->currentPoints[i].localize(par); } } else { d->currentPoints.clear(); diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index c5524fc107..495eb8b7d3 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -360,7 +360,9 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv } else if ((d->grabPermissions & CanTakeOverFromItems)) { allowed = true; QQuickItem * existingItemGrabber = qobject_cast(event->exclusiveGrabber(point)); - auto da = QQuickItemPrivate::get(parentItem())->deliveryAgentPrivate(); + auto da = parentItem() ? QQuickItemPrivate::get(parentItem())->deliveryAgentPrivate() + : QQuickDeliveryAgentPrivate::currentEventDeliveryAgent ? static_cast( + QQuickDeliveryAgentPrivate::get(QQuickDeliveryAgentPrivate::currentEventDeliveryAgent)) : nullptr; if (existingItemGrabber && ((existingItemGrabber->keepMouseGrab() && (QQuickDeliveryAgentPrivate::isMouseEvent(event) || da->isDeliveringTouchAsMouse())) || @@ -546,6 +548,10 @@ bool QQuickPointerHandler::parentContains(const QPointF &scenePosition) const if (m > 0) return p.x() >= -m && p.y() >= -m && p.x() <= par->width() + m && p.y() <= par->height() + m; return par->contains(p); + } else if (parent() && parent()->inherits("QQuick3DModel")) { + // If the parent is from Qt Quick 3D, assume that + // bounds checking was already done, as part of picking. + return true; } return false; } @@ -604,7 +610,7 @@ void QQuickPointerHandler::setTarget(QQuickItem *target) QQuickItem *QQuickPointerHandler::parentItem() const { - return static_cast(QObject::parent()); + return qmlobject_cast(QObject::parent()); } QQuickItem *QQuickPointerHandler::target() const @@ -641,7 +647,7 @@ void QQuickPointerHandler::handlePointerEvent(QPointerEvent *event) { bool wants = wantsPointerEvent(event); qCDebug(lcPointerHandlerDispatch) << metaObject()->className() << objectName() - << "on" << parentItem()->metaObject()->className() << parentItem()->objectName() + << "on" << parent()->metaObject()->className() << parent()->objectName() << (wants ? "WANTS" : "DECLINES") << event; if (wants) { handlePointerEventImpl(event); @@ -715,6 +721,9 @@ void QQuickPointerHandler::handlePointerEventImpl(QPointerEvent *event) the Item's interior. Initially \l [QML] {target} {target()} is the same, but it can be reassigned. + \note When a handler is declared in a \l QtQuick3D.Model object, the parent + is not an Item, therefore this property is \c null. + \sa {target}, QObject::parent() */ diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index 707a750f3b..d16c9e23cb 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -342,10 +342,13 @@ void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDev void QQuickTapHandler::connectPreRenderSignal(bool conn) { + auto par = parentItem(); + if (!par) + return; if (conn) - connect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); + connect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); else - disconnect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); + disconnect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); } void QQuickTapHandler::updateTimeHeld() diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 4fe3e9db6e..2b649d01cf 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -916,12 +916,13 @@ void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QVectorsecond; - } else { + } else if (par) { alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par); sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered); } if (!alreadyFiltered) { - localizePointerEvent(pointerEvent, handler->parentItem()); + if (par) + localizePointerEvent(pointerEvent, par); handler->handlePointerEvent(pointerEvent); } } @@ -1113,8 +1114,14 @@ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win) bool relevant = false; if (QQuickItem *item = qmlobject_cast(epd.exclusiveGrabber.data())) relevant = (item->window() == win); - else if (QQuickPointerHandler *handler = qmlobject_cast(epd.exclusiveGrabber.data())) - relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q); + else if (QQuickPointerHandler *handler = qmlobject_cast(epd.exclusiveGrabber.data())) { + if (handler->parentItem()) + relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q); + else + // a handler with no Item parent probably has a 3D Model parent. + // TODO actually check the window somehow + relevant = true; + } if (relevant) devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr); } @@ -1391,6 +1398,8 @@ void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event) void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) { Q_Q(QQuickDeliveryAgent); + QQuickDeliveryAgent *deliveringAgent = QQuickDeliveryAgentPrivate::currentEventDeliveryAgent; + QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = q; if (delayedTouch) { deliverDelayedTouchEvent(); @@ -1421,6 +1430,9 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done"; } #endif + if (Q_UNLIKELY(QQuickDeliveryAgentPrivate::currentEventDeliveryAgent != q)) + qCWarning(lcPtr, "detected interleaved frame-sync and actual events"); + QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = deliveringAgent; } void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, @@ -1434,18 +1446,23 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber) if (auto *handler = qmlobject_cast(grabber)) { - auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); - deliveryAgent = itemPriv->deliveryAgent(); - if (deliveryAgent == q) { + if (handler->parentItem()) { + auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); + deliveryAgent = itemPriv->deliveryAgent(); + if (deliveryAgent == q) { + handler->onGrabChanged(handler, transition, const_cast(event), + const_cast(point)); + } + if (grabGained) { + // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, + // whereas the subscene root item already knows it has its own DA. + if (isSubsceneAgent && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) + itemPriv->maybeHasSubsceneDeliveryAgent = true; + } + } else if (!isSubsceneAgent) { handler->onGrabChanged(handler, transition, const_cast(event), const_cast(point)); } - if (grabGained) { - // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, - // whereas the subscene root item already knows it has its own DA. - if (isSubsceneAgent && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) - itemPriv->maybeHasSubsceneDeliveryAgent = true; - } } else { switch (transition) { case QPointingDevice::CancelGrabExclusive: @@ -1681,10 +1698,14 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) // The grabber is not an item? It's a handler then. Let it have the event first. QQuickPointerHandler *handler = static_cast(grabber); receiver = static_cast(grabber)->parentItem(); - hasFiltered.clear(); - if (sendFilteredPointerEvent(event, receiver)) - done = true; - localizePointerEvent(event, receiver); + // Filtering via QQuickItem::childMouseEventFilter() is only possible + // if the handler's parent is an Item. It could be a QQ3D object. + if (receiver) { + hasFiltered.clear(); + if (sendFilteredPointerEvent(event, receiver)) + done = true; + localizePointerEvent(event, receiver); + } handler->handlePointerEvent(event); } if (done) @@ -1692,7 +1713,8 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) // If the grabber is an item or the grabbing handler didn't handle it, // then deliver the event to the item (which may have multiple handlers). hasFiltered.clear(); - deliverMatchingPointsToItem(receiver, true, event); + if (receiver) + deliverMatchingPointsToItem(receiver, true, event); } // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)