1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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.
*/
|