aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox')
-rw-r--r--src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox242
1 files changed, 242 insertions, 0 deletions
diff --git a/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox b/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox
new file mode 100644
index 0000000000..4a8b8eef43
--- /dev/null
+++ b/src/quick/doc/src/internal/ideal-pointer-event-delivery-single-drag.dox
@@ -0,0 +1,242 @@
+/*! \internal
+ \page qq-ideal-pointer-event-delivery-single-drag Dragging one DragHandler with one touchpoint
+ \tableofcontents
+
+ <a href="https://doc.qt.io/qt-6/qtquick-input-topic.html">Multi-touch</a>
+ is intended to be a strong feature in Qt Quick, so let's run this example
+ on a touchscreen:
+
+ \snippet pointerHandlers/pinchAndDragHandlers.qml entire
+
+ The intended behavior is that we have three Rectangles that can be dragged,
+ and you can alternatively perform a pinch gesture on the parent Rectangle
+ to scale and rotate it. The object instances involved look like this:
+
+ \dotfile pinchAndDragHandlers.dot "pinch and drag handlers"
+
+ (In these diagrams, ℚ is a shortcut for QQuick, to save space.
+ ResizeItemToWindow comes from \c qtdeclarative/tools/qml/ResizeItemToWindow.qml
+ which is a resource in the
+ <a href="https://doc.qt.io/qt-6/qtquick-qml-runtime.html">qml executable</a>
+ which wraps our top-level Rectangle into a Window.)
+
+ \section qq-ideal-pointer-event-delivery-press-draghandler-prep Touch press on a DragHandler: preparation
+
+ Let's start with the scenario that you attempt to drag one Rectangle
+ with one finger. A QTouchEvent arrives, it contains a single QEventPoint
+ representing the single finger, and we have to decide which items and
+ handlers we're going to visit.
+
+ \image html pinchAndDragHandlers-singlePressPrep.svg "touch press event delivery: preparation"
+
+ Since Qt 5.8 (change ccc5c54602821761a2f1a42c4bc473afd53439c9), we stopped
+ doing ad-hoc recursive delivery of touch and mouse events: we wanted to
+ ensure that delivery is deterministic (in spite of what user code may do to
+ the parent hierarchy during delivery), so we first build a list of Items to
+ visit, in QQuickDeliveryAgentPrivate::pointerTargets() (which is recursive
+ itself). This is somewhat expensive, but fortunately we only need to do
+ that when handling the event that begins a gesture, such as a press event.
+ A press event is a pointer event in which \e any QEventPoint has the
+ \c Pressed QEventPoint::state.
+
+ But how do we decide which items are relevant and need to be visited?
+ First, the QEventPoint must fall within the item's bounds, so we need to
+ call QQuickItem::mapFromScene() to localize from the scene (window)
+ coordinates to item coordinates, and then QQuickItem::contains() to check
+ whether it's inside. (QQuickItem::contains() is virtual so that QQuickShape
+ can be non-rectangular; also, any Item can have a
+ QQuickItem::containmentMask() to declare non-rectangular bounds.) Then, we
+ call QQuickItemPrivate::anyPointerHandlerWants() which calls
+ QQuickPointerHandler::wantsEventPoint() on each of the item's pointer
+ handlers: if any handler wants the eventpoint, we need to visit that item.
+ Otherwise, if QQuickItem::acceptTouchEvents() returns false, we do \e not
+ need to visit that item. Thus, pointerTargets() pre-visits all items in the
+ scene to build the list of potential targets.
+
+ \section qq-ideal-pointer-event-delivery-press-draghandler Touch press on a DragHandler: delivery to targets
+
+ After building the list, we are prepared to begin actual delivery of the
+ press event. QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent() loops
+ over \c targetItems and calls
+ QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(). (Parent-item
+ filtering could have intercepted it before that; but as stated, we're
+ neglecting that complication for now.)
+
+ \image html pinchAndDragHandlers-singlePressDelivery.svg "touch press event actual delivery"
+
+ For each item in \c targetItems, again we need to call mapFromScene() and
+ set QEventPoint::position() to item-local coordinates:
+ QQuickDeliveryAgentPrivate::localizePointerEvent() takes care of that.
+ We always let Pointer Handlers handle the event before the Item itself
+ (because this allows a handler to override or augment behavior that a C++
+ QQuickItem subclass has in its QQuickItem::touchEvent() function: another
+ complication that we're neglecting for now). The implementation for that
+ is in QQuickItemPrivate::handlePointerEvent(): it simply loops over any
+ handlers that are found in the list QQuickItemPrivate::ExtraData::pointerHandlers
+ and calls QQuickPointerHandler::handlePointerEvent() on each of those.
+ That's not a virtual function; but it calls wantsPointerEvent()
+ again, and then handlePointerEventImpl() which \e is virtual.
+
+ PinchHandler would get the event first if it was relevant, because
+ pointerTargets() was a preorder traversal, so parents come before children
+ in the \c targetItems list. But QQuickMultiPointHandler::wantsPointerEvent()
+ has already returned false, because QQuickMultiPointHandler::eligiblePoints()
+ has only found one point, and QQuickMultiPointHandler::minimumPointCount()
+ is 2 by default. (You might be wondering, what if the user presses a
+ second finger later to start the pinch gesture? We'll get to that below.)
+ So it can be skipped; next in \c targetItems should be a Rectangle whose
+ bounds contain QEventPoint::position() \e and that has a DragHandler.
+ By default, one point is enough for a DragHandler: its inherited
+ QQuickMultiPointHandler::wantsPointerEvent() has already returned \c true,
+ and QQuickMultiPointHandlerPrivate::currentPoints is already storing
+ information about the QEventPoints that it wants to handle.
+ (QEventPoint::id() is guaranteed to remain constant during one gesture:
+ once pressed, the same finger keeps manipulating the same QEventPoint.
+ In \c currentPoints[0], QQuickHandlerPoint::id() remembers it.)
+ So QQuickDragHandler::handlePointerEventImpl() can immediately iterate
+ \c currentPoints, find the QEventPoint that has the same ID; and then
+ it calls QQuickPointerHandler::setPassiveGrab(), because DragHandler
+ needs to monitor the position of that point. The drag gesture will not
+ begin until the point is dragged a distance in pixels greater than
+ QStyleHints::startDragDistance(), and it's not appropriate for DragHandler
+ to take the exclusive grab of that touchpoint until it's sure the user
+ really means to drag. (What if the same Rectangle also had a TapHandler?
+ The user could either drag, or tap without dragging; but at the time of the
+ press, it's ambiguous, so both handlers would need their own grabs, to express
+ interest in monitoring that touchpoint.) But without any grab, the handler
+ would not be visited again when the next event occurs: a touchpoint
+ movement or release.
+
+ We've omitted details about the meaning of QEventPoint::accepted() and
+ QEvent::accepted() flags. Pointer handlers need to take grabs explicitly:
+ that helps to remove ambiguity about the consequences of the \c accepted flags.
+
+ \section qq-ideal-pointer-event-delivery-move-draghandler Touch move on a DragHandler: delivery to grabber
+
+ \image html pinchAndDragHandlers-drag-one-rect.png "dragging one rectangle via touch"
+
+ So we're done handling the press; now let's try to start dragging.
+
+ Let's say the user's finger quickly moves far enough on the touchscreen to
+ generate a single QTouchEvent with a delta greater than the drag threshold.
+
+ Again, QGuiApplicationPrivate::processTouchEvent() handles the next QPA
+ touch event (QWindowSystemInterfacePrivate::TouchEvent). For each
+ touchpoint (for each finger being held down), it calls
+ QPointingDevicePrivate::pointById() to retrieve the
+ QPointingDevicePrivate::EventPointData that was stored previously when the
+ press event was delivered, and updates its stored state, including the
+ QEventPoint instance, to be current for this incoming move event.
+ QEventPoint::globalPressPosition() is not updated though: it continues to
+ hold the position at which the press occurred; therefore, any object that
+ ends up handing the moving point can check to see how far it moved since
+ press. Likewise, QEventPoint::pressTimestamp() holds the time at which it
+ was pressed.
+
+ \image html pinchAndDragHandlers-singleMoveDelivery.svg "touch move event delivery"
+
+ Another QTouchEvent instance is stack-allocated, with type
+ QEvent::TouchUpdate, in which \c point(0) is a QEventPoint (with state
+ QEventPoint::Updated) with QEventPoint::scenePosition() being the current
+ finger position in the window, while QEventPoint::scenePressPosition()
+ remembers where it was pressed during the previous press event. It's sent
+ to the application via QGuiApplication::sendSpontaneousEvent(), then to
+ ① QQuickWindow::event(), which dispatches to ② QQuickDeliveryAgent::event().
+ ③ QQuickDeliveryAgentPrivate::deliverPointerEvent() calls
+ ④ QQuickDeliveryAgentPrivate::deliverUpdatedPoints(), which (among other
+ things) iterates the QEventPoints, and for each of those, iterates the
+ passive grabbers in QPointingDevicePrivate::EventPointData::passiveGrabbers
+ and calls ⑤ QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(). It uses
+ ⑥ QQuickDeliveryAgentPrivate::localizePointerEvent() to ⑦ map
+ QEventPoint::position() to the passive-grabbing DragHandler's parent item's
+ coordinate system, and calls ⑧ QQuickPointerHandler::handlePointerEvent().
+ ⑨ QQuickMultiPointHandler::wantsPointerEvent() returns \c true because all
+ the same QQuickMultiPointHandlerPrivate::currentPoints still exist in this
+ QTouchEvent (with no points left over); so
+ ⑩ QQuickDragHandler::handlePointerEventImpl() is called. For each point, it
+ calculates the movement delta \c (scenePosition() - scenePressPosition());
+ ⑪ QQuickPointerHandlerPrivate::dragOverThreshold() checks whether it's moved
+ far enough to activate dragging. (If multiple points were being dragged,
+ handlePointerEventImpl() would also check whether they are all being
+ dragged in approximately the same direction.) It did move far enough, and
+ now DragHandler knows it should take responsibility for this gesture:
+ apparently the user is really trying to drag its parent item, the
+ Rectangle. It calls ⑫ QQuickMultiPointHandler::grabPoints() to try to take
+ the exclusive grab. Nothing is interfering with that, so the attempt
+ succeeds and returns \c true; therefore, it's ok to call
+ ⑬ QQuickPointerHandler::setActive(), which triggers
+ ⑭ QQuickDragHandler::onActiveChanged(), which updates some internal state
+ etc.; and then ⑮ emits the activeChanged() signal. In our example, the
+ Rectangle has a binding to ⑯ change color when the DragHandler becomes
+ active. And since by default, DragHandler's \c target is the same as its
+ \c parent, QQuickDragHandler::handlePointerEventImpl() ends with a call to
+ ⑰ QQuickMultiPointHandler::moveTarget(). That uses QMetaProperty::write()
+ to ⑱ change the Rectangle's \c x and \c y properties; the reason we do it
+ that way is in case property value interceptors (BoundaryRule or Behavior)
+ are in use. And so the Rectangle moves as far as the finger is dragged.
+
+ \section qq-ideal-pointer-event-delivery-release-draghandler Touch release: delivery to grabber
+
+ Now let's say there's a QPA event with
+ QWindowSystemInterface::TouchPoint::state being QEventPoint::Released.
+
+ \image html pinchAndDragHandlers-singleReleaseDelivery.svg "touch release event delivery"
+
+ It's processed like the touch move: the persistent QEventPoint is updated
+ with current values again, and another QTouchEvent instance is
+ stack-allocated, with type QEvent::TouchEnd, and ① sent to the window and ②
+ the delivery agent. When it gets to ③ QQuickDeliveryAgentPrivate::deliverPointerEvent(),
+ ④ deliverUpdatedPoints() is called first (same as for the move). As usual,
+ the event is not given to the item or its handlers until it's been ⑤
+ localized to the item's coordinate system.
+
+ ⑥ QQuickPointerHandler::handlePointerEvent() calls
+ ⑦ QQuickMultiPointHandler::wantsPointerEvent(), which is able to see that the
+ point it's tracking (in QQuickMultiPointHandlerPrivate::currentPoints) is
+ no longer eligible because it's been released; so it calls
+ ⑧ QQuickPointerHandler::setActive() with \c false immediately, which calls
+ ⑨ onActiveChanged() and emits the ⑩ activeChanged signal. (Thus our Rectangle
+ ⑪ changes its color again.) QQuickPointerHandler::handlePointerEvent() then
+ calls QPointerEvent::setExclusiveGrabber() with \c nullptr to give up its
+ exclusive grab. ⑫ QPointingDevice::grabChanged() is emitted.
+ QQuickDeliveryAgentPrivate::onGrabChanged() handles that signal, and calls
+ ⑬ QQuickDragHandler::onGrabChanged(), which has minimal consequences in this
+ case. (QQuickPointerHandler::onGrabChanged() calls
+ QQuickPointerHandler::setActive() with \c false again: it's a failsafe that
+ some other scenarios rely on.)
+
+ \section qq-ideal-pointer-event-delivery-touch-summary Touch delivery activity diagrams
+
+ So let's generalize the functionality we've covered so far.
+ As we'll see later, mouse events are treated a bit differently;
+ but ideally it would be the same: a mouse event is just a QPointerEvent
+ that comes from a different device, containing only one QEventPoint,
+ just like our single-finger touch event.
+
+ A begin (press) event goes to deliverPressOrReleaseEvent(), and then
+ if the QEventPoints weren't all accepted, it goes to deliverUpdatedPoints().
+
+ An update (move) event goes to deliverUpdatedPoints() only.
+
+ An end (release) event goes to deliverUpdatedPoints() and then
+ deliverPressOrReleaseEvent().
+
+ \startuml
+ !include ideal-pointer-event-delivery.puml
+ \enduml
+
+ QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() is called from
+ two places (deliverPressOrReleaseEvent() and deliverUpdatedPoints()), so
+ it's shown in a separate activity diagram:
+
+ \startuml
+ !include deliverMatchingPointsToItem.puml
+ \enduml
+
+ In conclusion, we've seen the details of touch event dispatching for one
+ short drag gesture to one DragHandler. In practice, Pointer Handlers are
+ still not the most common way to handle pointer events (even if we'd like
+ to end up there eventually): there are a lot of legacy QQuickItem
+ subclasses that do their event handling by overriding virtual functions
+ rather than by having handlers added. But let's save that for later.
+*/