From 48011c2dfeb83b4fe717034d4b3c353714fead48 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 3 Apr 2017 11:37:03 +0200 Subject: Start over with event delivery when touchpoint releases occur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new rule is that when the number of touchpoints changes, we start over with event delivery as if the touch had just begun, to give more opportunities to hand off processing from one item or handler to another. And MultiPointTouchArea can now handle the handoff: for example in tests/manual/pointer/pinchDragFlingMPTA.qml when the user is pressing three fingers, the PinchHandler is active; when the user then lifts one finger, the MPTA can resume handling the two remaining touchpoints as if they were just pressed. The change in QQuickMultiPointerHandler::wantsPointerEvent is both a behavior change and an optimization: released points aren't eligible; but if some points are released, then pressed, updated and stationary points are all eligible. And, figure this out without looping over the points twice. Change-Id: I26b7593de8e72b471adfec4a4482dd87a8288442 Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickmultipointerhandler.cpp | 36 +++++++++------------- src/quick/handlers/qquickmultipointerhandler_p.h | 2 +- src/quick/handlers/qquickpinchhandler.cpp | 6 ++-- src/quick/handlers/qquickpointerhandler.cpp | 1 + src/quick/handlers/qquicktaphandler.cpp | 7 +++-- src/quick/items/qquickevents.cpp | 36 +++++++++++++++++----- src/quick/items/qquickevents_p_p.h | 9 +++++- src/quick/items/qquickitem.cpp | 1 - src/quick/items/qquickwindow.cpp | 38 +++++++++++++++--------- src/quick/items/qquickwindow_p.h | 4 +-- 10 files changed, 87 insertions(+), 53 deletions(-) diff --git a/src/quick/handlers/qquickmultipointerhandler.cpp b/src/quick/handlers/qquickmultipointerhandler.cpp index 016210beff..a605b3f12e 100644 --- a/src/quick/handlers/qquickmultipointerhandler.cpp +++ b/src/quick/handlers/qquickmultipointerhandler.cpp @@ -66,44 +66,34 @@ bool QQuickMultiPointerHandler::wantsPointerEvent(QQuickPointerEvent *event) if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) return false; - const int pCount = event->pointCount(); - int pointCandidateCount = 0; - - // If one or more points are newly pressed, all points are candidates for this handler. - // In other cases however, do not steal the grab: that is, if a point has a grabber, - // it's not a candidate for this handler. - if (event->isPressEvent()) { - pointCandidateCount = pCount; - } else { - for (int i = 0; i < pCount; ++i) { - QQuickEventPoint *pt = event->point(i); - QObject *exclusiveGrabber = pt->exclusiveGrabber(); - if (!exclusiveGrabber || exclusiveGrabber == this) - ++pointCandidateCount; - } - } - - if (pointCandidateCount < m_requiredPointCount) - return false; if (sameAsCurrentPoints(event)) return true; - const QVector candidatePoints = pointsInsideOrNearTarget(event); + const QVector candidatePoints = eligiblePoints(event); const bool ret = (candidatePoints.size() == m_requiredPointCount); if (ret) m_currentPoints = candidatePoints; return ret; } -QVector QQuickMultiPointerHandler::pointsInsideOrNearTarget(QQuickPointerEvent *event) +QVector QQuickMultiPointerHandler::eligiblePoints(QQuickPointerEvent *event) { QVector ret; int c = event->pointCount(); QRectF targetBounds = target()->mapRectToScene(target()->boundingRect()) .marginsAdded(QMarginsF(m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold, m_pointDistanceThreshold)); + // If one or more points are newly pressed or released, all non-released points are candidates for this handler. + // In other cases however, do not steal the grab: that is, if a point has a grabber, + // it's not a candidate for this handler. + bool stealingAllowed = event->isPressEvent() || event->isReleaseEvent(); for (int i = 0; i < c; ++i) { QQuickEventPoint *p = event->point(i); - if (targetBounds.contains(p->scenePos())) + if (!stealingAllowed) { + QObject *exclusiveGrabber = p->exclusiveGrabber(); + if (exclusiveGrabber && exclusiveGrabber != this) + continue; + } + if (p->state() != QQuickEventPoint::Released && targetBounds.contains(p->scenePos())) ret << p; } return ret; @@ -136,6 +126,8 @@ bool QQuickMultiPointerHandler::sameAsCurrentPoints(QQuickPointerEvent *event) // TODO optimize: either ensure the points are sorted, // or use std::equal with a predicate for (int i = 0; ret && i < c; ++i) { + if (event->point(i)->state() == QQuickEventPoint::Released) + return false; bool found = false; int pointId = event->point(i)->pointId(); for (QQuickEventPoint *o : qAsConst(m_currentPoints)) diff --git a/src/quick/handlers/qquickmultipointerhandler_p.h b/src/quick/handlers/qquickmultipointerhandler_p.h index ce014d658c..1f2d213e56 100644 --- a/src/quick/handlers/qquickmultipointerhandler_p.h +++ b/src/quick/handlers/qquickmultipointerhandler_p.h @@ -88,7 +88,7 @@ protected: bool wantsPointerEvent(QQuickPointerEvent *event) override; bool sameAsCurrentPoints(QQuickPointerEvent *event); - QVector pointsInsideOrNearTarget(QQuickPointerEvent *event); + QVector eligiblePoints(QQuickPointerEvent *event); QPointF touchPointCentroid(); QVector2D touchPointCentroidVelocity(); qreal averageTouchPointDistance(const QPointF &ref); diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index 3af77593e2..465a49a6fd 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -228,7 +228,8 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) qCDebug(lcPinchHandler) << point->state() << point->sceneGrabPos() << "->" << point->scenePos(); } - if (!active()) { + bool containsReleasedPoints = event->isReleaseEvent(); + if (!active() && !containsReleasedPoints) { // Verify that least one of the points have moved beyond threshold needed to activate the handler for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { if (QQuickWindowPrivate::dragOverThreshold(point)) { @@ -302,7 +303,8 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) << ", rotation" << rotation; } - acceptPoints(m_currentPoints); + if (!containsReleasedPoints) + acceptPoints(m_currentPoints); emit updated(); } diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index 3774c2d24c..1ed80c0a5b 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -216,6 +216,7 @@ void QQuickPointerHandler::handlePointerEvent(QQuickPointerEvent *event) pt->cancelExclusiveGrab(); } } + event->device()->eventDeliveryTargets().append(this); } bool QQuickPointerHandler::wantsPointerEvent(QQuickPointerEvent *event) diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index f79dbb266a..d73e9fd100 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -270,7 +270,7 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *poi else setExclusiveGrab(point, press); } - if (!cancel && !press) { + if (!cancel && !press && parentContains(point)) { if (point->timeHeld() < longPressThreshold()) { // Assuming here that pointerEvent()->timestamp() is in ms. qreal ts = point->pointerEvent()->timestamp() / 1000.0; @@ -301,8 +301,9 @@ void QQuickTapHandler::setPressed(bool press, bool cancel, QQuickEventPoint *poi void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) { QQuickPointerSingleHandler::onGrabChanged(grabber, stateChange, point); - if (grabber == this && (stateChange == QQuickEventPoint::CancelGrabExclusive || stateChange == QQuickEventPoint::CancelGrabPassive)) - setPressed(false, true, point); + bool isCanceled = stateChange == QQuickEventPoint::CancelGrabExclusive || stateChange == QQuickEventPoint::CancelGrabPassive; + if (grabber == this && (isCanceled || point->state() == QQuickEventPoint::Released)) + setPressed(false, isCanceled, point); } void QQuickTapHandler::connectPreRenderSignal(bool conn) diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 0f613d300b..bde271882c 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -1055,6 +1055,18 @@ bool QQuickPointerMouseEvent::isPressEvent() const (me->buttons() & me->button()) == me->buttons()); } +bool QQuickPointerMouseEvent::isUpdateEvent() const +{ + auto me = static_cast(m_event); + return me->type() == QEvent::MouseMove; +} + +bool QQuickPointerMouseEvent::isReleaseEvent() const +{ + auto me = static_cast(m_event); + return me->type() == QEvent::MouseButtonRelease; +} + bool QQuickPointerTouchEvent::allPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { if (!m_touchPoints.at(i)->isAccepted()) @@ -1124,6 +1136,16 @@ bool QQuickPointerTouchEvent::isPressEvent() const return static_cast(m_event)->touchPointStates() & Qt::TouchPointPressed; } +bool QQuickPointerTouchEvent::isUpdateEvent() const +{ + return static_cast(m_event)->touchPointStates() & (Qt::TouchPointMoved | Qt::TouchPointStationary); +} + +bool QQuickPointerTouchEvent::isReleaseEvent() const +{ + return static_cast(m_event)->touchPointStates() & Qt::TouchPointReleased; +} + QVector QQuickPointerEvent::unacceptedPressedPointScenePositions() const { QVector points; @@ -1239,7 +1261,7 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i // Or else just document that velocity is always scene-relative and is not scaled and rotated with the item // but that would require changing tst_qquickwindow::touchEvent_velocity(): it expects transformed velocity - bool anyPressInside = false; + bool anyPressOrReleaseInside = false; bool anyGrabber = false; QMatrix4x4 transformMatrix(QQuickItemPrivate::get(item)->windowToItemTransform()); for (int i = 0; i < m_pointCount; ++i) { @@ -1250,8 +1272,9 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i bool isGrabber = p->exclusiveGrabber() == item; if (isGrabber) anyGrabber = true; - // include points inside the bounds + // include points inside the bounds if no other item is the grabber or if the item is filtering bool isInside = item->contains(item->mapFromScene(p->scenePos())); + bool hasAnotherGrabber = p->exclusiveGrabber() && p->exclusiveGrabber() != item; // filtering: (childMouseEventFilter) include points that are grabbed by children of the target item bool grabberIsChild = false; @@ -1265,11 +1288,10 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i } bool filterRelevant = isFiltering && grabberIsChild; - if (!(isGrabber || isInside || filterRelevant)) + if (!(isGrabber || (isInside && (!hasAnotherGrabber || isFiltering)) || filterRelevant)) continue; - bool isPress = p->state() == QQuickEventPoint::Pressed; - if (isPress && isInside) - anyPressInside = true; + if ((p->state() == QQuickEventPoint::Pressed || p->state() == QQuickEventPoint::Released) && isInside) + anyPressOrReleaseInside = true; const QTouchEvent::TouchPoint *tp = touchPointById(p->pointId()); if (tp) { eventStates |= tp->state(); @@ -1285,7 +1307,7 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i // Now touchPoints will have only points which are inside the item. // But if none of them were just pressed inside, and the item has no other reason to care, ignore them anyway. - if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty() || (!anyPressInside && !anyGrabber && !isFiltering)) + if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty() || (!anyPressOrReleaseInside && !anyGrabber && !isFiltering)) return nullptr; // if all points have the same state, set the event type accordingly diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 72d5be54c9..4b3587f18c 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -272,7 +272,8 @@ public: Released = Qt::TouchPointReleased // Canceled = Qt::TouchPointReleased << 1 // 0x10 // TODO maybe }; - Q_ENUM(State) + Q_DECLARE_FLAGS(States, State) + Q_FLAG(States) enum GrabState { GrabPassive = 0, @@ -406,6 +407,8 @@ public: // helpers for C++ only (during event delivery) virtual void localize(QQuickItem *target) = 0; virtual bool isPressEvent() const = 0; + virtual bool isUpdateEvent() const = 0; + virtual bool isReleaseEvent() const = 0; virtual QQuickPointerMouseEvent *asPointerMouseEvent() { return nullptr; } virtual QQuickPointerTouchEvent *asPointerTouchEvent() { return nullptr; } virtual QQuickPointerTabletEvent *asPointerTabletEvent() { return nullptr; } @@ -448,6 +451,8 @@ public: QQuickPointerEvent *reset(QEvent *) override; void localize(QQuickItem *target) override; bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; QQuickPointerMouseEvent *asPointerMouseEvent() override { return this; } const QQuickPointerMouseEvent *asPointerMouseEvent() const override { return this; } int pointCount() const override { return 1; } @@ -481,6 +486,8 @@ public: QQuickPointerEvent *reset(QEvent *) override; void localize(QQuickItem *target) override; bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; QQuickPointerTouchEvent *asPointerTouchEvent() override { return this; } const QQuickPointerTouchEvent *asPointerTouchEvent() const override { return this; } int pointCount() const override { return m_pointCount; } diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 4cb7ef3e22..e959b51bc3 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -5113,7 +5113,6 @@ bool QQuickItemPrivate::handlePointerEvent(QQuickPointerEvent *event, bool avoid if ((!avoidExclusiveGrabber || !event->hasExclusiveGrabber(handler)) && !eventDeliveryTargets.contains(handler)) { handler->handlePointerEvent(event); delivered = true; - eventDeliveryTargets.append(handler); } } } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 9c889d54f5..e4eed8454c 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1703,18 +1703,17 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven bool delivered = false; if (pointerEvent->isPressEvent()) { // send initial press - delivered = deliverPressEvent(pointerEvent); + delivered = deliverPressOrReleaseEvent(pointerEvent); } else if (pointerEvent->device()->type() == QQuickPointerDevice::Mouse) { // if this is an update or release from an actual mouse, // and the point wasn't grabbed, deliver only to PointerHandlers: // passive grabbers first, then the rest - QVector &eventDeliveryTargets = pointerEvent->device()->eventDeliveryTargets(); + const QVector &eventDeliveryTargets = pointerEvent->device()->eventDeliveryTargets(); for (auto handler : point->passiveGrabbers()) { // a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically if (Q_LIKELY(handler) && !eventDeliveryTargets.contains(handler)) { if (!sendFilteredPointerEvent(pointerEvent, handler->parentItem())) handler->handlePointerEvent(pointerEvent); - eventDeliveryTargets.append(handler); } } // If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order @@ -2266,9 +2265,11 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) qCDebug(DBG_TOUCH) << " - delivering" << event->asTouchEvent(); if (event->isPressEvent()) - deliverPressEvent(event); + deliverPressOrReleaseEvent(event); if (!event->allUpdatedPointsAccepted()) deliverUpdatedTouchPoints(event); + if (event->isReleaseEvent()) + deliverPressOrReleaseEvent(event, true); // Remove released points from itemForTouchPointId bool allReleased = true; @@ -2322,7 +2323,7 @@ void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve int pointCount = event->pointCount(); // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) - QVector &eventDeliveryTargets = event->device()->eventDeliveryTargets(); + const QVector &eventDeliveryTargets = event->device()->eventDeliveryTargets(); for (int i = 0; i < pointCount; ++i) { QQuickEventPoint *point = event->point(i); for (auto handler : point->passiveGrabbers()) { @@ -2330,7 +2331,6 @@ void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve if (sendFilteredPointerEvent(event, handler->parentItem())) return; handler->handlePointerEvent(event); - eventDeliveryTargets.append(handler); } } } @@ -2363,8 +2363,8 @@ void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve } } -// Deliver newly pressed touch points -bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event) +// Deliver an event containing newly pressed or released touch points +bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event, bool handlersOnly) { int pointCount = event->pointCount(); QVector targetItems; @@ -2372,6 +2372,8 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event) for (int i = 0; i < pointCount; ++i) { auto point = event->point(i); point->setAccepted(false); // because otherwise touchEventForItem will ignore it + if (point->grabberPointerHandler() && point->state() == QQuickEventPoint::Released) + point->setGrabberPointerHandler(nullptr, true); QVector targetItemsForPoint = pointerTargets(contentItem, point->scenePos(), !isTouchEvent, isTouchEvent); if (targetItems.count()) { targetItems = mergePointerTargets(targetItems, targetItemsForPoint); @@ -2380,14 +2382,14 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event) } } - if (allowChildEventFiltering) { + if (allowChildEventFiltering && !handlersOnly) { updateFilteringParentItems(targetItems); if (sendFilteredPointerEvent(event, nullptr)) return true; } for (QQuickItem *item: targetItems) { - deliverMatchingPointsToItem(item, event); + deliverMatchingPointsToItem(item, event, handlersOnly); if (event->allPointsAccepted()) break; } @@ -2395,7 +2397,7 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event) return event->allPointsAccepted(); } -void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent) +void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, bool handlersOnly) { Q_Q(QQuickWindow); QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); @@ -2403,7 +2405,15 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo // Let the Item's handlers (if any) have the event first. itemPrivate->handlePointerEvent(pointerEvent); - if (pointerEvent->allPointsAccepted()) + if (handlersOnly) + return; + if (pointerEvent->allPointsAccepted() && !pointerEvent->isReleaseEvent()) + return; + + // If all points are released and the item is not the grabber, it doesn't get the event. + // But if at least one point is still pressed, we might be in a potential gesture-takeover scenario. + if (pointerEvent->isReleaseEvent() && !pointerEvent->isUpdateEvent() + && !pointerEvent->exclusiveGrabbers().contains(item)) return; // TODO: unite this mouse point delivery with the synthetic mouse event below @@ -2464,11 +2474,11 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo if (eventAccepted) { // If the touch was accepted (regardless by whom or in what form), // update accepted new points. - bool isPress = pointerEvent->isPressEvent(); + bool isPressOrRelease = pointerEvent->isPressEvent() || pointerEvent->isReleaseEvent(); for (auto point: qAsConst(touchEvent->touchPoints())) { auto pointerEventPoint = ptEvent->pointById(point.id()); pointerEventPoint->setAccepted(); - if (isPress) + if (isPressOrRelease) pointerEventPoint->setGrabberItem(item); } } else { diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index f66809cc8a..fa39eca9a6 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -168,9 +168,9 @@ public: void deliverPointerEvent(QQuickPointerEvent *); void deliverTouchEvent(QQuickPointerTouchEvent *); bool deliverTouchCancelEvent(QTouchEvent *); - bool deliverPressEvent(QQuickPointerEvent *); + bool deliverPressOrReleaseEvent(QQuickPointerEvent *, bool handlersOnly = false); void deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event); - void deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent); + void deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, bool handlersOnly = false); QVector pointerTargets(QQuickItem *, const QPointF &, bool checkMouseButtons, bool checkAcceptsTouch) const; QVector mergePointerTargets(const QVector &list1, const QVector &list2) const; -- cgit v1.2.3