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