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