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 "qquickpincharea_p_p.h"
5#include "qquickwindow.h"
6
7#include <QtCore/qmath.h>
8#include <QtGui/qevent.h>
9#include <QtGui/qguiapplication.h>
10#include <QtGui/qstylehints.h>
11#include <qpa/qplatformintegration.h>
12#include <qpa/qplatformnativeinterface.h>
13#include <private/qguiapplication_p.h>
14#include <QVariant>
15
16#include <float.h>
17
18QT_BEGIN_NAMESPACE
19
20Q_STATIC_LOGGING_CATEGORY(lcPA, "qt.quick.pincharea")
21
22/*!
23 \qmltype PinchEvent
24 \nativetype QQuickPinchEvent
25 \inqmlmodule QtQuick
26 \ingroup qtquick-input-events
27 \brief For specifying information about a pinch event.
28
29 The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
30
31 The \c scale and \c previousScale properties provide the scale factor.
32
33 The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
34
35 The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
36
37 The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
38 be handled.
39
40 \sa PinchArea
41*/
42
43/*!
44 \qmlproperty point QtQuick::PinchEvent::center
45 \qmlproperty point QtQuick::PinchEvent::startCenter
46 \qmlproperty point QtQuick::PinchEvent::previousCenter
47
48 These properties hold the position of the center point between the two touch points.
49
50 \list
51 \li \c center is the current center point
52 \li \c previousCenter is the center point of the previous event.
53 \li \c startCenter is the center point when the gesture began
54 \endlist
55*/
56
57/*!
58 \qmlproperty real QtQuick::PinchEvent::scale
59 \qmlproperty real QtQuick::PinchEvent::previousScale
60
61 These properties hold the scale factor determined by the change in distance between the two touch points.
62
63 \list
64 \li \c scale is the current scale factor.
65 \li \c previousScale is the scale factor of the previous event.
66 \endlist
67
68 When a pinch gesture is started, the scale is \c 1.0.
69*/
70
71/*!
72 \qmlproperty real QtQuick::PinchEvent::angle
73 \qmlproperty real QtQuick::PinchEvent::previousAngle
74 \qmlproperty real QtQuick::PinchEvent::rotation
75
76 These properties hold the angle between the two touch points.
77
78 \list
79 \li \c angle is the current angle between the two points in the range -180 to 180.
80 \li \c previousAngle is the angle of the previous event.
81 \li \c rotation is the total rotation since the pinch gesture started.
82 \endlist
83
84 When a pinch gesture is started, the rotation is \c 0.0.
85*/
86
87/*!
88 \qmlproperty point QtQuick::PinchEvent::point1
89 \qmlproperty point QtQuick::PinchEvent::startPoint1
90 \qmlproperty point QtQuick::PinchEvent::point2
91 \qmlproperty point QtQuick::PinchEvent::startPoint2
92
93 These properties provide the actual touch points generating the pinch.
94
95 \list
96 \li \c point1 and \c point2 hold the current positions of the points.
97 \li \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched.
98 \endlist
99*/
100
101/*!
102 \qmlproperty bool QtQuick::PinchEvent::accepted
103
104 Setting this property to false in the \c PinchArea::onPinchStarted handler
105 will result in no further pinch events being generated, and the gesture
106 ignored.
107*/
108
109/*!
110 \qmlproperty int QtQuick::PinchEvent::pointCount
111
112 Holds the number of points currently touched. The PinchArea will not react
113 until two touch points have initited a gesture, but will remain active until
114 all touch points have been released.
115*/
116
117QQuickPinch::QQuickPinch()
118 : m_target(nullptr), m_minScale(1.0), m_maxScale(1.0)
119 , m_minRotation(0.0), m_maxRotation(0.0)
120 , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX)
121 , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false)
122{
123}
124
125QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate()
126{
127 delete pinch;
128}
129
130/*!
131 \qmltype PinchArea
132 \nativetype QQuickPinchArea
133 \inqmlmodule QtQuick
134 \ingroup qtquick-input
135 \inherits Item
136 \brief Enables simple pinch gesture handling.
137
138 A PinchArea is an invisible item that is typically used in conjunction with
139 a visible item in order to provide pinch gesture handling for that item.
140
141 The \l enabled property is used to enable and disable pinch handling for
142 the proxied item. When disabled, the pinch area becomes transparent to
143 mouse/touch events.
144
145 PinchArea can be used in two ways:
146
147 \list
148 \li setting a \c pinch.target to provide automatic interaction with an item
149 \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
150 \endlist
151
152 Since Qt 5.5, PinchArea can react to native pinch gesture events from the
153 operating system if available; otherwise it reacts only to touch events.
154
155 \sa PinchEvent, QNativeGestureEvent, QTouchEvent
156*/
157
158/*!
159 \qmlsignal QtQuick::PinchArea::pinchStarted(PinchEvent pinch)
160
161 This signal is emitted when the pinch area detects that a pinch gesture has
162 started: two touch points (fingers) have been detected, and they have moved
163 beyond the \l {QStyleHints}{startDragDistance} threshold for the gesture to begin.
164
165 The \a pinch parameter (not the same as the \l {PinchArea}{pinch}
166 property) provides information about the pinch gesture, including the scale,
167 center and angle of the pinch. At the time of the \c pinchStarted signal,
168 these values are reset to the default values, regardless of the results
169 from previous gestures: pinch.scale will be \c 1.0 and pinch.rotation will be \c 0.0.
170 As the gesture progresses, \l pinchUpdated will report the deviation from those
171 defaults.
172
173 To ignore this gesture set the \c pinch.accepted property to false. The gesture
174 will be canceled and no further events will be sent.
175*/
176
177/*!
178 \qmlsignal QtQuick::PinchArea::pinchUpdated(PinchEvent pinch)
179
180 This signal is emitted when the pinch area detects that a pinch gesture has changed.
181
182 The \a pinch parameter provides information about the pinch
183 gesture, including the scale, center and angle of the pinch. These values
184 reflect changes only since the beginning of the current gesture, and
185 therefore are not limited by the minimum and maximum limits in the
186 \l {PinchArea}{pinch} property.
187*/
188
189/*!
190 \qmlsignal QtQuick::PinchArea::pinchFinished(PinchEvent pinch)
191
192 This signal is emitted when the pinch area detects that a pinch gesture has finished.
193
194 The \a pinch parameter (not the same as the \l {PinchArea}{pinch}
195 property) provides information about the pinch gesture, including the
196 scale, center and angle of the pinch.
197*/
198
199/*!
200 \qmlsignal QtQuick::PinchArea::smartZoom(PinchEvent pinch)
201 \since 5.5
202
203 This signal is emitted when the pinch area detects a smart zoom gesture.
204 This gesture occurs only on certain operating systems such as \macos.
205
206 The \a pinch parameter provides information about the pinch
207 gesture, including the location where the gesture occurred. \c pinch.scale
208 will be greater than zero when the gesture indicates that the user wishes to
209 enter smart zoom, and zero when exiting (even though typically the same gesture
210 is used to toggle between the two states).
211*/
212
213
214/*!
215 \qmlpropertygroup QtQuick::PinchArea::pinch
216 \qmlproperty Item QtQuick::PinchArea::pinch.target
217 \qmlproperty bool QtQuick::PinchArea::pinch.active
218 \qmlproperty real QtQuick::PinchArea::pinch.minimumScale
219 \qmlproperty real QtQuick::PinchArea::pinch.maximumScale
220 \qmlproperty real QtQuick::PinchArea::pinch.minimumRotation
221 \qmlproperty real QtQuick::PinchArea::pinch.maximumRotation
222 \qmlproperty enumeration QtQuick::PinchArea::pinch.dragAxis
223 \qmlproperty real QtQuick::PinchArea::pinch.minimumX
224 \qmlproperty real QtQuick::PinchArea::pinch.maximumX
225 \qmlproperty real QtQuick::PinchArea::pinch.minimumY
226 \qmlproperty real QtQuick::PinchArea::pinch.maximumY
227
228 \c pinch provides a convenient way to make an item react to pinch gestures.
229
230 \list
231 \li \c pinch.target specifies the id of the item to drag.
232 \li \c pinch.active specifies if the target item is currently being dragged.
233 \li \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item.scale property, but not the \c PinchEvent \l {PinchEvent}{scale} property.
234 \li \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item.rotation property, but not the \c PinchEvent \l {PinchEvent}{rotation} property.
235 \li \c pinch.dragAxis specifies whether dragging in not allowed (\c Pinch.NoDrag), can be done horizontally (\c Pinch.XAxis), vertically (\c Pinch.YAxis), or both (\c Pinch.XAndYAxis)
236 \li \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes.
237 \endlist
238*/
239
240QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
241 : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
242{
243 Q_D(QQuickPinchArea);
244 d->init();
245 setAcceptTouchEvents(true);
246#ifdef Q_OS_MACOS
247 setAcceptHoverEvents(true); // needed to enable touch events on mouse hover.
248#endif
249}
250
251QQuickPinchArea::~QQuickPinchArea()
252{
253}
254/*!
255 \qmlproperty bool QtQuick::PinchArea::enabled
256 This property holds whether the item accepts pinch gestures.
257
258 This property defaults to true.
259*/
260bool QQuickPinchArea::isEnabled() const
261{
262 Q_D(const QQuickPinchArea);
263 return d->enabled;
264}
265
266void QQuickPinchArea::setEnabled(bool a)
267{
268 Q_D(QQuickPinchArea);
269 if (a != d->enabled) {
270 d->enabled = a;
271 emit enabledChanged();
272 }
273}
274
275void QQuickPinchArea::touchEvent(QTouchEvent *event)
276{
277 Q_D(QQuickPinchArea);
278 if (!d->enabled || !isVisible()) {
279 QQuickItem::touchEvent(event);
280 return;
281 }
282
283 // A common non-trivial starting scenario is the user puts down one finger,
284 // then that finger remains stationary while putting down a second one.
285 // However QQuickWindow will not send TouchUpdates for TouchPoints which
286 // were not initially accepted; that would be inefficient and noisy.
287 // So even if there is only one touchpoint so far, it's important to accept it
288 // in order to get updates later on (and it's accepted by default anyway).
289 // If the user puts down one finger, we're waiting for the other finger to drop.
290 // Therefore updatePinch() must do the right thing for any combination of
291 // points and states that may occur, and there is no reason to ignore any event.
292 // One consequence though is that if PinchArea is on top of something else,
293 // it's always going to accept the touches, and that means the item underneath
294 // will not get them (unless the PA's parent is doing parent filtering,
295 // as the Flickable does, for example).
296 switch (event->type()) {
297 case QEvent::TouchBegin:
298 case QEvent::TouchUpdate:
299 d->touchPoints.clear();
300 for (int i = 0; i < event->pointCount(); ++i) {
301 auto &tp = event->point(i);
302 if (tp.state() != QEventPoint::State::Released) {
303 d->touchPoints << tp;
304 tp.setAccepted();
305 }
306 }
307 updatePinch(event, filtering: false);
308 break;
309 case QEvent::TouchEnd:
310 clearPinch(event);
311 break;
312 case QEvent::TouchCancel:
313 cancelPinch(event);
314 break;
315 default:
316 QQuickItem::touchEvent(event);
317 }
318}
319
320void QQuickPinchArea::clearPinch(QTouchEvent *event)
321{
322 Q_D(QQuickPinchArea);
323 qCDebug(lcPA, "clear: %" PRIdQSIZETYPE " touchpoints", d->touchPoints.size());
324 d->touchPoints.clear();
325 if (d->inPinch) {
326 d->inPinch = false;
327 QPointF pinchCenter = mapFromScene(point: d->sceneLastCenter);
328 QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
329 pe.setStartCenter(d->pinchStartCenter);
330 pe.setPreviousCenter(pinchCenter);
331 pe.setPreviousAngle(d->pinchLastAngle);
332 pe.setPreviousScale(d->pinchLastScale);
333 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
334 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
335 pe.setPoint1(mapFromScene(point: d->lastPoint1));
336 pe.setPoint2(mapFromScene(point: d->lastPoint2));
337 emit pinchFinished(pinch: &pe);
338 if (d->pinch && d->pinch->target())
339 d->pinch->setActive(false);
340 }
341 d->pinchStartDist = 0;
342 d->pinchActivated = false;
343 d->initPinch = false;
344 d->pinchRejected = false;
345 d->id1 = -1;
346 if (event) {
347 for (const auto &point : event->points()) {
348 if (event->exclusiveGrabber(point) == this)
349 event->setExclusiveGrabber(point, exclusiveGrabber: nullptr);
350 }
351 }
352 setKeepTouchGrab(false);
353 setKeepMouseGrab(false);
354}
355
356void QQuickPinchArea::cancelPinch(QTouchEvent *event)
357{
358 Q_D(QQuickPinchArea);
359 qCDebug(lcPA, "cancel: %" PRIdQSIZETYPE " touchpoints", d->touchPoints.size());
360 d->touchPoints.clear();
361 if (d->inPinch) {
362 d->inPinch = false;
363 QPointF pinchCenter = mapFromScene(point: d->sceneLastCenter);
364 QQuickPinchEvent pe(d->pinchStartCenter, d->pinchStartScale, d->pinchStartAngle, d->pinchStartRotation);
365 pe.setStartCenter(d->pinchStartCenter);
366 pe.setPreviousCenter(pinchCenter);
367 pe.setPreviousAngle(d->pinchLastAngle);
368 pe.setPreviousScale(d->pinchLastScale);
369 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
370 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
371 pe.setPoint1(pe.startPoint1());
372 pe.setPoint2(pe.startPoint2());
373 emit pinchFinished(pinch: &pe);
374
375 d->pinchLastScale = d->pinchStartScale;
376 d->sceneLastCenter = d->sceneStartCenter;
377 d->pinchLastAngle = d->pinchStartAngle;
378 d->lastPoint1 = pe.startPoint1();
379 d->lastPoint2 = pe.startPoint2();
380 updatePinchTarget();
381
382 if (d->pinch && d->pinch->target())
383 d->pinch->setActive(false);
384 }
385 d->pinchStartDist = 0;
386 d->pinchActivated = false;
387 d->initPinch = false;
388 d->pinchRejected = false;
389 d->id1 = -1;
390 for (const auto &point : event->points()) {
391 if (event->exclusiveGrabber(point) == this)
392 event->setExclusiveGrabber(point, exclusiveGrabber: nullptr);
393 }
394 setKeepTouchGrab(false);
395 setKeepMouseGrab(false);
396}
397
398void QQuickPinchArea::updatePinch(QTouchEvent *event, bool filtering)
399{
400 Q_D(QQuickPinchArea);
401
402 if (d->touchPoints.size() < 2) {
403 // A pinch gesture is not occurring, so stealing the grab is permitted.
404 setKeepTouchGrab(false);
405 setKeepMouseGrab(false);
406 // During filtering, there's no need to hold a grab for one point,
407 // because filtering happens for every event anyway.
408 // But if we receive the event via direct delivery, and give up the grab,
409 // not only will we not see any more updates, but any filtering parent
410 // (such as Flickable) will also not get a chance to filter them.
411 // Continuing to hold the grab in this case keeps tst_TouchMouse::pinchOnFlickable() working.
412 if (filtering && !d->touchPoints.isEmpty() && event->exclusiveGrabber(point: d->touchPoints.first()) == this)
413 event->setExclusiveGrabber(point: d->touchPoints.first(), exclusiveGrabber: nullptr);
414 }
415
416 if (d->touchPoints.size() == 0) {
417 if (d->inPinch) {
418 d->inPinch = false;
419 QPointF pinchCenter = mapFromScene(point: d->sceneLastCenter);
420 QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
421 pe.setStartCenter(d->pinchStartCenter);
422 pe.setPreviousCenter(pinchCenter);
423 pe.setPreviousAngle(d->pinchLastAngle);
424 pe.setPreviousScale(d->pinchLastScale);
425 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
426 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
427 pe.setPoint1(mapFromScene(point: d->lastPoint1));
428 pe.setPoint2(mapFromScene(point: d->lastPoint2));
429 setKeepTouchGrab(false);
430 setKeepMouseGrab(false);
431 emit pinchFinished(pinch: &pe);
432 d->pinchStartDist = 0;
433 d->pinchActivated = false;
434 if (d->pinch && d->pinch->target())
435 d->pinch->setActive(false);
436 }
437 d->initPinch = false;
438 d->pinchRejected = false;
439 return;
440 }
441
442 QEventPoint touchPoint1 = d->touchPoints.at(i: 0);
443 QEventPoint touchPoint2 = d->touchPoints.at(i: d->touchPoints.size() >= 2 ? 1 : 0);
444
445 if (touchPoint1.state() == QEventPoint::State::Pressed)
446 d->sceneStartPoint1 = touchPoint1.scenePosition();
447
448 if (touchPoint2.state() == QEventPoint::State::Pressed)
449 d->sceneStartPoint2 = touchPoint2.scenePosition();
450
451 qCDebug(lcPA) << "updating based on" << touchPoint1 << touchPoint2;
452
453 QRectF bounds = clipRect();
454 // Pinch is not started unless there are exactly two touch points
455 // AND one or more of the points has just now been pressed (wasn't pressed already)
456 // AND both points are inside the bounds.
457 if (d->touchPoints.size() == 2
458 && (touchPoint1.state() == QEventPoint::State::Pressed || touchPoint2.state() == QEventPoint::State::Pressed) &&
459 bounds.contains(p: touchPoint1.position()) && bounds.contains(p: touchPoint2.position())) {
460 d->id1 = touchPoint1.id();
461 if (!d->pinchActivated)
462 qCDebug(lcPA, "pinch activating");
463 d->pinchActivated = true;
464 d->initPinch = true;
465 event->setExclusiveGrabber(point: touchPoint1, exclusiveGrabber: this);
466 event->setExclusiveGrabber(point: touchPoint2, exclusiveGrabber: this);
467 setKeepTouchGrab(true);
468 setKeepMouseGrab(true);
469 }
470 if (d->pinchActivated && !d->pinchRejected) {
471 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
472 QPointF p1 = touchPoint1.scenePosition();
473 QPointF p2 = touchPoint2.scenePosition();
474 qreal dx = p1.x() - p2.x();
475 qreal dy = p1.y() - p2.y();
476 qreal dist = qSqrt(v: dx*dx + dy*dy);
477 QPointF sceneCenter = (p1 + p2)/2;
478 qreal angle = QLineF(p1, p2).angle();
479 if (d->touchPoints.size() == 1) {
480 // If we only have one point then just move the center
481 if (d->id1 == touchPoint1.id())
482 sceneCenter = d->sceneLastCenter + touchPoint1.scenePosition() - d->lastPoint1;
483 else
484 sceneCenter = d->sceneLastCenter + touchPoint2.scenePosition() - d->lastPoint2;
485 angle = d->pinchLastAngle;
486 }
487 d->id1 = touchPoint1.id();
488 if (angle > 180)
489 angle -= 360;
490 qCDebug(lcPA, "pinch \u2316 %.1lf,%.1lf \u21e4%.1lf\u21e5 \u2220 %.1lf",
491 sceneCenter.x(), sceneCenter.y(), dist, angle);
492 if (!d->inPinch || d->initPinch) {
493 if (d->touchPoints.size() >= 2) {
494 if (d->initPinch) {
495 if (!d->inPinch)
496 d->pinchStartDist = dist;
497 d->initPinch = false;
498 }
499 d->sceneStartCenter = sceneCenter;
500 d->sceneLastCenter = sceneCenter;
501 d->pinchStartCenter = mapFromScene(point: sceneCenter);
502 d->pinchStartAngle = angle;
503 d->pinchLastScale = 1.0;
504 d->pinchLastAngle = angle;
505 d->pinchRotation = 0.0;
506 d->lastPoint1 = p1;
507 d->lastPoint2 = p2;
508 if (qAbs(t: dist - d->pinchStartDist) >= dragThreshold ||
509 (pinch()->axis() != QQuickPinch::NoDrag &&
510 (qAbs(t: p1.x()-d->sceneStartPoint1.x()) >= dragThreshold
511 || qAbs(t: p1.y()-d->sceneStartPoint1.y()) >= dragThreshold
512 || qAbs(t: p2.x()-d->sceneStartPoint2.x()) >= dragThreshold
513 || qAbs(t: p2.y()-d->sceneStartPoint2.y()) >= dragThreshold))) {
514 QQuickPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0);
515 d->pinchStartDist = dist;
516 pe.setStartCenter(d->pinchStartCenter);
517 pe.setPreviousCenter(d->pinchStartCenter);
518 pe.setPreviousAngle(d->pinchLastAngle);
519 pe.setPreviousScale(d->pinchLastScale);
520 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
521 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
522 pe.setPoint1(mapFromScene(point: d->lastPoint1));
523 pe.setPoint2(mapFromScene(point: d->lastPoint2));
524 pe.setPointCount(d->touchPoints.size());
525 emit pinchStarted(pinch: &pe);
526 if (pe.accepted()) {
527 d->inPinch = true;
528 event->setExclusiveGrabber(point: touchPoint1, exclusiveGrabber: this);
529 event->setExclusiveGrabber(point: touchPoint2, exclusiveGrabber: this);
530 setKeepTouchGrab(true);
531 // So that PinchArea works in PathView, grab mouse events too.
532 // We should be able to remove these setKeepMouseGrab calls when QTBUG-105567 is fixed.
533 setKeepMouseGrab(true);
534 d->inPinch = true;
535 if (d->pinch && d->pinch->target()) {
536 auto targetParent = pinch()->target()->parentItem();
537 d->pinchStartPos = targetParent ?
538 targetParent->mapToScene(point: pinch()->target()->position()) :
539 pinch()->target()->position();
540 d->pinchStartScale = d->pinch->target()->scale();
541 d->pinchStartRotation = d->pinch->target()->rotation();
542 d->pinch->setActive(true);
543 }
544 } else {
545 d->pinchRejected = true;
546 }
547 }
548 }
549 } else if (d->pinchStartDist > 0) {
550 qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
551 qreal da = d->pinchLastAngle - angle;
552 if (da > 180)
553 da -= 360;
554 else if (da < -180)
555 da += 360;
556 d->pinchRotation += da;
557 QPointF pinchCenter = mapFromScene(point: sceneCenter);
558 QQuickPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation);
559 pe.setStartCenter(d->pinchStartCenter);
560 pe.setPreviousCenter(mapFromScene(point: d->sceneLastCenter));
561 pe.setPreviousAngle(d->pinchLastAngle);
562 pe.setPreviousScale(d->pinchLastScale);
563 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
564 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
565 pe.setPoint1(touchPoint1.position());
566 pe.setPoint2(touchPoint2.position());
567 pe.setPointCount(d->touchPoints.size());
568 d->pinchLastScale = scale;
569 d->sceneLastCenter = sceneCenter;
570 d->pinchLastAngle = angle;
571 d->lastPoint1 = touchPoint1.scenePosition();
572 d->lastPoint2 = touchPoint2.scenePosition();
573 emit pinchUpdated(pinch: &pe);
574 updatePinchTarget();
575 }
576 }
577}
578
579void QQuickPinchArea::updatePinchTarget()
580{
581 Q_D(QQuickPinchArea);
582 if (d->pinch && d->pinch->target()) {
583 qreal s = d->pinchStartScale * d->pinchLastScale;
584 s = qMin(a: qMax(a: pinch()->minimumScale(),b: s), b: pinch()->maximumScale());
585 pinch()->target()->setScale(s);
586 QPointF pos = d->sceneLastCenter - d->sceneStartCenter + d->pinchStartPos;
587 if (auto targetParent = pinch()->target()->parentItem())
588 pos = targetParent->mapFromScene(point: pos);
589
590 if (pinch()->axis() & QQuickPinch::XAxis) {
591 qreal x = pos.x();
592 if (x < pinch()->xmin())
593 x = pinch()->xmin();
594 else if (x > pinch()->xmax())
595 x = pinch()->xmax();
596 pinch()->target()->setX(x);
597 }
598 if (pinch()->axis() & QQuickPinch::YAxis) {
599 qreal y = pos.y();
600 if (y < pinch()->ymin())
601 y = pinch()->ymin();
602 else if (y > pinch()->ymax())
603 y = pinch()->ymax();
604 pinch()->target()->setY(y);
605 }
606 if (d->pinchStartRotation >= pinch()->minimumRotation()
607 && d->pinchStartRotation <= pinch()->maximumRotation()) {
608 qreal r = d->pinchRotation + d->pinchStartRotation;
609 r = qMin(a: qMax(a: pinch()->minimumRotation(),b: r), b: pinch()->maximumRotation());
610 pinch()->target()->setRotation(r);
611 }
612 }
613}
614
615/*! \internal
616 PinchArea needs to filter touch events going to its children: in case
617 one of them stops event propagation by accepting the touch event, filtering
618 is the only way PinchArea can see the touch event.
619
620 This method is called childMouseEventFilter instead of childPointerEventFilter
621 for historical reasons, but actually filters all pointer events (and the
622 occasional QEvent::UngrabMouse).
623*/
624bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
625{
626 Q_D(QQuickPinchArea);
627 if (!d->enabled || !isVisible())
628 return QQuickItem::childMouseEventFilter(i, e);
629 auto *te = static_cast<QTouchEvent*>(e);
630 switch (e->type()) {
631 case QEvent::TouchBegin:
632 clearPinch(event: te);
633 Q_FALLTHROUGH();
634 case QEvent::TouchUpdate: {
635 const auto &points = te->points();
636 d->touchPoints.clear();
637 for (auto &tp : points) {
638 if (tp.state() != QEventPoint::State::Released)
639 d->touchPoints << tp;
640 }
641 updatePinch(event: te, filtering: true);
642 }
643 e->setAccepted(d->inPinch);
644 return d->inPinch;
645 case QEvent::TouchEnd:
646 clearPinch(event: te);
647 break;
648 default:
649 break;
650 }
651
652 return QQuickItem::childMouseEventFilter(i, e);
653}
654
655void QQuickPinchArea::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
656{
657 QQuickItem::geometryChange(newGeometry, oldGeometry);
658}
659
660void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
661{
662 QQuickItem::itemChange(change, value);
663}
664
665bool QQuickPinchArea::event(QEvent *event)
666{
667 Q_D(QQuickPinchArea);
668 if (!d->enabled || !isVisible())
669 return QQuickItem::event(event);
670
671 switch (event->type()) {
672#if QT_CONFIG(gestures)
673 case QEvent::NativeGesture: {
674 QNativeGestureEvent *gesture = static_cast<QNativeGestureEvent *>(event);
675 switch (gesture->gestureType()) {
676 case Qt::BeginNativeGesture:
677 clearPinch(event: nullptr); // probably not necessary; JIC
678 d->pinchStartCenter = gesture->position();
679 d->pinchStartAngle = 0.0;
680 d->pinchStartRotation = 0.0;
681 d->pinchRotation = 0.0;
682 d->pinchStartScale = 1.0;
683 d->pinchLastAngle = 0.0;
684 d->pinchLastScale = 1.0;
685 d->sceneStartPoint1 = gesture->scenePosition();
686 d->sceneStartPoint2 = gesture->scenePosition(); // TODO we never really know
687 d->lastPoint1 = gesture->scenePosition();
688 d->lastPoint2 = gesture->scenePosition(); // TODO we never really know
689 if (d->pinch && d->pinch->target()) {
690 d->pinchStartPos = d->pinch->target()->position();
691 d->pinchStartScale = d->pinch->target()->scale();
692 d->pinchStartRotation = d->pinch->target()->rotation();
693 d->pinch->setActive(true);
694 }
695 break;
696 case Qt::EndNativeGesture:
697 clearPinch(event: nullptr);
698 break;
699 case Qt::ZoomNativeGesture: {
700 if (d->pinchRejected)
701 break;
702 qreal scale = d->pinchLastScale * (1.0 + gesture->value());
703 QQuickPinchEvent pe(d->pinchStartCenter, scale, d->pinchLastAngle, 0.0);
704 pe.setStartCenter(d->pinchStartCenter);
705 pe.setPreviousCenter(d->pinchStartCenter);
706 pe.setPreviousAngle(d->pinchLastAngle);
707 pe.setPreviousScale(d->pinchLastScale);
708 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
709 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
710 pe.setPoint1(mapFromScene(point: d->lastPoint1));
711 pe.setPoint2(mapFromScene(point: d->lastPoint2));
712 pe.setPointCount(2);
713 d->pinchLastScale = scale;
714 if (d->inPinch)
715 emit pinchUpdated(pinch: &pe);
716 else
717 emit pinchStarted(pinch: &pe);
718 d->inPinch = true;
719 if (pe.accepted())
720 updatePinchTarget();
721 else
722 d->pinchRejected = true;
723 } break;
724 case Qt::SmartZoomNativeGesture: {
725 if (gesture->value() > 0.0 && d->pinch && d->pinch->target()) {
726 d->pinchStartPos = pinch()->target()->position();
727 d->pinchStartCenter = mapToItem(item: pinch()->target()->parentItem(), point: pinch()->target()->boundingRect().center());
728 d->pinchStartScale = d->pinch->target()->scale();
729 d->pinchStartRotation = d->pinch->target()->rotation();
730 d->pinchLastScale = d->pinchStartScale = d->pinch->target()->scale();
731 d->pinchLastAngle = d->pinchStartRotation = d->pinch->target()->rotation();
732 }
733 QQuickPinchEvent pe(gesture->position(), gesture->value(), d->pinchLastAngle, 0.0);
734 pe.setStartCenter(gesture->position());
735 pe.setPreviousCenter(d->pinchStartCenter);
736 pe.setPreviousAngle(d->pinchLastAngle);
737 pe.setPreviousScale(d->pinchLastScale);
738 pe.setStartPoint1(gesture->position());
739 pe.setStartPoint2(gesture->position());
740 pe.setPoint1(mapFromScene(point: gesture->scenePosition()));
741 pe.setPoint2(mapFromScene(point: gesture->scenePosition()));
742 pe.setPointCount(2);
743 emit smartZoom(pinch: &pe);
744 } break;
745 case Qt::RotateNativeGesture: {
746 if (d->pinchRejected)
747 break;
748 qreal angle = d->pinchLastAngle + gesture->value();
749 QQuickPinchEvent pe(d->pinchStartCenter, d->pinchLastScale, angle, 0.0);
750 pe.setStartCenter(d->pinchStartCenter);
751 pe.setPreviousCenter(d->pinchStartCenter);
752 pe.setPreviousAngle(d->pinchLastAngle);
753 pe.setPreviousScale(d->pinchLastScale);
754 pe.setStartPoint1(mapFromScene(point: d->sceneStartPoint1));
755 pe.setStartPoint2(mapFromScene(point: d->sceneStartPoint2));
756 pe.setPoint1(mapFromScene(point: d->lastPoint1));
757 pe.setPoint2(mapFromScene(point: d->lastPoint2));
758 pe.setPointCount(2);
759 d->pinchLastAngle = angle;
760 if (d->inPinch)
761 emit pinchUpdated(pinch: &pe);
762 else
763 emit pinchStarted(pinch: &pe);
764 d->inPinch = true;
765 d->pinchRotation = angle;
766 if (pe.accepted())
767 updatePinchTarget();
768 else
769 d->pinchRejected = true;
770 } break;
771 default:
772 return QQuickItem::event(event);
773 }
774 } break;
775#endif // gestures
776 case QEvent::Wheel:
777 event->ignore();
778 return false;
779 default:
780 return QQuickItem::event(event);
781 }
782
783 return true;
784}
785
786QQuickPinch *QQuickPinchArea::pinch()
787{
788 Q_D(QQuickPinchArea);
789 if (!d->pinch)
790 d->pinch = new QQuickPinch;
791 return d->pinch;
792}
793
794QT_END_NAMESPACE
795
796#include "moc_qquickpincharea_p.cpp"
797

source code of qtdeclarative/src/quick/items/qquickpincharea.cpp