Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
Two touchpoints dragging two DragHandlers

In Dragging one DragHandler with one touchpoint we began with a detailed tour through the event delivery logic while one Rectangle is being dragged on a touchscreen with one finger. Let's now delve into parallel delivery: two fingers dragging two Rectangles, each with its own DragHandler, 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"
}
}
}
dragging two rectangles via touch

Dragging two DragHandlers with two fingers

The user presses two fingers on two of the small Rectangles simultaneously.

① A QTouchEvent arrives, it contains two QEventPoints, and we have to decide which items and handlers we're going to visit. QQuickWindow::event() dispatches to ② QQuickDeliveryAgent::event(). ③ QQuickDeliveryAgentPrivate::deliverPointerEvent() calls ④ QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(), which iterates QPointerEvent::points() and calls ⑤ QQuickDeliveryAgentPrivate::pointerTargets() to build a list of relevant items and handlers that should have a chance to handle that point. If the candidate is a pointer handler, ⑥⑦⑧⑨ QQuickPointerHandler::wantsEventPoint() is the way of asking the handler whether or not it's interested. In this case, for each of the two touchpoints, the PinchHandler and one DragHandler both return true, so both handlers' parent items get added to the targets list. Then it merges the lists, so that we end up with one list of all relevant items for all points in the multi-touch event.

Then for each item in the targets list, we call ⑩ QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(). Again we need to call mapFromScene() and set QEventPoint::position() to item-local coordinates, then ⑪ QQuickItemPrivate::handlePointerEvent() loops over any handlers that are found in the list QQuickItemPrivate::ExtraData::pointerHandlers and calls QQuickPointerHandler::handlePointerEvent() on each of those. Note that the event has not been split up: each DragHandler sees both touchpoints. So it calls its own wantsPointerEvent(), which (according to its name) should decide whether it wants any part of the multi-touch event; but the parent class of DragHandler is QQuickMultiPointHandler. QQuickMultiPointHandler::wantsPointerEvent() iterates the points again, calls wantsEventPoint() again for each of those, and builds a list of candidatePoints. In this case, each DragHandler is only interested in one point: the one whose position is inside the Rectangle (DragHandler's parent item). That point goes into candidatePoints. For each of those, a QQuickHandlerPoint instance in QQuickMultiPointHandlerPrivate::currentPoints is initialized to remember that this DragHandler is handling this point.

Then we get to the virtual ⑫ handlePointerEventImpl() function. QQuickMultiPointHandler::handlePointerEventImpl() updates the QML-facing centroid property, another QQuickHandlerPoint instance; and QQuickDragHandler::handlePointerEventImpl() calls QQuickPointerHandlerPrivate::dragOverThreshold() to check whether the QEventPoint has been dragged past the dragThreshold. As long as it has not been dragged that far, DragHandler is only monitoring to see what the user will do, so it retains a ⑬ passive grab.

The PinchHandler also "wants" and passively grabs both points: they are both inside its parent Rectangle.

Handling of subsequent move events proceeds in a similar way as the single-point case : QQuickDeliveryAgentPrivate::deliverPointerEvent() calls QQuickDeliveryAgentPrivate::deliverUpdatedPoints(), which iterates the QEventPoints, and for each of those, iterates the passive grabbers in QPointingDevicePrivate::EventPointData::passiveGrabbers (in the order that they are stored, the same order that they added themselves as passive grabbers) and calls QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(). After QQuickDeliveryAgentPrivate::localizePointerEvent(), it calls QQuickPointerHandler::handlePointerEvent(). QQuickMultiPointHandler::wantsPointerEvent() returns true because the point in QQuickMultiPointHandlerPrivate::currentPoints still exists in this QTouchEvent; so QQuickDragHandler::handlePointerEventImpl() is called. For each point, it calculates the movement delta (scenePosition() - scenePressPosition()); and QQuickPointerHandlerPrivate::dragOverThreshold() checks whether it's moved far enough to activate dragging. When the user begins the drag gesture by dragging a finger far enough, the appropriate DragHandler will take the exclusive grab by calling QQuickMultiPointHandler::grabPoints(). This is not a take-over scenario, so grabPoints() succeeds; therefore, it's ok to call QQuickPointerHandler::setActive(). 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. And so each Rectangle moves as far as the finger is dragged.

deliverUpdatedPoints() also visits the PinchHandler. Since at least one of the DragHandlers got a chance to take the exclusive grab before delivery to the PinchHandler, and we have declared

grabPermissions: PointerHandler.TakeOverForbidden

the PinchHandler does not have permission to "take over" the exclusive grab from an existing handler. If the qt.quick.handler.grab logging category is enabled, you will see a message like

qt.quick.handler.grab QQuickPointerHandler::approveGrabTransition - point 1 permission "TakeOverForbidden" : QQuickPinchHandler(0x...) denied from QQuickDragHandler(0x..., name = "dh1") to QQuickPinchHandler(0x...)
virtual bool approveGrabTransition(QPointerEvent *event, const QEventPoint &point, QObject *proposedGrabber)
Check this handler's rules to see if \l proposedGrabber will be allowed to take the exclusive grab.
EGLImageKHR EGLint * name
GLint GLint GLint GLint GLint x
[0]

However if you drag two fingers on the root Rectangle, outside the smaller Rectangles that have DragHandlers, pinching works, because the PinchHandler is the only candidate to handle those points. This is explained in the next chapter: Pinch on a touchscreen