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

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