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

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