Qt 6.x
The Qt SDK
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
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
165{
166 if (scale == persistentScale())
167 return;
168
169 m_scaleAxis.updateValue(m_scaleAxis.activeValue(), scale);
171}
172
173#if QT_DEPRECATED_SINCE(6, 5)
181void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
182{
183 if (qFuzzyCompare(m_rotationAxis.minimum(), minimumRotation))
184 return;
185
186 m_rotationAxis.setMinimum(minimumRotation);
188}
189
197void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation)
198{
199 if (qFuzzyCompare(m_rotationAxis.maximum(), maximumRotation))
200 return;
201
202 m_rotationAxis.setMaximum(maximumRotation);
204}
205#endif
206
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
267{
268 if (rot == persistentRotation())
269 return;
270
271 m_rotationAxis.updateValue(m_rotationAxis.activeValue(), rot);
273}
274
326{
327 if (trans == persistentTranslation())
328 return;
329
330 m_xAxis.updateValue(m_xAxis.activeValue(), trans.x());
331 m_yAxis.updateValue(m_yAxis.activeValue(), trans.y());
333}
334
336{
338 return false;
339
340#if QT_CONFIG(gestures)
341 if (event->type() == QEvent::NativeGesture) {
342 const auto gesture = static_cast<const QNativeGestureEvent *>(event);
343 if (!gesture->fingerCount() || (gesture->fingerCount() >= minimumPointCount() &&
344 gesture->fingerCount() <= maximumPointCount())) {
345 switch (gesture->gestureType()) {
350 return parentContains(event->point(0));
351 default:
352 return false;
353 }
354 } else {
355 return false;
356 }
357 }
358#endif
359
360 return true;
361}
362
478{
480 const bool curActive = active();
481 m_xAxis.onActiveChanged(curActive, 0);
482 m_yAxis.onActiveChanged(curActive, 0);
483 m_scaleAxis.onActiveChanged(curActive, 1);
484 m_rotationAxis.onActiveChanged(curActive, 0);
485
486 if (curActive) {
487 m_startAngles = angles(centroid().sceneGrabPosition());
488 m_startDistance = averageTouchPointDistance(centroid().sceneGrabPosition());
489 m_startTargetPos = target() ? target()->position() : QPointF();
490 qCDebug(lcPinchHandler) << "activated with starting scale" << m_scaleAxis.m_startValue
491 << "target scale" << m_scaleAxis.m_startValue << "rotation" << m_rotationAxis.m_startValue
492 << "target pos" << m_startTargetPos;
493 } else {
494 m_startTargetPos = QPointF();
495 qCDebug(lcPinchHandler) << "deactivated with scale" << m_scaleAxis.m_activeValue << "rotation" << m_rotationAxis.m_activeValue;
496 }
497}
498
500{
502 if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) {
503 for (const QQuickHandlerPoint &p : currentPoints())
504 qCDebug(lcPinchHandler) << Qt::hex << p.id() << p.sceneGrabPosition() << "->" << p.scenePosition();
505 }
506
507 qreal dist = 0;
508#if QT_CONFIG(gestures)
509 if (event->type() == QEvent::NativeGesture) {
510 const auto gesture = static_cast<const QNativeGestureEvent *>(event);
511 mutableCentroid().reset(event, event->point(0));
512 switch (gesture->gestureType()) {
514 setActive(true);
515 // Native gestures for 2-finger pinch do not allow dragging, so
516 // the centroid won't move during the gesture, and translation stays at zero
517 return;
520 setActive(false);
521 emit updated();
522 return;
524 setActiveScale(m_scaleAxis.activeValue() * (1 + gesture->value()));
525 break;
527 setActiveRotation(m_rotationAxis.activeValue() + gesture->value());
528 break;
529 default:
530 // Nothing of interest (which is unexpected, because wantsPointerEvent() should have returned false)
531 return;
532 }
533 } else
534#endif // QT_CONFIG(gestures)
535 {
536 const bool containsReleasedPoints = event->isEndEvent();
537 QVector<QEventPoint> chosenPoints;
538 for (const QQuickHandlerPoint &p : currentPoints()) {
539 auto ep = event->pointById(p.id());
540 Q_ASSERT(ep);
541 chosenPoints << *ep;
542 }
543 if (!active()) {
544 // Verify that at least one of the points has moved beyond threshold needed to activate the handler
545 int numberOfPointsDraggedOverThreshold = 0;
546 QVector2D accumulatedDrag;
547 const QVector2D currentCentroid(centroid().scenePosition());
548 const QVector2D pressCentroid(centroid().scenePressPosition());
549
551 const int dragThresholdSquared = dragThreshold * dragThreshold;
552
553 double accumulatedCentroidDistance = 0; // Used to detect scale
554 if (event->isBeginEvent())
555 m_accumulatedStartCentroidDistance = 0; // Used to detect scale
556
557 float accumulatedMovementMagnitude = 0;
558
559 for (auto &point : chosenPoints) {
560 if (!containsReleasedPoints) {
561 accumulatedDrag += QVector2D(point.scenePressPosition() - point.scenePosition());
562 /*
563 In order to detect a drag, we want to check if all points have moved more or
564 less in the same direction.
565
566 We then take each point, and convert the point to a local coordinate system where
567 the centroid is the origin. This is done both for the press positions and the
568 current positions. We will then have two positions:
569
570 - pressCentroidRelativePosition
571 is the start point relative to the press centroid
572 - currentCentroidRelativePosition
573 is the current point relative to the current centroid
574
575 If those two points are far enough apart, it might not be considered as a drag
576 anymore. (Note that the threshold will matched to the average of the relative
577 movement of all the points). Therefore, a big relative movement will make a big
578 contribution to the average relative movement.
579
580 The algorithm then can be described as:
581 For each point:
582 - Calculate vector pressCentroidRelativePosition (from the press centroid to the press position)
583 - Calculate vector currentCentroidRelativePosition (from the current centroid to the current position)
584 - Calculate the relative movement vector:
585
586 centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition
587
588 and measure its magnitude. Add the magnitude to the accumulatedMovementMagnitude.
589
590 Finally, if the accumulatedMovementMagnitude is below some threshold, it means
591 that the points were stationary or they were moved in parallel (e.g. the hand
592 was moved, but the relative position between each finger remained very much
593 the same). This is then used to rule out if there is a rotation or scale.
594 */
595 QVector2D pressCentroidRelativePosition = QVector2D(point.scenePosition()) - currentCentroid;
596 QVector2D currentCentroidRelativePosition = QVector2D(point.scenePressPosition()) - pressCentroid;
597 QVector2D centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition;
598 accumulatedMovementMagnitude += centroidRelativeMovement.length();
599
600 accumulatedCentroidDistance += qreal(pressCentroidRelativePosition.length());
601 if (event->isBeginEvent())
602 m_accumulatedStartCentroidDistance += qreal((QVector2D(point.scenePressPosition()) - pressCentroid).length());
603 } else {
604 setPassiveGrab(event, point);
605 }
606 if (point.state() == QEventPoint::Pressed) {
607 point.setAccepted(false); // don't stop propagation
608 setPassiveGrab(event, point);
609 }
611 if (d->dragOverThreshold(point))
612 ++numberOfPointsDraggedOverThreshold;
613 }
614
615 const bool requiredNumberOfPointsDraggedOverThreshold =
616 numberOfPointsDraggedOverThreshold >= minimumPointCount() &&
617 numberOfPointsDraggedOverThreshold <= maximumPointCount();
618 accumulatedMovementMagnitude /= currentPoints().size();
619
620 QVector2D avgDrag = accumulatedDrag / currentPoints().size();
621 if (!xAxis()->enabled())
622 avgDrag.setX(0);
623 if (!yAxis()->enabled())
624 avgDrag.setY(0);
625
626 const qreal centroidMovementDelta = qreal((currentCentroid - pressCentroid).length());
627
628 qreal distanceToCentroidDelta = qAbs(accumulatedCentroidDistance - m_accumulatedStartCentroidDistance); // Used to detect scale
629 if (numberOfPointsDraggedOverThreshold >= 1) {
630 if (requiredNumberOfPointsDraggedOverThreshold &&
631 avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) {
632 // Drag
633 if (grabPoints(event, chosenPoints))
634 setActive(true);
635 } else if (distanceToCentroidDelta > dragThreshold) { // all points should in accumulation have been moved beyond threshold (?)
636 // Scale
637 if (grabPoints(event, chosenPoints))
638 setActive(true);
639 } else if (distanceToCentroidDelta < dragThreshold && (centroidMovementDelta < dragThreshold)) {
640 // Rotate
641 // Since it wasn't a scale and if we exceeded the dragthreshold, and the
642 // centroid didn't moved much, the points must have been moved around the centroid.
643 if (grabPoints(event, chosenPoints))
644 setActive(true);
645 }
646 }
647 if (!active())
648 return;
649 }
650
651 // avoid mapping the minima and maxima, as they might have unmappable values
652 // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords.
653 // 1. scale
654 qreal activeScale = 1;
655 if (m_scaleAxis.enabled()) {
656 dist = averageTouchPointDistance(centroid().scenePosition());
657 activeScale = dist / m_startDistance;
658 activeScale = qBound(m_scaleAxis.minimum() / m_scaleAxis.m_startValue, activeScale,
659 m_scaleAxis.maximum() / m_scaleAxis.m_startValue);
661 }
662
663 // 2. rotate
664 if (m_rotationAxis.enabled()) {
665 QVector<PointData> newAngles = angles(centroid().scenePosition());
666 const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles);
667 setActiveRotation(m_rotationAxis.m_activeValue + angleDelta);
668 m_startAngles = std::move(newAngles);
669 }
670
671 if (!containsReleasedPoints)
672 acceptPoints(chosenPoints);
673 }
674
675
676 if (target() && target()->parentItem()) {
677 auto *t = target();
678 const QPointF centroidParentPos = t->parentItem()->mapFromScene(centroid().scenePosition());
679 // 3. Drag/translate
680 const QPointF centroidStartParentPos = t->parentItem()->mapFromScene(centroid().sceneGrabPosition());
681 auto activeTranslation = centroidParentPos - centroidStartParentPos;
682 // apply rotation + scaling around the centroid - then apply translation.
684 m_startTargetPos, QVector2D(activeTranslation),
685 t->scale(), m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
686 t->rotation(), m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
687
688 if (xAxis()->enabled())
689 pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum()));
690 else
691 pos.rx() -= qreal(activeTranslation.x());
692 if (yAxis()->enabled())
693 pos.setY(qBound(yAxis()->minimum(), pos.y(), yAxis()->maximum()));
694 else
695 pos.ry() -= qreal(activeTranslation.y());
696
697 const QVector2D delta(activeTranslation.x() - m_xAxis.activeValue(),
698 activeTranslation.y() - m_yAxis.activeValue());
699 m_xAxis.updateValue(activeTranslation.x(), m_xAxis.persistentValue() + delta.x(), delta.x());
700 m_yAxis.updateValue(activeTranslation.y(), m_yAxis.persistentValue() + delta.y(), delta.y());
702 t->setPosition(pos);
703 t->setRotation(m_rotationAxis.persistentValue());
704 t->setScale(m_scaleAxis.persistentValue());
705 } else {
707 auto accumulated = QPointF(m_xAxis.m_startValue, m_yAxis.m_startValue) + activeTranslation;
708 const QVector2D delta(activeTranslation.x() - m_xAxis.activeValue(),
709 activeTranslation.y() - m_yAxis.activeValue());
710 m_xAxis.updateValue(activeTranslation.x(), accumulated.x(), delta.x());
711 m_yAxis.updateValue(activeTranslation.y(), accumulated.y(), delta.y());
713 }
714
715 qCDebug(lcPinchHandler) << "centroid" << centroid().scenePressPosition() << "->" << centroid().scenePosition()
716 << ", distance" << m_startDistance << "->" << dist
717 << ", scale" << m_scaleAxis.m_startValue << "->" << m_scaleAxis.m_accumulatedValue
718 << ", rotation" << m_rotationAxis.m_startValue << "->" << m_rotationAxis.m_accumulatedValue
719 << ", translation" << persistentTranslation()
720 << " from " << event->device()->type();
721
722 emit updated();
723}
724
741
742#include "moc_qquickpinchhandler_p.cpp"
@ NativeGesture
Definition qcoreevent.h:246
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
The QNativeGestureEvent class contains parameters that describe a gesture event. \inmodule QtGui.
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
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)
QPointF adjustedPosForTransform(const QPointF &centroid, const QPointF &startPos, const QVector2D &activeTranslatation, qreal startScale, qreal activeScale, qreal startRotation, qreal activeRotation)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
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)
FINALQPointF persistentTranslation
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)
\readonly \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 lengthSquared() const noexcept
Returns the squared length of the vector from the origin.
Definition qvectornd.h:524
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:501
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
Definition qvectornd.h:505
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
Definition qvectornd.h:504
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:56
@ RotateNativeGesture
@ ZoomNativeGesture
@ BeginNativeGesture
@ EndNativeGesture
#define Q_UNLIKELY(x)
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#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:92
std::uniform_real_distribution dist(1, 2.5)
[2]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent