Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
19
20Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch")
21
22
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)
77void QQuickPinchHandler::setMinimumScale(qreal minimumScale)
78{
79 if (qFuzzyCompare(m_scaleAxis.minimum(), minimumScale))
80 return;
81
82 m_scaleAxis.setMinimum(minimumScale);
84}
85
93void QQuickPinchHandler::setMaximumScale(qreal maximumScale)
94{
95 if (qFuzzyCompare(m_scaleAxis.maximum(), maximumScale))
96 return;
97
98 m_scaleAxis.setMaximum(maximumScale);
100}
101#endif
102
118{
119 if (scale == activeScale())
120 return;
121
122 qreal delta = scale / m_scaleAxis.activeValue();
123 m_scaleAxis.updateValue(scale, m_scaleAxis.m_startValue * scale, delta);
124 emit scaleChanged(delta);
125}
126
164{
165 if (scale == persistentScale())
166 return;
167
168 m_scaleAxis.updateValue(m_scaleAxis.activeValue(), scale);
170}
171
172#if QT_DEPRECATED_SINCE(6, 5)
180void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
181{
182 if (qFuzzyCompare(m_rotationAxis.minimum(), minimumRotation))
183 return;
184
185 m_rotationAxis.setMinimum(minimumRotation);
187}
188
196void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation)
197{
198 if (qFuzzyCompare(m_rotationAxis.maximum(), maximumRotation))
199 return;
200
201 m_rotationAxis.setMaximum(maximumRotation);
203}
204#endif
205
240{
241 if (rot == activeRotation())
242 return;
243
244 qreal delta = rot - m_rotationAxis.activeValue();
245 m_rotationAxis.updateValue(rot, m_rotationAxis.m_startValue + rot, delta);
246 emit rotationChanged(delta);
247}
248
265{
266 if (rot == persistentRotation())
267 return;
268
269 m_rotationAxis.updateValue(m_rotationAxis.activeValue(), rot);
271}
272
324{
325 if (trans == persistentTranslation())
326 return;
327
328 m_xAxis.updateValue(m_xAxis.activeValue(), trans.x());
329 m_yAxis.updateValue(m_yAxis.activeValue(), trans.y());
331}
332
334{
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()) {
348 return parentContains(event->point(0));
349 default:
350 return false;
351 }
352 } else {
353 return false;
354 }
355 }
356#endif
357
358 return true;
359}
360
476{
478 const bool curActive = active();
479 m_xAxis.onActiveChanged(curActive, 0);
480 m_yAxis.onActiveChanged(curActive, 0);
481 m_scaleAxis.onActiveChanged(curActive, 1);
482 m_rotationAxis.onActiveChanged(curActive, 0);
483
484 if (curActive) {
485 m_startAngles = angles(centroid().sceneGrabPosition());
486 m_startDistance = averageTouchPointDistance(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
498{
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, event->point(0));
510 switch (gesture->gestureType()) {
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;
518 setActive(false);
519 emit updated();
520 return;
522 setActiveScale(m_scaleAxis.activeValue() * (1 + gesture->value()));
523 break;
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(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
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 }
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(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, 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, 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, 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(centroid().scenePosition());
655 activeScale = dist / m_startDistance;
656 activeScale = qBound(m_scaleAxis.minimum() / m_scaleAxis.m_startValue, activeScale,
657 m_scaleAxis.maximum() / m_scaleAxis.m_startValue);
659 }
660
661 // 2. rotate
662 if (m_rotationAxis.enabled()) {
663 QVector<PointData> newAngles = angles(centroid().scenePosition());
664 const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles);
665 setActiveRotation(m_rotationAxis.m_activeValue + angleDelta);
666 m_startAngles = std::move(newAngles);
667 }
668
669 if (!containsReleasedPoints)
670 acceptPoints(chosenPoints);
671 }
672
673
674 if (target() && target()->parentItem()) {
675 auto *t = target();
676 const QPointF centroidParentPos = t->parentItem()->mapFromScene(centroid().scenePosition());
677 // 3. Drag/translate
678 const QPointF centroidStartParentPos = t->parentItem()->mapFromScene(centroid().sceneGrabPosition());
679 auto activeTranslation = centroidParentPos - centroidStartParentPos;
680 // apply rotation + scaling around the centroid - then apply translation.
681 QPointF pos = QQuickItemPrivate::get(t)->adjustedPosForTransform(centroidParentPos,
682 m_startTargetPos, QVector2D(activeTranslation),
683 t->scale(), m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
684 t->rotation(), m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
685
686 if (xAxis()->enabled())
687 pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum()));
688 else
689 pos.rx() -= qreal(activeTranslation.x());
690 if (yAxis()->enabled())
691 pos.setY(qBound(yAxis()->minimum(), pos.y(), 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(activeTranslation.x(), m_xAxis.persistentValue() + delta.x(), delta.x());
698 m_yAxis.updateValue(activeTranslation.y(), m_yAxis.persistentValue() + delta.y(), delta.y());
700 t->setPosition(pos);
701 t->setRotation(m_rotationAxis.persistentValue());
702 t->setScale(m_scaleAxis.persistentValue());
703 } else {
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(activeTranslation.x(), accumulated.x(), delta.x());
709 m_yAxis.updateValue(activeTranslation.y(), accumulated.y(), delta.y());
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
739
740#include "moc_qquickpinchhandler_p.cpp"
@ NativeGesture
Definition qcoreevent.h:246
qsizetype size() const noexcept
Definition qlist.h:397
The QNativeGestureEvent class contains parameters that describe a gesture event. \inmodule QtGui.
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
A base class for pointer events.
Definition qevent.h:73
void onActiveChanged(bool active, qreal initActiveValue)
void updateValue(qreal activeValue, qreal accumulatedValue, qreal delta=0)
qreal persistentValue() const
void setMinimum(qreal minimum)
void setMaximum(qreal maximum)
static QQuickItemPrivate * get(QQuickItem *item)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
QPointF position() const
void handlePointerEventImpl(QPointerEvent *event) override
This function can be overridden to implement whatever behavior a specific subclass is intended to hav...
QQuickHandlerPoint & mutableCentroid()
Returns a modifiable reference to the point that will be returned by the \l centroid property.
QList< QQuickHandlerPoint > & currentPoints()
bool wantsPointerEvent(QPointerEvent *event) override
It is the responsibility of this function to decide whether the event could be relevant at all to thi...
qreal averageTouchPointDistance(const QPointF &ref)
void acceptPoints(const QVector< QEventPoint > &points)
QVector< PointData > angles(const QPointF &ref) const
bool grabPoints(QPointerEvent *event, const QVector< QEventPoint > &points)
static qreal averageAngleDelta(const QVector< PointData > &old, const QVector< PointData > &newAngles)
void rotationChanged(qreal delta)
void minimumScaleChanged()
void setPersistentScale(qreal scale)
\qmlsignal QtQuick::PinchHandler::scaleChanged(qreal delta)
void scaleChanged(qreal delta)
void maximumRotationChanged()
void setActiveScale(qreal scale)
\readonly \qmlproperty real QtQuick::PinchHandler::activeScale
bool wantsPointerEvent(QPointerEvent *event) override
It is the responsibility of this function to decide whether the event could be relevant at all to thi...
void handlePointerEventImpl(QPointerEvent *event) override
This function can be overridden to implement whatever behavior a specific subclass is intended to hav...
void onActiveChanged() override
\qmlpropertygroup QtQuick::PinchHandler::xAxis \qmlproperty real QtQuick::PinchHandler::xAxis....
void setPersistentRotation(qreal rot)
\qmlproperty real QtQuick::PinchHandler::persistentRotation
void setPersistentTranslation(const QPointF &trans)
\qmlsignal QtQuick::PinchHandler::translationChanged(QVector2D delta)
void setActiveRotation(qreal rot)
\qmlsignal QtQuick::PinchHandler::rotationChanged(qreal delta)
void translationChanged(QVector2D delta)
void minimumRotationChanged()
void maximumScaleChanged()
bool parentContains(const QEventPoint &point) const
Returns true if margin() > 0 and point is within the margin beyond QQuickItem::boundingRect(),...
QQuickItem * parentItem() const
\qmlproperty Item QtQuick::PointerHandler::parent
void setPassiveGrab(QPointerEvent *event, const QEventPoint &point, bool grab=true)
Acquire or give up a passive grab of the given point, according to the grab state.
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
float length() const noexcept
Returns the length of the vector from the origin.
Definition qvectornd.h:519
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:502
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:501
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
@ NoButton
Definition qnamespace.h:57
@ RotateNativeGesture
@ ZoomNativeGesture
@ BeginNativeGesture
@ EndNativeGesture
#define Q_UNLIKELY(x)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLenum GLuint GLenum GLsizei length
struct _cl_event * event
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
GLenum GLenum GLenum GLenum GLenum scale
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
double qreal
Definition qtypes.h:187
std::uniform_real_distribution dist(1, 2.5)
[2]