1// Copyright (C) 2016 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 "qquickpinchhandler_p.h"
5#include <QtQml/qqmlinfo.h>
6#include <QtQuick/qquickwindow.h>
7#include <private/qsgadaptationlayer_p.h>
8#include <private/qquickitem_p.h>
9#include <private/qguiapplication_p.h>
10#include <private/qquickmultipointhandler_p_p.h>
11#include <private/qquickwindow_p.h>
12#include <QEvent>
13#include <QMouseEvent>
14#include <QDebug>
15#include <qpa/qplatformnativeinterface.h>
16#include <math.h>
17
18QT_BEGIN_NAMESPACE
19
20Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
21
22/*!
23 \qmltype PinchHandler
24 \instantiates QQuickPinchHandler
25 \inherits MultiPointHandler
26 \inqmlmodule QtQuick
27 \ingroup qtquick-input-handlers
28 \brief Handler for pinch gestures.
29
30 PinchHandler is a handler that interprets a multi-finger gesture to
31 interactively rotate, zoom, and drag an Item. Like other Input Handlers,
32 by default it is fully functional, and manipulates its \l target,
33 which is the Item within which it is declared.
34
35 \snippet pointerHandlers/pinchHandler.qml 0
36
37 It has properties to restrict the range of dragging, rotation, and zoom.
38
39 If it is declared within one Item but is assigned a different \l target, it
40 handles events within the bounds of the outer Item but manipulates the
41 \c target Item instead:
42
43 \snippet pointerHandlers/pinchHandlerDifferentTarget.qml 0
44
45 A third way to use it is to set \l target to \c null and react to property
46 changes in some other way:
47
48 \snippet pointerHandlers/pinchHandlerNullTarget.qml 0
49
50 \image touchpoints-pinchhandler.png
51
52 \note The pinch begins when the number of fingers pressed is between
53 \l {MultiPointHandler::minimumPointCount}{minimumPointCount} and
54 \l {MultiPointHandler::maximumPointCount}{maximumPointCount}, inclusive.
55 Until then, PinchHandler tracks the positions of any pressed fingers,
56 but if it's a disallowed number, it does not scale or rotate
57 its \l target, and the \l active property remains \c false.
58
59 \sa PinchArea, QPointerEvent::pointCount(), QNativeGestureEvent::fingerCount(), {Qt Quick Examples - Pointer Handlers}
60*/
61
62QQuickPinchHandler::QQuickPinchHandler(QQuickItem *parent)
63 : QQuickMultiPointHandler(parent, 2)
64{
65 // Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state
66 d_func()->acceptedButtons = Qt::NoButton;
67}
68
69#if QT_DEPRECATED_SINCE(6, 5)
70/*!
71 \qmlproperty real QtQuick::PinchHandler::minimumScale
72 \deprecated [6.5] Use scaleAxis.minimum
73
74 The minimum acceptable \l {Item::scale}{scale} to be applied
75 to the \l target.
76*/
77void QQuickPinchHandler::setMinimumScale(qreal minimumScale)
78{
79 if (qFuzzyCompare(p1: m_scaleAxis.minimum(), p2: minimumScale))
80 return;
81
82 m_scaleAxis.setMinimum(minimumScale);
83 emit minimumScaleChanged();
84}
85
86/*!
87 \qmlproperty real QtQuick::PinchHandler::maximumScale
88 \deprecated [6.5] Use scaleAxis.maximum
89
90 The maximum acceptable \l {Item::scale}{scale} to be applied
91 to the \l target.
92*/
93void QQuickPinchHandler::setMaximumScale(qreal maximumScale)
94{
95 if (qFuzzyCompare(p1: m_scaleAxis.maximum(), p2: maximumScale))
96 return;
97
98 m_scaleAxis.setMaximum(maximumScale);
99 emit maximumScaleChanged();
100}
101#endif
102
103/*!
104 \readonly
105 \qmlproperty real QtQuick::PinchHandler::activeScale
106
107 The scale factor while the pinch gesture is being performed.
108 It is 1.0 when the gesture begins, increases as the touchpoints are spread
109 apart, and decreases as the touchpoints are brought together.
110 If \l target is not null, its \l {Item::scale}{scale} will be automatically
111 multiplied by this value.
112 Otherwise, bindings can be used to do arbitrary things with this value.
113
114 \sa QtQuick::PinchHandler::scaleAxis.activeValue
115*/
116
117void QQuickPinchHandler::setActiveScale(qreal scale)
118{
119 if (scale == activeScale())
120 return;
121
122 qreal delta = scale / m_scaleAxis.activeValue();
123 m_scaleAxis.updateValue(activeValue: scale, accumulatedValue: m_scaleAxis.m_startValue * scale, delta);
124 emit scaleChanged(delta);
125}
126
127/*!
128 \qmlsignal QtQuick::PinchHandler::scaleChanged(qreal delta)
129
130 The \c scaleChanged signal is emitted when \l activeScale (and therefore
131 \l persistentScale) changes. The \a delta value gives the multiplicative
132 change in scale. For example, if the user moves fingers to change the pinch
133 distance so that \c activeScale changes from 2 to 2.5, \c
134 scaleChanged(1.25) will be emitted. You can use that to incrementally
135 change the scale of an item:
136
137 \snippet pointerHandlers/pinchHandlerScaleOrRotationChanged.qml 0
138
139 \note If you set the \l persistentScale property directly, \c delta is \c 1.
140*/
141
142/*!
143 \readonly
144 \qmlproperty QVector2D QtQuick::PinchHandler::scale
145 \deprecated [6.5] Use persistentScale
146*/
147
148/*!
149 \qmlproperty real QtQuick::PinchHandler::persistentScale
150
151 The scale factor that will automatically be set on the \l target if it is not null.
152 Otherwise, bindings can be used to do arbitrary things with this value.
153 While the pinch gesture is being performed, it is continuously multiplied by
154 \l activeScale; after the gesture ends, it stays the same; and when the next
155 pinch gesture begins, it begins to be multiplied by activeScale again.
156
157 It's possible to set this property, as a way of synchronizing the basis
158 scale with a scale that was set in some other way, for example by another
159 handler. If you set this property directly, \c activeScale does not change,
160 and \c scaleChanged(1) is emitted.
161*/
162
163void QQuickPinchHandler::setPersistentScale(qreal scale)
164{
165 if (scale == persistentScale())
166 return;
167
168 m_scaleAxis.updateValue(activeValue: m_scaleAxis.activeValue(), accumulatedValue: scale);
169 emit scaleChanged(delta: 1);
170}
171
172#if QT_DEPRECATED_SINCE(6, 5)
173/*!
174 \qmlproperty real QtQuick::PinchHandler::minimumRotation
175 \deprecated [6.5] Use rotationAxis.minimum
176
177 The minimum acceptable \l {Item::rotation}{rotation} to be applied
178 to the \l target.
179*/
180void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
181{
182 if (qFuzzyCompare(p1: m_rotationAxis.minimum(), p2: minimumRotation))
183 return;
184
185 m_rotationAxis.setMinimum(minimumRotation);
186 emit minimumRotationChanged();
187}
188
189/*!
190 \qmlproperty real QtQuick::PinchHandler::maximumRotation
191 \deprecated [6.5] Use rotationAxis.maximum
192
193 The maximum acceptable \l {Item::rotation}{rotation} to be applied
194 to the \l target.
195*/
196void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation)
197{
198 if (qFuzzyCompare(p1: m_rotationAxis.maximum(), p2: maximumRotation))
199 return;
200
201 m_rotationAxis.setMaximum(maximumRotation);
202 emit maximumRotationChanged();
203}
204#endif
205
206/*!
207 \qmlsignal QtQuick::PinchHandler::rotationChanged(qreal delta)
208
209 The \c rotationChanged signal is emitted when \l activeRotation (and
210 therefore \l persistentRotation) changes. The \a delta value gives the
211 additive change in rotation. For example, if the user moves fingers to
212 change the pinch distance so that \c activeRotation changes from 10 to 30
213 degrees, \c rotationChanged(20) will be emitted. You can use that to
214 incrementally change the rotation of an item:
215
216 \snippet pointerHandlers/pinchHandlerScaleOrRotationChanged.qml 0
217
218 \note If you set the \l persistentRotation property directly, \c delta is \c 0.
219*/
220
221/*!
222 \readonly
223 \qmlproperty QVector2D QtQuick::PinchHandler::rotation
224 \deprecated [6.5] Use activeRotation
225*/
226
227/*!
228 \readonly
229 \qmlproperty real QtQuick::PinchHandler::activeRotation
230
231 The rotation of the pinch gesture in degrees, with positive values clockwise.
232 It is \c 0 when the gesture begins. If \l target is not null, this will be
233 automatically added to its \l {Item::rotation}{rotation}. Otherwise,
234 bindings can be used to do arbitrary things with this value.
235
236 \sa QtQuick::PinchHandler::rotationAxis.activeValue
237*/
238
239void QQuickPinchHandler::setActiveRotation(qreal rot)
240{
241 if (rot == activeRotation())
242 return;
243
244 qreal delta = rot - m_rotationAxis.activeValue();
245 m_rotationAxis.updateValue(activeValue: rot, accumulatedValue: m_rotationAxis.m_startValue + rot, delta);
246 emit rotationChanged(delta);
247}
248
249/*!
250 \qmlproperty real QtQuick::PinchHandler::persistentRotation
251
252 The rotation to be applied to the \l target if it is not null.
253 Otherwise, bindings can be used to do arbitrary things with this value.
254 While the pinch gesture is being performed, \l activeRotation is continuously
255 added; after the gesture ends, it stays the same; and when the next
256 pinch gesture begins, it begins to be modified by activeRotation again.
257
258 It's possible to set this property, as a way of synchronizing the basis
259 rotation with a rotation that was set in some other way, for example by
260 another handler. If you set this property directly, \c activeRotation does
261 not change, and \c rotationChanged(0) is emitted.
262*/
263
264void QQuickPinchHandler::setPersistentRotation(qreal rot)
265{
266 if (rot == persistentRotation())
267 return;
268
269 m_rotationAxis.updateValue(activeValue: m_rotationAxis.activeValue(), accumulatedValue: rot);
270 emit rotationChanged(delta: 0);
271}
272
273/*!
274 \qmlsignal QtQuick::PinchHandler::translationChanged(QVector2D delta)
275
276 The \c translationChanged signal is emitted when \l activeTranslation (and
277 therefore \l persistentTranslation) changes. The \a delta vector gives the
278 change in translation. You can use that to incrementally change the
279 position of an item:
280
281 \snippet pointerHandlers/pinchHandlerNullTarget.qml 0
282
283 \note If you set the \l persistentTranslation property directly,
284 \c delta is \c {0, 0}.
285*/
286
287/*!
288 \readonly
289 \qmlproperty QVector2D QtQuick::PinchHandler::translation
290 \deprecated [6.5] Use activeTranslation
291*/
292/*!
293 \readonly
294 \qmlproperty QPointF QtQuick::PinchHandler::activeTranslation
295
296 The translation of the cluster of points while the pinch gesture is being
297 performed. It is \c {0, 0} when the gesture begins, and increases as the
298 \l {eventPoint}{eventPoint(s)} are dragged downward and to the right. After the gesture
299 ends, it stays the same; and when the next pinch gesture begins, it is
300 reset to \c {0, 0} again.
301
302 \note On some touchpads, such as on a \macos trackpad, native gestures do
303 not generate any translation values, and this property stays at \c (0, 0).
304*/
305
306/*!
307 \qmlproperty QPointF QtQuick::PinchHandler::persistentTranslation
308
309 The translation to be applied to the \l target if it is not \c null.
310 Otherwise, bindings can be used to do arbitrary things with this value.
311 While the pinch gesture is being performed, \l activeTranslation is
312 continuously added to it; after the gesture ends, it stays the same.
313
314 It's possible to set this property, as a way of synchronizing the basis
315 translation with a translation that was set in some other way, for example
316 by another handler. If you set this property directly, \c activeTranslation
317 does not change, and \c translationChanged({0, 0}) is emitted.
318
319 \note On some touchpads, such as on a \macos trackpad, native gestures do
320 not generate any translation values, and this property stays at \c (0, 0).
321*/
322
323void QQuickPinchHandler::setPersistentTranslation(const QPointF &trans)
324{
325 if (trans == persistentTranslation())
326 return;
327
328 m_xAxis.updateValue(activeValue: m_xAxis.activeValue(), accumulatedValue: trans.x());
329 m_yAxis.updateValue(activeValue: m_yAxis.activeValue(), accumulatedValue: trans.y());
330 emit translationChanged(delta: {});
331}
332
333bool QQuickPinchHandler::wantsPointerEvent(QPointerEvent *event)
334{
335 if (!QQuickMultiPointHandler::wantsPointerEvent(event))
336 return false;
337
338#if QT_CONFIG(gestures)
339 if (event->type() == QEvent::NativeGesture) {
340 const auto gesture = static_cast<const QNativeGestureEvent *>(event);
341 if (!gesture->fingerCount() || (gesture->fingerCount() >= minimumPointCount() &&
342 gesture->fingerCount() <= maximumPointCount())) {
343 switch (gesture->gestureType()) {
344 case Qt::BeginNativeGesture:
345 case Qt::EndNativeGesture:
346 case Qt::ZoomNativeGesture:
347 case Qt::RotateNativeGesture:
348 return parentContains(point: event->point(i: 0));
349 default:
350 return false;
351 }
352 } else {
353 return false;
354 }
355 }
356#endif
357
358 return true;
359}
360
361/*!
362 \qmlpropertygroup QtQuick::PinchHandler::xAxis
363 \qmlproperty real QtQuick::PinchHandler::xAxis.minimum
364 \qmlproperty real QtQuick::PinchHandler::xAxis.maximum
365 \qmlproperty bool QtQuick::PinchHandler::xAxis.enabled
366 \qmlproperty real QtQuick::PinchHandler::xAxis.activeValue
367
368 \c xAxis controls the constraints for horizontal translation of the \l target item.
369
370 \c minimum is the minimum acceptable x coordinate of the translation.
371 \c maximum is the maximum acceptable x coordinate of the translation.
372 If \c enabled is true, horizontal dragging is allowed.
373
374 The \c activeValueChanged signal is emitted when \c activeValue changes, to
375 provide the increment by which it changed.
376 This is intended for incrementally adjusting one property via multiple handlers.
377
378 \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
379
380 \note The snippet is contrived: PinchHandler already knows how to move,
381 scale and rotate its parent item, but this code achieves different behavior
382 in a less-declarative way, to illustrate how to use \c activeValueChanged
383 in special cases.
384*/
385
386/*!
387 \qmlpropertygroup QtQuick::PinchHandler::yAxis
388 \qmlproperty real QtQuick::PinchHandler::yAxis.minimum
389 \qmlproperty real QtQuick::PinchHandler::yAxis.maximum
390 \qmlproperty bool QtQuick::PinchHandler::yAxis.enabled
391 \qmlproperty real QtQuick::PinchHandler::yAxis.activeValue
392
393 \c yAxis controls the constraints for vertical translation of the \l target item.
394
395 \c minimum is the minimum acceptable y coordinate of the translation.
396 \c maximum is the maximum acceptable y coordinate of the translation.
397 If \c enabled is true, vertical dragging is allowed.
398
399 The \c activeValueChanged signal is emitted when \c activeValue changes, to
400 provide the increment by which it changed.
401 This is intended for incrementally adjusting one property via multiple handlers.
402
403 \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
404
405 \note The snippet is contrived: PinchHandler already knows how to move,
406 scale and rotate its parent item, but this code achieves different behavior
407 in a less-declarative way, to illustrate how to use \c activeValueChanged
408 in special cases.
409*/
410
411/*!
412 \qmlpropertygroup QtQuick::PinchHandler::scaleAxis
413 \qmlproperty real QtQuick::PinchHandler::scaleAxis.minimum
414 \qmlproperty real QtQuick::PinchHandler::scaleAxis.maximum
415 \qmlproperty bool QtQuick::PinchHandler::scaleAxis.enabled
416 \qmlproperty real QtQuick::PinchHandler::scaleAxis.activeValue
417
418 \c scaleAxis controls the constraints for setting the \l {QtQuick::Item::scale}{scale}
419 of the \l target item according to the distance between the touchpoints.
420
421 \c minimum is the minimum acceptable scale.
422 \c maximum is the maximum acceptable scale.
423 If \c enabled is true, scaling is allowed.
424 \c activeValue is the same as \l {QtQuick::PinchHandler::activeScale}.
425
426 The \c activeValueChanged signal is emitted when \c activeValue changes, to
427 provide the multiplier for the incremental change.
428 This is intended for incrementally adjusting one property via multiple handlers.
429
430 \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
431
432 \note The snippet is contrived: PinchHandler already knows how to move,
433 scale and rotate its parent item, but this code achieves different behavior
434 in a less-declarative way, to illustrate how to use \c activeValueChanged
435 in special cases.
436*/
437
438/*!
439 \qmlpropertygroup QtQuick::PinchHandler::rotationAxis
440 \qmlproperty real QtQuick::PinchHandler::rotationAxis.minimum
441 \qmlproperty real QtQuick::PinchHandler::rotationAxis.maximum
442 \qmlproperty bool QtQuick::PinchHandler::rotationAxis.enabled
443 \qmlproperty real QtQuick::PinchHandler::rotationAxis.activeValue
444
445 \c rotationAxis controls the constraints for setting the \l {QtQuick::Item::rotation}{rotation}
446 of the \l target item according to the rotation of the group of touchpoints.
447
448 \c minimum is the minimum acceptable rotation.
449 \c maximum is the maximum acceptable rotation.
450 If \c enabled is true, rotation is allowed.
451 \c activeValue is the same as \l {QtQuick::PinchHandler::activeRotation}.
452
453 The \c activeValueChanged signal is emitted when \c activeValue changes, to
454 provide the increment by which it changed.
455 This is intended for incrementally adjusting one property via multiple handlers.
456
457 \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
458
459 \note The snippet is contrived: PinchHandler already knows how to move,
460 scale and rotate its parent item, but this code achieves different behavior
461 in a less-declarative way, to illustrate how to use \c activeValueChanged
462 in special cases.
463*/
464
465/*!
466 \readonly
467 \qmlproperty bool QtQuick::PinchHandler::active
468
469 This property is \c true when all the constraints (epecially
470 \l {MultiPointHandler::minimumPointCount}{minimumPointCount} and
471 \l {MultiPointHandler::maximumPointCount}{maximumPointCount}) are satisfied
472 and the \l target, if any, is being manipulated.
473*/
474
475void QQuickPinchHandler::onActiveChanged()
476{
477 QQuickMultiPointHandler::onActiveChanged();
478 const bool curActive = active();
479 m_xAxis.onActiveChanged(active: curActive, initActiveValue: 0);
480 m_yAxis.onActiveChanged(active: curActive, initActiveValue: 0);
481 m_scaleAxis.onActiveChanged(active: curActive, initActiveValue: 1);
482 m_rotationAxis.onActiveChanged(active: curActive, initActiveValue: 0);
483
484 if (curActive) {
485 m_startAngles = angles(ref: centroid().sceneGrabPosition());
486 m_startDistance = averageTouchPointDistance(ref: centroid().sceneGrabPosition());
487 m_startTargetPos = target() ? target()->position() : QPointF();
488 qCDebug(lcPinchHandler) << "activated with starting scale" << m_scaleAxis.m_startValue
489 << "rotation" << m_rotationAxis.m_startValue
490 << "target pos" << m_startTargetPos;
491 } else {
492 m_startTargetPos = QPointF();
493 qCDebug(lcPinchHandler) << "deactivated with scale" << m_scaleAxis.m_activeValue << "rotation" << m_rotationAxis.m_activeValue;
494 }
495}
496
497void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
498{
499 QQuickMultiPointHandler::handlePointerEventImpl(event);
500 if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) {
501 for (const QQuickHandlerPoint &p : currentPoints())
502 qCDebug(lcPinchHandler) << Qt::hex << p.id() << p.sceneGrabPosition() << "->" << p.scenePosition();
503 }
504
505 qreal dist = 0;
506#if QT_CONFIG(gestures)
507 if (event->type() == QEvent::NativeGesture) {
508 const auto gesture = static_cast<const QNativeGestureEvent *>(event);
509 mutableCentroid().reset(event, point: event->point(i: 0));
510 switch (gesture->gestureType()) {
511 case Qt::BeginNativeGesture:
512 setActive(true);
513 // Native gestures for 2-finger pinch do not allow dragging, so
514 // the centroid won't move during the gesture, and translation stays at zero
515 return;
516 case Qt::EndNativeGesture:
517 mutableCentroid().reset();
518 setActive(false);
519 emit updated();
520 return;
521 case Qt::ZoomNativeGesture:
522 setActiveScale(m_scaleAxis.activeValue() * (1 + gesture->value()));
523 break;
524 case Qt::RotateNativeGesture:
525 setActiveRotation(m_rotationAxis.activeValue() + gesture->value());
526 break;
527 default:
528 // Nothing of interest (which is unexpected, because wantsPointerEvent() should have returned false)
529 return;
530 }
531 } else
532#endif // QT_CONFIG(gestures)
533 {
534 const bool containsReleasedPoints = event->isEndEvent();
535 QVector<QEventPoint> chosenPoints;
536 for (const QQuickHandlerPoint &p : currentPoints()) {
537 auto ep = event->pointById(id: p.id());
538 Q_ASSERT(ep);
539 chosenPoints << *ep;
540 }
541 if (!active()) {
542 // Verify that at least one of the points has moved beyond threshold needed to activate the handler
543 int numberOfPointsDraggedOverThreshold = 0;
544 QVector2D accumulatedDrag;
545 const QVector2D currentCentroid(centroid().scenePosition());
546 const QVector2D pressCentroid(centroid().scenePressPosition());
547
548 const int dragThreshold = QQuickPointerHandler::dragThreshold();
549 const int dragThresholdSquared = dragThreshold * dragThreshold;
550
551 double accumulatedCentroidDistance = 0; // Used to detect scale
552 if (event->isBeginEvent())
553 m_accumulatedStartCentroidDistance = 0; // Used to detect scale
554
555 float accumulatedMovementMagnitude = 0;
556
557 for (auto &point : chosenPoints) {
558 if (!containsReleasedPoints) {
559 accumulatedDrag += QVector2D(point.scenePressPosition() - point.scenePosition());
560 /*
561 In order to detect a drag, we want to check if all points have moved more or
562 less in the same direction.
563
564 We then take each point, and convert the point to a local coordinate system where
565 the centroid is the origin. This is done both for the press positions and the
566 current positions. We will then have two positions:
567
568 - pressCentroidRelativePosition
569 is the start point relative to the press centroid
570 - currentCentroidRelativePosition
571 is the current point relative to the current centroid
572
573 If those two points are far enough apart, it might not be considered as a drag
574 anymore. (Note that the threshold will matched to the average of the relative
575 movement of all the points). Therefore, a big relative movement will make a big
576 contribution to the average relative movement.
577
578 The algorithm then can be described as:
579 For each point:
580 - Calculate vector pressCentroidRelativePosition (from the press centroid to the press position)
581 - Calculate vector currentCentroidRelativePosition (from the current centroid to the current position)
582 - Calculate the relative movement vector:
583
584 centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition
585
586 and measure its magnitude. Add the magnitude to the accumulatedMovementMagnitude.
587
588 Finally, if the accumulatedMovementMagnitude is below some threshold, it means
589 that the points were stationary or they were moved in parallel (e.g. the hand
590 was moved, but the relative position between each finger remained very much
591 the same). This is then used to rule out if there is a rotation or scale.
592 */
593 QVector2D pressCentroidRelativePosition = QVector2D(point.scenePosition()) - currentCentroid;
594 QVector2D currentCentroidRelativePosition = QVector2D(point.scenePressPosition()) - pressCentroid;
595 QVector2D centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition;
596 accumulatedMovementMagnitude += centroidRelativeMovement.length();
597
598 accumulatedCentroidDistance += qreal(pressCentroidRelativePosition.length());
599 if (event->isBeginEvent())
600 m_accumulatedStartCentroidDistance += qreal((QVector2D(point.scenePressPosition()) - pressCentroid).length());
601 } else {
602 setPassiveGrab(event, point);
603 }
604 if (point.state() == QEventPoint::Pressed) {
605 point.setAccepted(false); // don't stop propagation
606 setPassiveGrab(event, point);
607 }
608 Q_D(QQuickMultiPointHandler);
609 if (d->dragOverThreshold(point))
610 ++numberOfPointsDraggedOverThreshold;
611 }
612
613 const bool requiredNumberOfPointsDraggedOverThreshold =
614 numberOfPointsDraggedOverThreshold >= minimumPointCount() &&
615 numberOfPointsDraggedOverThreshold <= maximumPointCount();
616 accumulatedMovementMagnitude /= currentPoints().size();
617
618 QVector2D avgDrag = accumulatedDrag / currentPoints().size();
619 if (!xAxis()->enabled())
620 avgDrag.setX(0);
621 if (!yAxis()->enabled())
622 avgDrag.setY(0);
623
624 const qreal centroidMovementDelta = qreal((currentCentroid - pressCentroid).length());
625
626 qreal distanceToCentroidDelta = qAbs(t: accumulatedCentroidDistance - m_accumulatedStartCentroidDistance); // Used to detect scale
627 if (numberOfPointsDraggedOverThreshold >= 1) {
628 if (requiredNumberOfPointsDraggedOverThreshold &&
629 avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) {
630 // Drag
631 if (grabPoints(event, points: chosenPoints))
632 setActive(true);
633 } else if (distanceToCentroidDelta > dragThreshold) { // all points should in accumulation have been moved beyond threshold (?)
634 // Scale
635 if (grabPoints(event, points: chosenPoints))
636 setActive(true);
637 } else if (distanceToCentroidDelta < dragThreshold && (centroidMovementDelta < dragThreshold)) {
638 // Rotate
639 // Since it wasn't a scale and if we exceeded the dragthreshold, and the
640 // centroid didn't moved much, the points must have been moved around the centroid.
641 if (grabPoints(event, points: chosenPoints))
642 setActive(true);
643 }
644 }
645 if (!active())
646 return;
647 }
648
649 // avoid mapping the minima and maxima, as they might have unmappable values
650 // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords.
651 // 1. scale
652 qreal activeScale = 1;
653 if (m_scaleAxis.enabled()) {
654 dist = averageTouchPointDistance(ref: centroid().scenePosition());
655 activeScale = dist / m_startDistance;
656 activeScale = qBound(min: m_scaleAxis.minimum() / m_scaleAxis.m_startValue, val: activeScale,
657 max: m_scaleAxis.maximum() / m_scaleAxis.m_startValue);
658 setActiveScale(activeScale);
659 }
660
661 // 2. rotate
662 if (m_rotationAxis.enabled()) {
663 QVector<PointData> newAngles = angles(ref: centroid().scenePosition());
664 const qreal angleDelta = averageAngleDelta(old: m_startAngles, newAngles);
665 setActiveRotation(m_rotationAxis.m_activeValue + angleDelta);
666 m_startAngles = std::move(newAngles);
667 }
668
669 if (!containsReleasedPoints)
670 acceptPoints(points: chosenPoints);
671 }
672
673
674 if (target() && target()->parentItem()) {
675 auto *t = target();
676 const QPointF centroidParentPos = t->parentItem()->mapFromScene(point: centroid().scenePosition());
677 // 3. Drag/translate
678 const QPointF centroidStartParentPos = t->parentItem()->mapFromScene(point: centroid().sceneGrabPosition());
679 auto activeTranslation = centroidParentPos - centroidStartParentPos;
680 // apply rotation + scaling around the centroid - then apply translation.
681 QPointF pos = QQuickItemPrivate::get(item: t)->adjustedPosForTransform(centroid: centroidParentPos,
682 startPos: m_startTargetPos, activeTranslatation: QVector2D(activeTranslation),
683 startScale: t->scale(), activeScale: m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
684 startRotation: t->rotation(), activeRotation: m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
685
686 if (xAxis()->enabled())
687 pos.setX(qBound(min: xAxis()->minimum(), val: pos.x(), max: xAxis()->maximum()));
688 else
689 pos.rx() -= qreal(activeTranslation.x());
690 if (yAxis()->enabled())
691 pos.setY(qBound(min: yAxis()->minimum(), val: pos.y(), max: yAxis()->maximum()));
692 else
693 pos.ry() -= qreal(activeTranslation.y());
694
695 const QVector2D delta(activeTranslation.x() - m_xAxis.activeValue(),
696 activeTranslation.y() - m_yAxis.activeValue());
697 m_xAxis.updateValue(activeValue: activeTranslation.x(), accumulatedValue: m_xAxis.persistentValue() + delta.x(), delta: delta.x());
698 m_yAxis.updateValue(activeValue: activeTranslation.y(), accumulatedValue: m_yAxis.persistentValue() + delta.y(), delta: delta.y());
699 emit translationChanged(delta);
700 t->setPosition(pos);
701 t->setRotation(m_rotationAxis.persistentValue());
702 t->setScale(m_scaleAxis.persistentValue());
703 } else {
704 auto activeTranslation = centroid().scenePosition() - centroid().scenePressPosition();
705 auto accumulated = QPointF(m_xAxis.m_startValue, m_yAxis.m_startValue) + activeTranslation;
706 const QVector2D delta(activeTranslation.x() - m_xAxis.activeValue(),
707 activeTranslation.y() - m_yAxis.activeValue());
708 m_xAxis.updateValue(activeValue: activeTranslation.x(), accumulatedValue: accumulated.x(), delta: delta.x());
709 m_yAxis.updateValue(activeValue: activeTranslation.y(), accumulatedValue: accumulated.y(), delta: delta.y());
710 emit translationChanged(delta);
711 }
712
713 qCDebug(lcPinchHandler) << "centroid" << centroid().scenePressPosition() << "->" << centroid().scenePosition()
714 << ", distance" << m_startDistance << "->" << dist
715 << ", scale" << m_scaleAxis.m_startValue << "->" << m_scaleAxis.m_accumulatedValue
716 << ", rotation" << m_rotationAxis.m_startValue << "->" << m_rotationAxis.m_accumulatedValue
717 << ", translation" << persistentTranslation()
718 << " from " << event->device()->type();
719
720 emit updated();
721}
722
723/*!
724 \internal
725 \qmlproperty flags QtQuick::PinchHandler::acceptedButtons
726
727 This property is not used in PinchHandler.
728*/
729
730/*!
731 \readonly
732 \qmlproperty QtQuick::handlerPoint QtQuick::PinchHandler::centroid
733
734 A point exactly in the middle of the currently-pressed touch points.
735 The \l target will be rotated around this point.
736*/
737
738QT_END_NAMESPACE
739
740#include "moc_qquickpinchhandler_p.cpp"
741

source code of qtdeclarative/src/quick/handlers/qquickpinchhandler.cpp