![]() |
Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
|
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. In Two touchpoints dragging two DragHandlers we looked at delivery of two touchpoints to drag two Rectangles. Now let's look at using two touchpoints to activate a PinchHandler on a parent Rectangle.
As we saw, it's possible for the user to drag two DragHandlers with two fingers. It's also possible to execute the pinch gesture with the same two fingers in slightly different positions. Qt Quick acts like a distributed gesture recognizer when delivering multi-touch events: each pointer handler only tries to recognize one kind of gesture, but now let's look at a somewhat conflicting situation to see how the gesture gets disambiguated.
Let's modify the scenario from Two touchpoints dragging two DragHandlers : First let's change the code:
The user places one finger on one of the small Rectangles, and one on the larger outer rectangle. One DragHandler and the PinchHandler each take passive grabs. Now when the user begins dragging, it may happen that the DragHandler is activated first; but because of the modified grabPermissions we expect that the PinchHandler can take over the exclusive grab on both touchpoints, as soon as both fingers have moved farther than the drag threshold. How does this look in practice?
① A QTouchEvent arrives, holding 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 ③ deliverPointerEvent() which calls ④ deliverUpdatedPoints() which calls ⑤ deliverToPassiveGrabbers(). The two passive grabbers are the DragHandler and the PinchHandler. When ⑥ handlePointerEvent() is called on the PinchHandler, wantsPointerEvent() is called to precheck eligibility, and returns true; then we proceed to ⑧ handlePointerEventImpl(), where the PinchHandler figures out that it needs to take responsibility for handling the now-valid pinch gesture from now on, to the exclusion of other gestures. It calls ⑨ grabPoints(), which calls setExclusiveGrab(). That function needs to negotiate for permission first. It calls ⑩ canGrab() which calls approveGrabTransition() which actually checks the grabPermissions on the PinchHandler and also the relevant permissions on the previous grabber, if any. (In our case, the DragHandler's grabPermissions are relevant if DragHandler already had the exclusive grab. But if the grabber was an Item, it would check QQuickItem::keepTouchGrab().)
The permissions on both the old grabber and the proposed grabber allow the takeover, so the negotiation is a success, and the PinchHandler proceeds to call QTouchEvent::setExclusiveGrabber(). That calls QPointingDevice::setExclusiveGrabber() on the touchscreen device, which emits the grabChanged() signal. QQuickDeliveryAgent::onGrabChanged() is connected to that signal, and dispatches to the onGrabChanged() functions of two handlers that are involved in the grab transition: first, the DragHandler is told that it is losing its grab, so it calls setActive(false) on itself. (This is an asymmetric aspect of the architecture: it's useful that the base class implementation QQuickPointerHandler::onGrabChanged() deactivates any handler that is losing its grab, because so far it's an invariant property that it should always be done, so it might as well be consistently done in one place in the code. But handlers need to decide for themselves when to activate: they all have different conditions that must be satisfied to become active.) Then the PinchHandler is told that it is acquiring the exclusive grab.
QQuickPinchHandler::handlePointerEventImpl() calls setActive(true) on itself, then setActiveScale() and setActiveRotation(). Then it manipulates the target Item, which is our root Rectangle, according to what the pinch gesture is indicating that the user wants: it can be scaled, rotated and moved. On a touchscreen, the user is allowed to move the Rectangle by dragging two fingers while also scaling and rotating it; but on a macOS trackpad that's not allowed. In either case though, the Rectangle's position changes anyway, because we want the end result to be the same as if the QML code had set the scale, rotation, x and y properties separately. Changing the scale and \rotation usually requires changing the position to compensate.
In general, handlers should not call QQuickItem setters directly: they should use QMetaProperty. This is because QML allows the use of property-value interceptors such as Behavior and BoundaryRule. Those are bypassed and cannot react when C++ code calls setters directly.
There's also a completely independent way that a pinch gesture can be delivered: as a QNativeGestureEvent. Find out more about that here: Native pinch gesture on a touchpad