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 <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2021-03-09 21:41:06 +01:00
parent 21a458e16e
commit 1285b67a11
6 changed files with 68 additions and 29 deletions

View File

@ -99,8 +99,10 @@ QQuickDragHandler::QQuickDragHandler(QQuickItem *parent)
QPointF QQuickDragHandler::targetCentroidPosition() QPointF QQuickDragHandler::targetCentroidPosition()
{ {
QPointF pos = centroid().position(); QPointF pos = centroid().position();
if (target() != parentItem()) if (auto par = parentItem()) {
pos = parentItem()->mapToItem(target(), pos); if (target() != par)
pos = par->mapToItem(target(), pos);
}
return 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. // In case the grab got handed over from another grabber, we might not get the Press.
auto isDescendant = [](QQuickItem *parent, QQuickItem *target) { auto isDescendant = [](QQuickItem *parent, QQuickItem *target) {
return (target != parent) && !target->isAncestorOf(parent); return parent && (target != parent) && !target->isAncestorOf(parent);
}; };
if (m_snapMode == SnapAlways if (m_snapMode == SnapAlways
|| (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget) || (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget)

View File

@ -101,8 +101,10 @@ bool QQuickHoverHandler::event(QEvent *event)
void QQuickHoverHandler::componentComplete() void QQuickHoverHandler::componentComplete()
{ {
parentItem()->setAcceptHoverEvents(true); if (auto par = parentItem()) {
QQuickItemPrivate::get(parentItem())->setHasHoverInChild(true); par->setAcceptHoverEvents(true);
QQuickItemPrivate::get(par)->setHasHoverInChild(true);
}
} }
bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event) bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)

View File

@ -104,7 +104,8 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QPointerEvent *event)
d->currentPoints.resize(c); d->currentPoints.resize(c);
for (int i = 0; i < c; ++i) { for (int i = 0; i < c; ++i) {
d->currentPoints[i].reset(event, candidatePoints[i]); d->currentPoints[i].reset(event, candidatePoints[i]);
d->currentPoints[i].localize(parentItem()); if (auto par = parentItem())
d->currentPoints[i].localize(par);
} }
} else { } else {
d->currentPoints.clear(); d->currentPoints.clear();

View File

@ -360,7 +360,9 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv
} else if ((d->grabPermissions & CanTakeOverFromItems)) { } else if ((d->grabPermissions & CanTakeOverFromItems)) {
allowed = true; allowed = true;
QQuickItem * existingItemGrabber = qobject_cast<QQuickItem *>(event->exclusiveGrabber(point)); QQuickItem * existingItemGrabber = qobject_cast<QQuickItem *>(event->exclusiveGrabber(point));
auto da = QQuickItemPrivate::get(parentItem())->deliveryAgentPrivate(); auto da = parentItem() ? QQuickItemPrivate::get(parentItem())->deliveryAgentPrivate()
: QQuickDeliveryAgentPrivate::currentEventDeliveryAgent ? static_cast<QQuickDeliveryAgentPrivate *>(
QQuickDeliveryAgentPrivate::get(QQuickDeliveryAgentPrivate::currentEventDeliveryAgent)) : nullptr;
if (existingItemGrabber && if (existingItemGrabber &&
((existingItemGrabber->keepMouseGrab() && ((existingItemGrabber->keepMouseGrab() &&
(QQuickDeliveryAgentPrivate::isMouseEvent(event) || da->isDeliveringTouchAsMouse())) || (QQuickDeliveryAgentPrivate::isMouseEvent(event) || da->isDeliveringTouchAsMouse())) ||
@ -546,6 +548,10 @@ bool QQuickPointerHandler::parentContains(const QPointF &scenePosition) const
if (m > 0) if (m > 0)
return p.x() >= -m && p.y() >= -m && p.x() <= par->width() + m && p.y() <= par->height() + m; return p.x() >= -m && p.y() >= -m && p.x() <= par->width() + m && p.y() <= par->height() + m;
return par->contains(p); 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; return false;
} }
@ -604,7 +610,7 @@ void QQuickPointerHandler::setTarget(QQuickItem *target)
QQuickItem *QQuickPointerHandler::parentItem() const QQuickItem *QQuickPointerHandler::parentItem() const
{ {
return static_cast<QQuickItem *>(QObject::parent()); return qmlobject_cast<QQuickItem *>(QObject::parent());
} }
QQuickItem *QQuickPointerHandler::target() const QQuickItem *QQuickPointerHandler::target() const
@ -641,7 +647,7 @@ void QQuickPointerHandler::handlePointerEvent(QPointerEvent *event)
{ {
bool wants = wantsPointerEvent(event); bool wants = wantsPointerEvent(event);
qCDebug(lcPointerHandlerDispatch) << metaObject()->className() << objectName() qCDebug(lcPointerHandlerDispatch) << metaObject()->className() << objectName()
<< "on" << parentItem()->metaObject()->className() << parentItem()->objectName() << "on" << parent()->metaObject()->className() << parent()->objectName()
<< (wants ? "WANTS" : "DECLINES") << event; << (wants ? "WANTS" : "DECLINES") << event;
if (wants) { if (wants) {
handlePointerEventImpl(event); 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 the Item's interior. Initially \l [QML] {target} {target()} is the same, but it
can be reassigned. 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() \sa {target}, QObject::parent()
*/ */

View File

@ -342,10 +342,13 @@ void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDev
void QQuickTapHandler::connectPreRenderSignal(bool conn) void QQuickTapHandler::connectPreRenderSignal(bool conn)
{ {
auto par = parentItem();
if (!par)
return;
if (conn) if (conn)
connect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); connect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld);
else else
disconnect(parentItem()->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld); disconnect(par->window(), &QQuickWindow::beforeSynchronizing, this, &QQuickTapHandler::updateTimeHeld);
} }
void QQuickTapHandler::updateTimeHeld() void QQuickTapHandler::updateTimeHeld()

View File

@ -916,12 +916,13 @@ void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QVector<QPointer
// Yes, the event was already filtered to that parent, do not call it again but use // Yes, the event was already filtered to that parent, do not call it again but use
// the result of the previous call to determine if we should call the handler. // the result of the previous call to determine if we should call the handler.
alreadyFiltered = it->second; alreadyFiltered = it->second;
} else { } else if (par) {
alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par); alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par);
sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered); sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered);
} }
if (!alreadyFiltered) { if (!alreadyFiltered) {
localizePointerEvent(pointerEvent, handler->parentItem()); if (par)
localizePointerEvent(pointerEvent, par);
handler->handlePointerEvent(pointerEvent); handler->handlePointerEvent(pointerEvent);
} }
} }
@ -1113,8 +1114,14 @@ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win)
bool relevant = false; bool relevant = false;
if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data())) if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data()))
relevant = (item->window() == win); relevant = (item->window() == win);
else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) {
relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q); 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) if (relevant)
devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr); devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr);
} }
@ -1391,6 +1398,8 @@ void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event)
void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
{ {
Q_Q(QQuickDeliveryAgent); Q_Q(QQuickDeliveryAgent);
QQuickDeliveryAgent *deliveringAgent = QQuickDeliveryAgentPrivate::currentEventDeliveryAgent;
QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = q;
if (delayedTouch) { if (delayedTouch) {
deliverDelayedTouchEvent(); deliverDelayedTouchEvent();
@ -1421,6 +1430,9 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done"; qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done";
} }
#endif #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, 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) // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber)
if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) { if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) {
auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); if (handler->parentItem()) {
deliveryAgent = itemPriv->deliveryAgent(); auto itemPriv = QQuickItemPrivate::get(handler->parentItem());
if (deliveryAgent == q) { deliveryAgent = itemPriv->deliveryAgent();
if (deliveryAgent == q) {
handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
const_cast<QEventPoint &>(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<QPointerEvent *>(event), handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
const_cast<QEventPoint &>(point)); const_cast<QEventPoint &>(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 { } else {
switch (transition) { switch (transition) {
case QPointingDevice::CancelGrabExclusive: 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. // The grabber is not an item? It's a handler then. Let it have the event first.
QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber); QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber);
receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem(); receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
hasFiltered.clear(); // Filtering via QQuickItem::childMouseEventFilter() is only possible
if (sendFilteredPointerEvent(event, receiver)) // if the handler's parent is an Item. It could be a QQ3D object.
done = true; if (receiver) {
localizePointerEvent(event, receiver); hasFiltered.clear();
if (sendFilteredPointerEvent(event, receiver))
done = true;
localizePointerEvent(event, receiver);
}
handler->handlePointerEvent(event); handler->handlePointerEvent(event);
} }
if (done) 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, // 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). // then deliver the event to the item (which may have multiple handlers).
hasFiltered.clear(); 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) // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)