1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickmultipointtoucharea_p.h" |
5 | #include <QtQuick/qquickwindow.h> |
6 | #include <private/qsgadaptationlayer_p.h> |
7 | #include <private/qquickitem_p.h> |
8 | #include <private/qquickwindow_p.h> |
9 | #include <private/qguiapplication_p.h> |
10 | #include <QtGui/private/qevent_p.h> |
11 | #include <QtGui/private/qeventpoint_p.h> |
12 | #include <QtGui/private/qpointingdevice_p.h> |
13 | #include <QEvent> |
14 | #include <QMouseEvent> |
15 | #include <QDebug> |
16 | #include <qpa/qplatformnativeinterface.h> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | DEFINE_BOOL_CONFIG_OPTION(qmlMptaVisualTouchDebugging, QML_VISUAL_TOUCH_DEBUGGING) |
21 | |
22 | /*! |
23 | \qmltype TouchPoint |
24 | \instantiates QQuickTouchPoint |
25 | \inqmlmodule QtQuick |
26 | \ingroup qtquick-input-events |
27 | \brief Describes a touch point in a MultiPointTouchArea. |
28 | |
29 | The TouchPoint type contains information about a touch point, such as the current |
30 | position, pressure, and area. |
31 | |
32 | \image touchpoint-metrics.png |
33 | */ |
34 | |
35 | /*! |
36 | \qmlproperty int QtQuick::TouchPoint::pointId |
37 | |
38 | This property holds the point id of the touch point. |
39 | |
40 | Each touch point within a MultiPointTouchArea will have a unique id. |
41 | */ |
42 | void QQuickTouchPoint::setPointId(int id) |
43 | { |
44 | if (_id == id) |
45 | return; |
46 | _id = id; |
47 | emit pointIdChanged(); |
48 | } |
49 | |
50 | /*! |
51 | \qmlproperty real QtQuick::TouchPoint::x |
52 | \qmlproperty real QtQuick::TouchPoint::y |
53 | |
54 | These properties hold the current position of the touch point. |
55 | */ |
56 | |
57 | void QQuickTouchPoint::setPosition(QPointF p) |
58 | { |
59 | bool xch = (_x != p.x()); |
60 | bool ych = (_y != p.y()); |
61 | if (!xch && !ych) |
62 | return; |
63 | _x = p.x(); |
64 | _y = p.y(); |
65 | if (xch) |
66 | emit xChanged(); |
67 | if (ych) |
68 | emit yChanged(); |
69 | } |
70 | |
71 | /*! |
72 | \qmlproperty size QtQuick::TouchPoint::ellipseDiameters |
73 | \since 5.9 |
74 | |
75 | This property holds the major and minor axes of the ellipse representing |
76 | the covered area of the touch point. |
77 | */ |
78 | void QQuickTouchPoint::setEllipseDiameters(const QSizeF &d) |
79 | { |
80 | if (_ellipseDiameters == d) |
81 | return; |
82 | _ellipseDiameters = d; |
83 | emit ellipseDiametersChanged(); |
84 | } |
85 | |
86 | /*! |
87 | \qmlproperty real QtQuick::TouchPoint::pressure |
88 | \qmlproperty vector2d QtQuick::TouchPoint::velocity |
89 | |
90 | These properties hold additional information about the current state of the touch point. |
91 | |
92 | \list |
93 | \li \c pressure is a value in the range of 0.0 to 1.0. |
94 | \li \c velocity is a vector with magnitude reported in pixels per second. |
95 | \endlist |
96 | |
97 | Not all touch devices support velocity. If velocity is not supported, it will be reported |
98 | as 0,0. |
99 | */ |
100 | void QQuickTouchPoint::setPressure(qreal pressure) |
101 | { |
102 | if (_pressure == pressure) |
103 | return; |
104 | _pressure = pressure; |
105 | emit pressureChanged(); |
106 | } |
107 | |
108 | /*! |
109 | \qmlproperty real QtQuick::TouchPoint::rotation |
110 | \since 5.9 |
111 | |
112 | This property holds the angular orientation of this touch point. The return |
113 | value is in degrees, where zero (the default) indicates the finger or token |
114 | is pointing upwards, a negative angle means it's rotated to the left, and a |
115 | positive angle means it's rotated to the right. Most touchscreens do not |
116 | detect rotation, so zero is the most common value. |
117 | |
118 | \sa QEventPoint::rotation() |
119 | */ |
120 | void QQuickTouchPoint::setRotation(qreal r) |
121 | { |
122 | if (_rotation == r) |
123 | return; |
124 | _rotation = r; |
125 | emit rotationChanged(); |
126 | } |
127 | |
128 | void QQuickTouchPoint::setVelocity(const QVector2D &velocity) |
129 | { |
130 | if (_velocity == velocity) |
131 | return; |
132 | _velocity = velocity; |
133 | emit velocityChanged(); |
134 | } |
135 | |
136 | /*! |
137 | \deprecated |
138 | \qmlproperty rectangle QtQuick::TouchPoint::area |
139 | |
140 | A rectangle covering the area of the touch point, centered on the current |
141 | position of the touch point. |
142 | |
143 | It is deprecated because a touch point is more correctly modeled as an ellipse, |
144 | whereas this rectangle represents the outer bounds of the ellipse after \l rotation. |
145 | */ |
146 | void QQuickTouchPoint::setArea(const QRectF &area) |
147 | { |
148 | if (_area == area) |
149 | return; |
150 | _area = area; |
151 | emit areaChanged(); |
152 | } |
153 | |
154 | /*! |
155 | \qmlproperty bool QtQuick::TouchPoint::pressed |
156 | |
157 | This property holds whether the touch point is currently pressed. |
158 | */ |
159 | void QQuickTouchPoint::setPressed(bool pressed) |
160 | { |
161 | if (_pressed == pressed) |
162 | return; |
163 | _pressed = pressed; |
164 | emit pressedChanged(); |
165 | } |
166 | |
167 | /*! |
168 | \qmlproperty real QtQuick::TouchPoint::startX |
169 | \qmlproperty real QtQuick::TouchPoint::startY |
170 | |
171 | These properties hold the starting position of the touch point. |
172 | */ |
173 | |
174 | void QQuickTouchPoint::setStartX(qreal startX) |
175 | { |
176 | if (_startX == startX) |
177 | return; |
178 | _startX = startX; |
179 | emit startXChanged(); |
180 | } |
181 | |
182 | void QQuickTouchPoint::setStartY(qreal startY) |
183 | { |
184 | if (_startY == startY) |
185 | return; |
186 | _startY = startY; |
187 | emit startYChanged(); |
188 | } |
189 | |
190 | /*! |
191 | \qmlproperty real QtQuick::TouchPoint::previousX |
192 | \qmlproperty real QtQuick::TouchPoint::previousY |
193 | |
194 | These properties hold the previous position of the touch point. |
195 | */ |
196 | void QQuickTouchPoint::setPreviousX(qreal previousX) |
197 | { |
198 | if (_previousX == previousX) |
199 | return; |
200 | _previousX = previousX; |
201 | emit previousXChanged(); |
202 | } |
203 | |
204 | void QQuickTouchPoint::setPreviousY(qreal previousY) |
205 | { |
206 | if (_previousY == previousY) |
207 | return; |
208 | _previousY = previousY; |
209 | emit previousYChanged(); |
210 | } |
211 | |
212 | /*! |
213 | \qmlproperty real QtQuick::TouchPoint::sceneX |
214 | \qmlproperty real QtQuick::TouchPoint::sceneY |
215 | |
216 | These properties hold the current position of the touch point in scene coordinates. |
217 | */ |
218 | |
219 | void QQuickTouchPoint::setSceneX(qreal sceneX) |
220 | { |
221 | if (_sceneX == sceneX) |
222 | return; |
223 | _sceneX = sceneX; |
224 | emit sceneXChanged(); |
225 | } |
226 | |
227 | void QQuickTouchPoint::setSceneY(qreal sceneY) |
228 | { |
229 | if (_sceneY == sceneY) |
230 | return; |
231 | _sceneY = sceneY; |
232 | emit sceneYChanged(); |
233 | } |
234 | |
235 | /*! |
236 | \qmlproperty pointingDeviceUniqueId QtQuick::TouchPoint::uniqueId |
237 | \since 5.9 |
238 | |
239 | This property holds the unique ID of the touch point or token. |
240 | |
241 | It is normally empty, because touchscreens cannot uniquely identify fingers. |
242 | But when it is set, it is expected to uniquely identify a specific token |
243 | (fiducial object). |
244 | |
245 | Interpreting the contents of this ID requires knowledge of the hardware and |
246 | drivers in use (e.g. various TUIO-based touch surfaces). |
247 | */ |
248 | void QQuickTouchPoint::setUniqueId(const QPointingDeviceUniqueId &id) |
249 | { |
250 | _uniqueId = id; |
251 | emit uniqueIdChanged(); |
252 | } |
253 | |
254 | |
255 | /*! |
256 | \qmltype GestureEvent |
257 | \instantiates QQuickGrabGestureEvent |
258 | \inqmlmodule QtQuick |
259 | \ingroup qtquick-input-events |
260 | \brief The parameter given with the gestureStarted signal. |
261 | |
262 | The GestureEvent object has the current touch points, which you may choose |
263 | to interpret as a gesture, and an invokable method to grab the involved |
264 | points exclusively. |
265 | */ |
266 | |
267 | /*! |
268 | \qmlproperty real QtQuick::GestureEvent::dragThreshold |
269 | |
270 | This property holds the system setting for the distance a finger must move |
271 | before it is interpreted as a drag. It comes from |
272 | QStyleHints::startDragDistance(). |
273 | */ |
274 | |
275 | /*! |
276 | \qmlproperty list<TouchPoint> QtQuick::GestureEvent::touchPoints |
277 | |
278 | This property holds the set of current touch points. |
279 | */ |
280 | |
281 | /*! |
282 | \qmlmethod QtQuick::GestureEvent::grab() |
283 | |
284 | Acquires an exclusive grab of the mouse and all the \l touchPoints, and |
285 | calls \l {QQuickItem::setKeepTouchGrab()}{setKeepTouchGrab()} and |
286 | \l {QQuickItem::setKeepMouseGrab()}{setKeepMouseGrab()} so that any |
287 | parent Item that \l {QQuickItem::filtersChildMouseEvents()}{filters} its |
288 | children's events will not be allowed to take over the grabs. |
289 | */ |
290 | |
291 | /*! |
292 | \qmltype MultiPointTouchArea |
293 | \instantiates QQuickMultiPointTouchArea |
294 | \inqmlmodule QtQuick |
295 | \inherits Item |
296 | \ingroup qtquick-input |
297 | \brief Enables handling of multiple touch points. |
298 | |
299 | |
300 | A MultiPointTouchArea is an invisible item that is used to track multiple touch points. |
301 | |
302 | The \l Item::enabled property is used to enable and disable touch handling. When disabled, |
303 | the touch area becomes transparent to mouse and touch events. |
304 | |
305 | By default, the mouse will be handled the same way as a single touch point, |
306 | and items under the touch area will not receive mouse events because the |
307 | touch area is handling them. But if the \l mouseEnabled property is set to |
308 | false, it becomes transparent to mouse events so that another |
309 | mouse-sensitive Item (such as a MouseArea) can be used to handle mouse |
310 | interaction separately. |
311 | |
312 | MultiPointTouchArea can be used in two ways: |
313 | |
314 | \list |
315 | \li setting \c touchPoints to provide touch point objects with properties that can be bound to |
316 | \li using the onTouchUpdated or onPressed, onUpdated and onReleased handlers |
317 | \endlist |
318 | |
319 | While a MultiPointTouchArea \e can take exclusive ownership of certain touch points, it is also possible to have |
320 | multiple MultiPointTouchAreas active at the same time, each operating on a different set of touch points. |
321 | |
322 | \sa TouchPoint |
323 | */ |
324 | |
325 | /*! |
326 | \qmlsignal QtQuick::MultiPointTouchArea::pressed(list<TouchPoint> touchPoints) |
327 | |
328 | This signal is emitted when new touch points are added. \a touchPoints is a list of these new points. |
329 | |
330 | If minimumTouchPoints is set to a value greater than one, this signal will not be emitted until the minimum number |
331 | of required touch points has been reached. |
332 | |
333 | \note If you use the \c touchPoints argument in your signal handler code, |
334 | it's best to rename it in your formal parameter to avoid confusion with the |
335 | \c touchPoints property (see \l{QML Coding Conventions}): |
336 | \qml |
337 | onPressed: (points) => console.log("pressed", points.length) |
338 | \endqml |
339 | */ |
340 | |
341 | /*! |
342 | \qmlsignal QtQuick::MultiPointTouchArea::updated(list<TouchPoint> touchPoints) |
343 | |
344 | This signal is emitted when existing touch points are updated. \a touchPoints is a list of these updated points. |
345 | |
346 | \note If you use the \c touchPoints argument in your signal handler code, |
347 | it's best to rename it in your formal parameter to avoid confusion with the |
348 | \c touchPoints property (see \l{QML Coding Conventions}): |
349 | \qml |
350 | onUpdated: (points) => console.log("updated", points.length) |
351 | \endqml |
352 | */ |
353 | |
354 | /*! |
355 | \qmlsignal QtQuick::MultiPointTouchArea::released(list<TouchPoint> touchPoints) |
356 | |
357 | This signal is emitted when existing touch points are removed. \a touchPoints is a list of these removed points. |
358 | |
359 | \note If you use the \c touchPoints argument in your signal handler code, |
360 | it's best to rename it in your formal parameter to avoid confusion with the |
361 | \c touchPoints property (see \l{QML Coding Conventions}): |
362 | \qml |
363 | onReleased: (points) => console.log("released", points.length) |
364 | \endqml |
365 | */ |
366 | |
367 | /*! |
368 | \qmlsignal QtQuick::MultiPointTouchArea::canceled(list<TouchPoint> touchPoints) |
369 | |
370 | This signal is emitted when new touch events have been canceled because another item stole the touch event handling. |
371 | |
372 | This signal is for advanced use: it is useful when there is more than one MultiPointTouchArea |
373 | that is handling input, or when there is a MultiPointTouchArea inside a \l Flickable. In the latter |
374 | case, if you execute some logic in the \c onPressed signal handler and then start dragging, the |
375 | \l Flickable may steal the touch handling from the MultiPointTouchArea. In these cases, to reset |
376 | the logic when the MultiPointTouchArea has lost the touch handling to the \l Flickable, |
377 | \c canceled should be handled in addition to \l released. |
378 | |
379 | \a touchPoints is the list of canceled points. |
380 | |
381 | \note If you use the \c touchPoints argument in your signal handler code, |
382 | it's best to rename it in your formal parameter to avoid confusion with the |
383 | \c touchPoints property (see \l{QML Coding Conventions}): |
384 | \qml |
385 | onCanceled: (points) => console.log("canceled", points.length) |
386 | \endqml |
387 | */ |
388 | |
389 | // TODO Qt 7: remove the notes above about the signal touchPoints arguments |
390 | |
391 | /*! |
392 | \qmlsignal QtQuick::MultiPointTouchArea::gestureStarted(GestureEvent gesture) |
393 | |
394 | This signal is emitted when the global drag threshold has been reached. |
395 | |
396 | This signal is typically used when a MultiPointTouchArea has been nested in a Flickable or another MultiPointTouchArea. |
397 | When the threshold has been reached and the signal is handled, you can determine whether or not the touch |
398 | area should grab the current touch points. By default they will not be grabbed; to grab them call \c gesture.grab(). If the |
399 | gesture is not grabbed, the nesting Flickable, for example, would also have an opportunity to grab. |
400 | |
401 | The \a gesture object also includes information on the current set of \c touchPoints and the \c dragThreshold. |
402 | */ |
403 | |
404 | /*! |
405 | \qmlsignal QtQuick::MultiPointTouchArea::touchUpdated(list<TouchPoint> touchPoints) |
406 | |
407 | This signal is emitted when the touch points handled by the MultiPointTouchArea change. This includes adding new touch points, |
408 | removing or canceling previous touch points, as well as updating current touch point data. \a touchPoints is the list of all current touch |
409 | points. |
410 | */ |
411 | |
412 | /*! |
413 | \qmlproperty list<TouchPoint> QtQuick::MultiPointTouchArea::touchPoints |
414 | |
415 | This property holds a set of user-defined touch point objects that can be bound to. |
416 | |
417 | If mouseEnabled is true (the default) and the left mouse button is pressed |
418 | while the mouse is over the touch area, the current mouse position will be |
419 | one of these touch points. |
420 | |
421 | In the following example, we have two small rectangles that follow our touch points. |
422 | |
423 | \snippet qml/multipointtoucharea/multipointtoucharea.qml 0 |
424 | |
425 | By default this property holds an empty list. |
426 | |
427 | \sa TouchPoint |
428 | */ |
429 | |
430 | QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent) |
431 | : QQuickItem(parent), |
432 | _minimumTouchPoints(0), |
433 | _maximumTouchPoints(INT_MAX), |
434 | _touchMouseDevice(nullptr), |
435 | _stealMouse(false), |
436 | _mouseEnabled(true) |
437 | { |
438 | setAcceptedMouseButtons(Qt::LeftButton); |
439 | setFiltersChildMouseEvents(true); |
440 | if (qmlMptaVisualTouchDebugging()) { |
441 | setFlag(flag: QQuickItem::ItemHasContents); |
442 | } |
443 | setAcceptTouchEvents(true); |
444 | #ifdef Q_OS_MACOS |
445 | setAcceptHoverEvents(true); // needed to enable touch events on mouse hover. |
446 | #endif |
447 | } |
448 | |
449 | QQuickMultiPointTouchArea::~QQuickMultiPointTouchArea() |
450 | { |
451 | clearTouchLists(); |
452 | for (QObject *obj : std::as_const(t&: _touchPoints)) { |
453 | QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj); |
454 | if (!dtp->isQmlDefined()) |
455 | delete dtp; |
456 | } |
457 | } |
458 | |
459 | /*! |
460 | \qmlproperty int QtQuick::MultiPointTouchArea::minimumTouchPoints |
461 | \qmlproperty int QtQuick::MultiPointTouchArea::maximumTouchPoints |
462 | |
463 | These properties hold the range of touch points to be handled by the touch area. |
464 | |
465 | These are convenience that allow you to, for example, have nested MultiPointTouchAreas, |
466 | one handling two finger touches, and another handling three finger touches. |
467 | |
468 | By default, all touch points within the touch area are handled. |
469 | |
470 | If mouseEnabled is true, the mouse acts as a touch point, so it is also |
471 | subject to these constraints: for example if maximumTouchPoints is two, you |
472 | can use the mouse as one touch point and a finger as another touch point |
473 | for a total of two. |
474 | */ |
475 | |
476 | int QQuickMultiPointTouchArea::minimumTouchPoints() const |
477 | { |
478 | return _minimumTouchPoints; |
479 | } |
480 | |
481 | void QQuickMultiPointTouchArea::setMinimumTouchPoints(int num) |
482 | { |
483 | if (_minimumTouchPoints == num) |
484 | return; |
485 | _minimumTouchPoints = num; |
486 | emit minimumTouchPointsChanged(); |
487 | } |
488 | |
489 | int QQuickMultiPointTouchArea::maximumTouchPoints() const |
490 | { |
491 | return _maximumTouchPoints; |
492 | } |
493 | |
494 | void QQuickMultiPointTouchArea::setMaximumTouchPoints(int num) |
495 | { |
496 | if (_maximumTouchPoints == num) |
497 | return; |
498 | _maximumTouchPoints = num; |
499 | emit maximumTouchPointsChanged(); |
500 | } |
501 | |
502 | /*! |
503 | \qmlproperty bool QtQuick::MultiPointTouchArea::mouseEnabled |
504 | |
505 | This property controls whether the MultiPointTouchArea will handle mouse |
506 | events too. If it is true (the default), the touch area will treat the |
507 | mouse the same as a single touch point; if it is false, the touch area will |
508 | ignore mouse events and allow them to "pass through" so that they can be |
509 | handled by other items underneath. |
510 | */ |
511 | void QQuickMultiPointTouchArea::setMouseEnabled(bool arg) |
512 | { |
513 | if (_mouseEnabled != arg) { |
514 | _mouseEnabled = arg; |
515 | if (_mouseTouchPoint && !arg) |
516 | _mouseTouchPoint = nullptr; |
517 | emit mouseEnabledChanged(); |
518 | } |
519 | } |
520 | |
521 | void QQuickMultiPointTouchArea::touchEvent(QTouchEvent *event) |
522 | { |
523 | switch (event->type()) { |
524 | case QEvent::TouchBegin: |
525 | case QEvent::TouchUpdate: |
526 | case QEvent::TouchEnd: { |
527 | //if e.g. a parent Flickable has the mouse grab, don't process the touch events |
528 | QQuickWindow *c = window(); |
529 | QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
530 | if (grabber && grabber != this && grabber->keepMouseGrab() && grabber->isEnabled()) { |
531 | QQuickItem *item = this; |
532 | while ((item = item->parentItem())) { |
533 | if (item == grabber) |
534 | return; |
535 | } |
536 | } |
537 | updateTouchData(event); |
538 | if (event->type() == QEvent::TouchEnd) |
539 | ungrab(normalRelease: true); |
540 | break; |
541 | } |
542 | case QEvent::TouchCancel: |
543 | ungrab(); |
544 | break; |
545 | default: |
546 | QQuickItem::touchEvent(event); |
547 | break; |
548 | } |
549 | } |
550 | |
551 | void QQuickMultiPointTouchArea::grabGesture(QPointingDevice *dev) |
552 | { |
553 | _stealMouse = true; |
554 | |
555 | grabMouse(); |
556 | setKeepMouseGrab(true); |
557 | |
558 | QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(q: dev); |
559 | for (auto it = _touchPoints.keyBegin(), end = _touchPoints.keyEnd(); it != end; ++it) { |
560 | if (*it != -1) // -1 might be the mouse-point, but we already grabbed the mouse above. |
561 | if (auto pt = devPriv->queryPointById(id: *it)) |
562 | pt->exclusiveGrabber = this; |
563 | } |
564 | setKeepTouchGrab(true); |
565 | } |
566 | |
567 | void QQuickMultiPointTouchArea::updateTouchData(QEvent *event, RemapEventPoints remap) |
568 | { |
569 | bool ended = false; |
570 | bool moved = false; |
571 | bool started = false; |
572 | |
573 | clearTouchLists(); |
574 | QList<QEventPoint> touchPoints; |
575 | bool touchPointsFromEvent = false; |
576 | QPointingDevice *dev = nullptr; |
577 | |
578 | switch (event->type()) { |
579 | case QEvent::TouchBegin: |
580 | case QEvent::TouchUpdate: |
581 | case QEvent::TouchEnd: { |
582 | QTouchEvent* te = static_cast<QTouchEvent*>(event); |
583 | touchPoints = te->points(); |
584 | touchPointsFromEvent = true; |
585 | dev = const_cast<QPointingDevice *>(te->pointingDevice()); |
586 | break; |
587 | } |
588 | case QEvent::MouseButtonPress: { |
589 | auto da = QQuickItemPrivate::get(item: this)->deliveryAgentPrivate(); |
590 | _mouseQpaTouchPoint = QEventPoint(da->touchMouseId); |
591 | _touchMouseDevice = da->touchMouseDevice; |
592 | Q_FALLTHROUGH(); |
593 | } |
594 | case QEvent::MouseMove: |
595 | case QEvent::MouseButtonRelease: { |
596 | QMouseEvent *me = static_cast<QMouseEvent*>(event); |
597 | _mouseQpaTouchPoint = me->points().first(); |
598 | dev = const_cast<QPointingDevice *>(me->pointingDevice()); |
599 | if (event->type() == QEvent::MouseButtonPress) { |
600 | addTouchPoint(e: me); |
601 | started = true; |
602 | } |
603 | touchPoints << _mouseQpaTouchPoint; |
604 | break; |
605 | } |
606 | default: |
607 | qWarning(msg: "updateTouchData: unhandled event type %d" , event->type()); |
608 | break; |
609 | } |
610 | |
611 | int numTouchPoints = touchPoints.size(); |
612 | //always remove released touches, and make sure we handle all releases before adds. |
613 | for (const QEventPoint &p : std::as_const(t&: touchPoints)) { |
614 | QEventPoint::State touchPointState = p.state(); |
615 | int id = p.id(); |
616 | if (touchPointState & QEventPoint::State::Released) { |
617 | QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(key: id)); |
618 | if (!dtp) |
619 | continue; |
620 | updateTouchPoint(dtp, &p); |
621 | dtp->setPressed(false); |
622 | _releasedTouchPoints.append(t: dtp); |
623 | _touchPoints.remove(key: id); |
624 | ended = true; |
625 | } |
626 | } |
627 | if (numTouchPoints >= _minimumTouchPoints && numTouchPoints <= _maximumTouchPoints) { |
628 | for (QEventPoint &p : touchPoints) { |
629 | QPointF oldPos = p.position(); |
630 | auto transformBack = qScopeGuard(f: [&] { QMutableEventPoint::setPosition(p, arg: oldPos); }); |
631 | if (touchPointsFromEvent && remap == RemapEventPoints::ToLocal) |
632 | QMutableEventPoint::setPosition(p, arg: mapFromScene(point: p.scenePosition())); |
633 | QEventPoint::State touchPointState = p.state(); |
634 | int id = p.id(); |
635 | if (touchPointState & QEventPoint::State::Released) { |
636 | //handled above |
637 | } else if (!_touchPoints.contains(key: id)) { //could be pressed, moved, or stationary |
638 | // (we may have just obtained enough points to start tracking them -- in that case moved or stationary count as newly pressed) |
639 | addTouchPoint(p: &p); |
640 | started = true; |
641 | } else if ((touchPointState & QEventPoint::State::Updated) || |
642 | (touchPointState & QEventPoint::State::Stationary)) { |
643 | // React to a stationary point as if the point moved. (QTBUG-77142) |
644 | QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(key: id)); |
645 | Q_ASSERT(dtp); |
646 | _movedTouchPoints.append(t: dtp); |
647 | updateTouchPoint(dtp,&p); |
648 | moved = true; |
649 | } else { |
650 | QQuickTouchPoint* dtp = static_cast<QQuickTouchPoint*>(_touchPoints.value(key: id)); |
651 | Q_ASSERT(dtp); |
652 | updateTouchPoint(dtp,&p); |
653 | } |
654 | } |
655 | |
656 | //see if we should be grabbing the gesture |
657 | if (!_stealMouse /* !ignoring gesture*/) { |
658 | bool offerGrab = false; |
659 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
660 | for (const QEventPoint &p : std::as_const(t&: touchPoints)) { |
661 | if (p.state() == QEventPoint::State::Released) |
662 | continue; |
663 | const QPointF ¤tPos = p.scenePosition(); |
664 | const QPointF &startPos = p.scenePressPosition(); |
665 | if (qAbs(t: currentPos.x() - startPos.x()) > dragThreshold) |
666 | offerGrab = true; |
667 | else if (qAbs(t: currentPos.y() - startPos.y()) > dragThreshold) |
668 | offerGrab = true; |
669 | if (offerGrab) |
670 | break; |
671 | } |
672 | |
673 | if (offerGrab) { |
674 | QQuickGrabGestureEvent event; |
675 | event._touchPoints = _touchPoints.values(); |
676 | emit gestureStarted(gesture: &event); |
677 | if (event.wantsGrab() && dev) |
678 | grabGesture(dev); |
679 | } |
680 | } |
681 | |
682 | if (ended) |
683 | emit released(touchPoints: _releasedTouchPoints); |
684 | if (moved) |
685 | emit updated(touchPoints: _movedTouchPoints); |
686 | if (started && !_pressedTouchPoints.isEmpty()) |
687 | emit pressed(touchPoints: _pressedTouchPoints); |
688 | if (ended || moved || started) emit touchUpdated(touchPoints: _touchPoints.values()); |
689 | } |
690 | } |
691 | |
692 | void QQuickMultiPointTouchArea::clearTouchLists() |
693 | { |
694 | for (QObject *obj : std::as_const(t&: _releasedTouchPoints)) { |
695 | QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj); |
696 | if (!dtp->isQmlDefined()) { |
697 | _touchPoints.remove(key: dtp->pointId()); |
698 | delete dtp; |
699 | } else { |
700 | dtp->setInUse(false); |
701 | } |
702 | } |
703 | _releasedTouchPoints.clear(); |
704 | _pressedTouchPoints.clear(); |
705 | _movedTouchPoints.clear(); |
706 | } |
707 | |
708 | void QQuickMultiPointTouchArea::addTouchPoint(const QEventPoint *p) |
709 | { |
710 | QQuickTouchPoint *dtp = nullptr; |
711 | for (QQuickTouchPoint* tp : std::as_const(t&: _touchPrototypes)) { |
712 | if (!tp->inUse()) { |
713 | tp->setInUse(true); |
714 | dtp = tp; |
715 | break; |
716 | } |
717 | } |
718 | |
719 | if (dtp == nullptr) |
720 | dtp = new QQuickTouchPoint(false); |
721 | dtp->setPointId(p->id()); |
722 | updateTouchPoint(dtp,p); |
723 | dtp->setPressed(true); |
724 | _touchPoints.insert(key: p->id(),value: dtp); |
725 | _pressedTouchPoints.append(t: dtp); |
726 | } |
727 | |
728 | void QQuickMultiPointTouchArea::addTouchPoint(const QMouseEvent *e) |
729 | { |
730 | QQuickTouchPoint *dtp = nullptr; |
731 | for (QQuickTouchPoint *tp : std::as_const(t&: _touchPrototypes)) { |
732 | if (!tp->inUse()) { |
733 | tp->setInUse(true); |
734 | dtp = tp; |
735 | break; |
736 | } else if (_mouseTouchPoint == tp) { |
737 | return; // do not allow more than one touchpoint to react to the mouse (QTBUG-83662) |
738 | } |
739 | } |
740 | |
741 | if (dtp == nullptr) |
742 | dtp = new QQuickTouchPoint(false); |
743 | updateTouchPoint(dtp, e); |
744 | dtp->setPressed(true); |
745 | _touchPoints.insert(key: _mouseQpaTouchPoint.id(), value: dtp); |
746 | _pressedTouchPoints.append(t: dtp); |
747 | _mouseTouchPoint = dtp; |
748 | } |
749 | |
750 | #ifdef Q_OS_MACOS |
751 | void QQuickMultiPointTouchArea::hoverEnterEvent(QHoverEvent *event) |
752 | { |
753 | setTouchEventsEnabled(isEnabled()); |
754 | QQuickItem::hoverEnterEvent(event); |
755 | } |
756 | |
757 | void QQuickMultiPointTouchArea::hoverLeaveEvent(QHoverEvent *event) |
758 | { |
759 | setTouchEventsEnabled(false); |
760 | QQuickItem::hoverLeaveEvent(event); |
761 | } |
762 | |
763 | void QQuickMultiPointTouchArea::setTouchEventsEnabled(bool enable) |
764 | { |
765 | // Resolve function for enabling touch events from the (cocoa) platform plugin. |
766 | typedef void (*RegisterTouchWindowFunction)(QWindow *, bool); |
767 | RegisterTouchWindowFunction registerTouchWindow = reinterpret_cast<RegisterTouchWindowFunction>( |
768 | QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow" )); |
769 | if (!registerTouchWindow) |
770 | return; // Not necessarily an error, Qt might be using a different platform plugin. |
771 | |
772 | registerTouchWindow(window(), enable); |
773 | } |
774 | |
775 | void QQuickMultiPointTouchArea::itemChange(ItemChange change, const ItemChangeData &data) |
776 | { |
777 | if (change == ItemEnabledHasChanged) |
778 | setAcceptHoverEvents(data.boolValue); |
779 | QQuickItem::itemChange(change, data); |
780 | } |
781 | #endif // Q_OS_MACOS |
782 | |
783 | void QQuickMultiPointTouchArea::addTouchPrototype(QQuickTouchPoint *prototype) |
784 | { |
785 | int id = _touchPrototypes.size(); |
786 | prototype->setPointId(id); |
787 | _touchPrototypes.insert(key: id, value: prototype); |
788 | } |
789 | |
790 | void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QEventPoint *p) |
791 | { |
792 | //TODO: if !qmlDefined, could bypass setters. |
793 | // also, should only emit signals after all values have been set |
794 | dtp->setUniqueId(p->uniqueId()); |
795 | dtp->setPosition(p->position()); |
796 | dtp->setEllipseDiameters(p->ellipseDiameters()); |
797 | dtp->setPressure(p->pressure()); |
798 | dtp->setRotation(p->rotation()); |
799 | dtp->setVelocity(p->velocity()); |
800 | QRectF area(QPointF(), p->ellipseDiameters()); |
801 | area.moveCenter(p: p->position()); |
802 | dtp->setArea(area); |
803 | dtp->setStartX(p->pressPosition().x()); |
804 | dtp->setStartY(p->pressPosition().y()); |
805 | dtp->setPreviousX(p->lastPosition().x()); |
806 | dtp->setPreviousY(p->lastPosition().y()); |
807 | dtp->setSceneX(p->scenePosition().x()); |
808 | dtp->setSceneY(p->scenePosition().y()); |
809 | } |
810 | |
811 | void QQuickMultiPointTouchArea::updateTouchPoint(QQuickTouchPoint *dtp, const QMouseEvent *e) |
812 | { |
813 | dtp->setPreviousX(dtp->x()); |
814 | dtp->setPreviousY(dtp->y()); |
815 | dtp->setPosition(e->position()); |
816 | if (e->type() == QEvent::MouseButtonPress) { |
817 | dtp->setStartX(e->position().x()); |
818 | dtp->setStartY(e->position().y()); |
819 | } |
820 | dtp->setSceneX(e->scenePosition().x()); |
821 | dtp->setSceneY(e->scenePosition().y()); |
822 | } |
823 | |
824 | void QQuickMultiPointTouchArea::mousePressEvent(QMouseEvent *event) |
825 | { |
826 | if (!isEnabled() || !_mouseEnabled || event->button() != Qt::LeftButton) { |
827 | QQuickItem::mousePressEvent(event); |
828 | return; |
829 | } |
830 | |
831 | _stealMouse = false; |
832 | setKeepMouseGrab(false); |
833 | event->setAccepted(true); |
834 | _mousePos = event->position(); |
835 | if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) |
836 | return; |
837 | |
838 | if (_touchPoints.size() >= _minimumTouchPoints - 1 && _touchPoints.size() < _maximumTouchPoints) { |
839 | updateTouchData(event); |
840 | } |
841 | } |
842 | |
843 | void QQuickMultiPointTouchArea::mouseMoveEvent(QMouseEvent *event) |
844 | { |
845 | if (!isEnabled() || !_mouseEnabled) { |
846 | QQuickItem::mouseMoveEvent(event); |
847 | return; |
848 | } |
849 | |
850 | if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) |
851 | return; |
852 | |
853 | _movedTouchPoints.clear(); |
854 | updateTouchData(event); |
855 | } |
856 | |
857 | void QQuickMultiPointTouchArea::mouseReleaseEvent(QMouseEvent *event) |
858 | { |
859 | _stealMouse = false; |
860 | if (!isEnabled() || !_mouseEnabled) { |
861 | QQuickItem::mouseReleaseEvent(event); |
862 | return; |
863 | } |
864 | |
865 | if (event->source() != Qt::MouseEventNotSynthesized && event->source() != Qt::MouseEventSynthesizedByQt) |
866 | return; |
867 | |
868 | if (_mouseTouchPoint) { |
869 | updateTouchData(event); |
870 | _mouseTouchPoint->setInUse(false); |
871 | _releasedTouchPoints.removeAll(t: _mouseTouchPoint); |
872 | _mouseTouchPoint = nullptr; |
873 | } |
874 | |
875 | setKeepMouseGrab(false); |
876 | } |
877 | |
878 | void QQuickMultiPointTouchArea::ungrab(bool normalRelease) |
879 | { |
880 | _stealMouse = false; |
881 | setKeepMouseGrab(false); |
882 | setKeepTouchGrab(false); |
883 | if (!normalRelease) |
884 | ungrabTouchPoints(); |
885 | |
886 | if (_touchPoints.size()) { |
887 | for (QObject *obj : std::as_const(t&: _touchPoints)) |
888 | static_cast<QQuickTouchPoint*>(obj)->setPressed(false); |
889 | if (!normalRelease) |
890 | emit canceled(touchPoints: _touchPoints.values()); |
891 | clearTouchLists(); |
892 | for (QObject *obj : std::as_const(t&: _touchPoints)) { |
893 | QQuickTouchPoint *dtp = static_cast<QQuickTouchPoint*>(obj); |
894 | if (!dtp->isQmlDefined()) |
895 | delete dtp; |
896 | else |
897 | dtp->setInUse(false); |
898 | } |
899 | _touchPoints.clear(); |
900 | emit touchUpdated(touchPoints: QList<QObject*>()); |
901 | } |
902 | } |
903 | |
904 | void QQuickMultiPointTouchArea::mouseUngrabEvent() |
905 | { |
906 | ungrab(); |
907 | } |
908 | |
909 | void QQuickMultiPointTouchArea::touchUngrabEvent() |
910 | { |
911 | ungrab(); |
912 | } |
913 | |
914 | bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event) |
915 | { |
916 | const QPointF localPos = mapFromScene(point: event->scenePosition()); |
917 | |
918 | QQuickWindow *c = window(); |
919 | QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
920 | bool stealThisEvent = _stealMouse; |
921 | if ((stealThisEvent || contains(point: localPos)) && (!grabber || !grabber->keepMouseGrab())) { |
922 | QMutableSinglePointEvent mouseEvent(*event); |
923 | const auto oldPosition = mouseEvent.position(); |
924 | QMutableEventPoint::setPosition(p&: mouseEvent.point(i: 0), arg: localPos); |
925 | mouseEvent.setSource(Qt::MouseEventSynthesizedByQt); |
926 | mouseEvent.setAccepted(false); |
927 | QMouseEvent *pmouseEvent = static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&mouseEvent)); |
928 | |
929 | switch (mouseEvent.type()) { |
930 | case QEvent::MouseMove: |
931 | mouseMoveEvent(event: pmouseEvent); |
932 | break; |
933 | case QEvent::MouseButtonPress: |
934 | mousePressEvent(event: pmouseEvent); |
935 | break; |
936 | case QEvent::MouseButtonRelease: |
937 | mouseReleaseEvent(event: pmouseEvent); |
938 | break; |
939 | default: |
940 | break; |
941 | } |
942 | grabber = c ? c->mouseGrabberItem() : nullptr; |
943 | if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) |
944 | grabMouse(); |
945 | |
946 | QMutableEventPoint::setPosition(p&: mouseEvent.point(i: 0), arg: oldPosition); |
947 | return stealThisEvent; |
948 | } |
949 | if (event->type() == QEvent::MouseButtonRelease) { |
950 | _stealMouse = false; |
951 | if (c && c->mouseGrabberItem() == this) |
952 | ungrabMouse(); |
953 | setKeepMouseGrab(false); |
954 | } |
955 | return false; |
956 | } |
957 | |
958 | bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *receiver, QEvent *event) |
959 | { |
960 | if (!isEnabled() || !isVisible()) |
961 | return QQuickItem::childMouseEventFilter(receiver, event); |
962 | switch (event->type()) { |
963 | case QEvent::MouseButtonPress: { |
964 | auto da = QQuickItemPrivate::get(item: this)->deliveryAgentPrivate(); |
965 | // If we already got a chance to filter the touchpoint that generated this synth-mouse-press, |
966 | // and chose not to filter it, ignore it now, too. |
967 | if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedByQt && |
968 | _lastFilterableTouchPointIds.contains(t: da->touchMouseId)) |
969 | return false; |
970 | } Q_FALLTHROUGH(); |
971 | case QEvent::MouseMove: |
972 | case QEvent::MouseButtonRelease: |
973 | return sendMouseEvent(event: static_cast<QMouseEvent *>(event)); |
974 | case QEvent::TouchBegin: |
975 | _lastFilterableTouchPointIds.clear(); |
976 | Q_FALLTHROUGH(); |
977 | case QEvent::TouchUpdate: |
978 | for (const auto &tp : static_cast<QTouchEvent*>(event)->points()) { |
979 | if (tp.state() == QEventPoint::State::Pressed) |
980 | _lastFilterableTouchPointIds << tp.id(); |
981 | } |
982 | if (!shouldFilter(event)) |
983 | return false; |
984 | updateTouchData(event, remap: RemapEventPoints::ToLocal); |
985 | return _stealMouse; |
986 | case QEvent::TouchEnd: { |
987 | if (!shouldFilter(event)) |
988 | return false; |
989 | updateTouchData(event, remap: RemapEventPoints::ToLocal); |
990 | ungrab(normalRelease: true); |
991 | } |
992 | break; |
993 | default: |
994 | break; |
995 | } |
996 | return QQuickItem::childMouseEventFilter(receiver, event); |
997 | } |
998 | |
999 | bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event) |
1000 | { |
1001 | QQuickWindow *c = window(); |
1002 | QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
1003 | bool disabledItem = grabber && !grabber->isEnabled(); |
1004 | bool stealThisEvent = _stealMouse; |
1005 | bool containsPoint = false; |
1006 | if (!stealThisEvent) { |
1007 | switch (event->type()) { |
1008 | case QEvent::MouseButtonPress: |
1009 | case QEvent::MouseMove: |
1010 | case QEvent::MouseButtonRelease: { |
1011 | QMouseEvent *me = static_cast<QMouseEvent*>(event); |
1012 | containsPoint = contains(point: mapFromScene(point: me->scenePosition())); |
1013 | } |
1014 | break; |
1015 | case QEvent::TouchBegin: |
1016 | case QEvent::TouchUpdate: |
1017 | case QEvent::TouchEnd: { |
1018 | QTouchEvent *te = static_cast<QTouchEvent*>(event); |
1019 | for (const QEventPoint &point : te->points()) { |
1020 | if (contains(point: mapFromScene(point: point.scenePosition()))) { |
1021 | containsPoint = true; |
1022 | break; |
1023 | } |
1024 | } |
1025 | } |
1026 | break; |
1027 | default: |
1028 | break; |
1029 | } |
1030 | } |
1031 | if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { |
1032 | return true; |
1033 | } |
1034 | ungrab(); |
1035 | return false; |
1036 | } |
1037 | |
1038 | QSGNode *QQuickMultiPointTouchArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
1039 | { |
1040 | Q_UNUSED(data); |
1041 | |
1042 | if (!qmlMptaVisualTouchDebugging()) |
1043 | return nullptr; |
1044 | |
1045 | QSGInternalRectangleNode *rectangle = static_cast<QSGInternalRectangleNode *>(oldNode); |
1046 | if (!rectangle) rectangle = QQuickItemPrivate::get(item: this)->sceneGraphContext()->createInternalRectangleNode(); |
1047 | |
1048 | rectangle->setRect(QRectF(0, 0, width(), height())); |
1049 | rectangle->setColor(QColor(255, 0, 0, 50)); |
1050 | rectangle->update(); |
1051 | return rectangle; |
1052 | } |
1053 | |
1054 | QT_END_NAMESPACE |
1055 | |
1056 | #include "moc_qquickmultipointtoucharea_p.cpp" |
1057 | |