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

View File

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

View File

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

View File

@ -360,7 +360,9 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv
} else if ((d->grabPermissions & CanTakeOverFromItems)) {
allowed = true;
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 &&
((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<QQuickItem *>(QObject::parent());
return qmlobject_cast<QQuickItem *>(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()
*/

View File

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

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
// the result of the previous call to determine if we should call the handler.
alreadyFiltered = it->second;
} 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<QQuickItem *>(epd.exclusiveGrabber.data()))
relevant = (item->window() == win);
else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data()))
relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q);
else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(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<QQuickPointerHandler *>(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<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),
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 {
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<QQuickPointerHandler *>(grabber);
receiver = static_cast<QQuickPointerHandler *>(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)