Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
Dragging one DragHandler with one touchpoint

Multi-touch is intended to be a strong feature in Qt Quick, so let's run this example on a touchscreen:

import QtQuick
Rectangle {
id: root
width: 400
height: 400
color: ph.active ? "aquamarine" : "beige"
PinchHandler {
id: ph
grabPermissions: PointerHandler.TakeOverForbidden
}
Rectangle {
objectName: "rect1"
x: 50
width: 100
height: 100
color: dh1.active ? "tomato" : "wheat"
DragHandler {
id: dh1
objectName: "dh1"
}
}
Rectangle {
objectName: "rect2"
x: 250
width: 100
height: 100
color: dh2.active ? "tomato" : "lightsteelblue"
DragHandler {
id: dh2
objectName: "dh2"
}
}
Rectangle {
objectName: "rect3"
x: 150
y: 150
width: 100
height: 100
color: dh3.active ? "tomato" : "darksalmon"
DragHandler {
id: dh3
objectName: "dh3"
}
}
}

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:

pinch and drag handlers

(In these diagrams, ℚ is a shortcut for QQuick, to save space. ResizeItemToWindow comes from qtdeclarative/tools/qml/ResizeItemToWindow.qml which is a resource in the qml executable which wraps our top-level Rectangle into a Window.)

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.

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 any QEventPoint has the 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 not need to visit that item. Thus, pointerTargets() pre-visits all items in the scene to build the list of potential targets.

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 targetItems and calls QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(). (Parent-item filtering could have intercepted it before that; but as stated, we're neglecting that complication for now.)

touch press event actual delivery

For each item in 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 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 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 targetItems should be a Rectangle whose bounds contain QEventPoint::position() and that has a DragHandler. By default, one point is enough for a DragHandler: its inherited QQuickMultiPointHandler::wantsPointerEvent() has already returned 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 currentPoints[0], QQuickHandlerPoint::id() remembers it.) So QQuickDragHandler::handlePointerEventImpl() can immediately iterate 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 accepted flags.

Touch move on a DragHandler: delivery to grabber

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.

touch move event delivery

Another QTouchEvent instance is stack-allocated, with type QEvent::TouchUpdate, in which 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 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 (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 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 target is the same as its parent, QQuickDragHandler::handlePointerEventImpl() ends with a call to ⑰ QQuickMultiPointHandler::moveTarget(). That uses QMetaProperty::write() to ⑱ change the Rectangle's x and 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.

Touch release: delivery to grabber

Now let's say there's a QPA event with QWindowSystemInterface::TouchPoint::state being QEventPoint::Released.

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 false immediately, which calls ⑨ onActiveChanged() and emits the ⑩ activeChanged signal. (Thus our Rectangle ⑪ changes its color again.) QQuickPointerHandler::handlePointerEvent() then calls QPointerEvent::setExclusiveGrabber() with 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 false again: it's a failsafe that some other scenarios rely on.)

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

QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() is called from two places (deliverPressOrReleaseEvent() and deliverUpdatedPoints()), so it's shown in a separate activity diagram:

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.

In the meantime, to go further with how Pointer Handlers work (but with less-daunting amounts of detail), you might want to look at Two touchpoints dragging two DragHandlers and Pinch on a touchscreen Alternatively you could compare to mouse dragging: Dragging one DragHandler with the mouse