diff options
author | Jan Arve Sæther <jan-arve.saether@qt.io> | 2018-05-22 14:59:08 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2018-08-02 17:48:14 +0000 |
commit | ea195452e80e4b5a70e8c8cdf96a30581a8dd456 (patch) | |
tree | e92198b486164f3373364461842bb77ad9bead33 | |
parent | fe1a259be66835bc937890f3ed62bb2e8496d0f1 (diff) |
Make pinch handler not activate on dragging
..when dragging (translation) is disabled
In order to do this, we had to integrate QQuickAxis to the PinchHandler
which allows enabling/disabling x and y axis individually.
This allows us to have one item with PinchHandler with translation
disabled (but to only handle rotate and scale) together with a two-finger
drag handler.
Change-Id: I1581c575ffba2e5d007163bec36e5699bdd86cbc
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
5 files changed, 121 insertions, 9 deletions
diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index 75a2847aa0..1c3dbf9902 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -47,6 +47,7 @@ #include <QMouseEvent> #include <QDebug> #include <qpa/qplatformnativeinterface.h> +#include <math.h> QT_BEGIN_NAMESPACE @@ -334,7 +335,7 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } else #endif // QT_CONFIG(gestures) { - bool containsReleasedPoints = event->isReleaseEvent(); + const bool containsReleasedPoints = event->isReleaseEvent(); QVector<QQuickEventPoint *> chosenPoints; for (const QQuickHandlerPoint &p : m_currentPoints) { QQuickEventPoint *ep = event->pointById(p.id()); @@ -342,10 +343,65 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } if (!active()) { // Verify that at least one of the points has moved beyond threshold needed to activate the handler + int numberOfPointsDraggedOverThreshold = 0; + QVector2D accumulatedDrag; + const QVector2D currentCentroid(m_centroid.scenePosition()); + const QVector2D pressCentroid(m_centroid.scenePressPosition()); + + QStyleHints *styleHints = QGuiApplication::styleHints(); + const int dragThreshold = styleHints->startDragDistance(); + const int dragThresholdSquared = dragThreshold * dragThreshold; + + double accumulatedCentroidDistance = 0; // Used to detect scale + if (event->isPressEvent()) + m_accumulatedStartCentroidDistance = 0; // Used to detect scale + + float accumulatedMovementMagnitude = 0; + for (QQuickEventPoint *point : qAsConst(chosenPoints)) { - if (!containsReleasedPoints && QQuickWindowPrivate::dragOverThreshold(point) && grabPoints(chosenPoints)) { - setActive(true); - break; + if (!containsReleasedPoints) { + accumulatedDrag += QVector2D(point->scenePressPosition() - point->scenePosition()); + /* + In order to detect a drag, we want to check if all points have moved more or + less in the same direction. + + We then take each point, and convert the point to a local coordinate system where + the centroid is the origin. This is done both for the press positions and the + current positions. We will then have two positions: + + - pressCentroidRelativePosition + is the start point relative to the press centroid + - currentCentroidRelativePosition + is the current point relative to the current centroid + + If those two points are far enough apart, it might not be considered as a drag + anymore. (Note that the threshold will matched to the average of the relative + movement of all the points). Therefore, a big relative movement will make a big + contribution to the average relative movement. + + The algorithm then can be described as: + For each point: + - Calculate vector pressCentroidRelativePosition (from the press centroid to the press position) + - Calculate vector currentCentroidRelativePosition (from the current centroid to the current position) + - Calculate the relative movement vector: + + centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition + + and measure its magnitude. Add the magnitude to the accumulatedMovementMagnitude. + + Finally, if the accumulatedMovementMagnitude is below some threshold, it means + that the points were stationary or they were moved in parallel (e.g. the hand + was moved, but the relative position between each finger remained very much + the same). This is then used to rule out if there is a rotation or scale. + */ + QVector2D pressCentroidRelativePosition = QVector2D(point->scenePosition()) - currentCentroid; + QVector2D currentCentroidRelativePosition = QVector2D(point->scenePressPosition()) - pressCentroid; + QVector2D centroidRelativeMovement = currentCentroidRelativePosition - pressCentroidRelativePosition; + accumulatedMovementMagnitude += centroidRelativeMovement.length(); + + accumulatedCentroidDistance += pressCentroidRelativePosition.length(); + if (event->isPressEvent()) + m_accumulatedStartCentroidDistance += (QVector2D(point->scenePressPosition()) - pressCentroid).length(); } else { setPassiveGrab(point); } @@ -353,11 +409,44 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) point->setAccepted(false); // don't stop propagation setPassiveGrab(point); } + if (QQuickWindowPrivate::dragOverThreshold(point)) + ++numberOfPointsDraggedOverThreshold; + } + + const bool requiredNumberOfPointsDraggedOverThreshold = numberOfPointsDraggedOverThreshold >= minimumPointCount() && numberOfPointsDraggedOverThreshold <= maximumPointCount(); + accumulatedMovementMagnitude /= m_currentPoints.count(); + + QVector2D avgDrag = accumulatedDrag / m_currentPoints.count(); + if (!xAxis()->enabled()) + avgDrag.setX(0); + if (!yAxis()->enabled()) + avgDrag.setY(0); + + const qreal centroidMovementDelta = (currentCentroid - pressCentroid).length(); + + qreal distanceToCentroidDelta = qAbs(accumulatedCentroidDistance - m_accumulatedStartCentroidDistance); // Used to detect scale + if (numberOfPointsDraggedOverThreshold >= 1) { + if (requiredNumberOfPointsDraggedOverThreshold && avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) { + // Drag + if (grabPoints(chosenPoints)) + setActive(true); + } else if (distanceToCentroidDelta > dragThreshold) { // all points should in accumulation have been moved beyond threshold (?) + // Scale + if (grabPoints(chosenPoints)) + setActive(true); + } else if (distanceToCentroidDelta < dragThreshold && (centroidMovementDelta < dragThreshold)) { + // Rotate + // Since it wasn't a scale and if we exceeded the dragthreshold, and the + // centroid didn't moved much, the points must have been moved around the centroid. + if (grabPoints(chosenPoints)) + setActive(true); + } } if (!active()) return; } // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter + // avoid mapping the minima and maxima, as they might have unmappable values // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords. // 1. scale @@ -376,7 +465,7 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } QPointF centroidParentPos; - QRectF bounds(m_minimumX, m_minimumY, m_maximumX - m_minimumX, m_maximumY - m_minimumY); + QRectF bounds(QPointF(xAxis()->minimum(), yAxis()->minimum()), QPointF(xAxis()->maximum(), yAxis()->maximum()) ); if (target() && target()->parentItem()) { centroidParentPos = target()->parentItem()->mapFromScene(m_centroid.scenePosition()); centroidParentPos = QPointF(qBound(bounds.left(), centroidParentPos.x(), bounds.right()), @@ -391,6 +480,10 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) // 3. Drag/translate const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_centroid.sceneGrabPosition()); m_activeTranslation = QVector2D(centroidParentPos - centroidStartParentPos); + if (!xAxis()->enabled()) + m_activeTranslation.setX(0); + if (!yAxis()->enabled()) + m_activeTranslation.setY(0); // apply rotation + scaling around the centroid - then apply translation. QMatrix4x4 mat; diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h index 305802b6cf..6ec119ba72 100644 --- a/src/quick/handlers/qquickpinchhandler_p.h +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -55,6 +55,7 @@ #include "qevent.h" #include "qquickmultipointhandler_p.h" #include <private/qquicktranslate_p.h> +#include "qquickdragaxis_p.h" QT_BEGIN_NAMESPACE @@ -74,6 +75,8 @@ class Q_AUTOTEST_EXPORT QQuickPinchHandler : public QQuickMultiPointHandler Q_PROPERTY(qreal maximumX READ maximumX WRITE setMaximumX NOTIFY maximumXChanged) Q_PROPERTY(qreal minimumY READ minimumY WRITE setMinimumY NOTIFY minimumYChanged) Q_PROPERTY(qreal maximumY READ maximumY WRITE setMaximumY NOTIFY maximumYChanged) + Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT) + Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT) public: enum PinchOrigin { @@ -111,6 +114,9 @@ public: qreal maximumY() const { return m_maximumY; } void setMaximumY(qreal maxY); + QQuickDragAxis *xAxis() { return &m_xAxis; } + QQuickDragAxis *yAxis() { return &m_yAxis; } + signals: void minimumScaleChanged(); void maximumScaleChanged(); @@ -145,6 +151,8 @@ private: qreal m_maximumX = qInf(); qreal m_minimumY = -qInf(); qreal m_maximumY = qInf(); + QQuickDragAxis m_xAxis; + QQuickDragAxis m_yAxis; PinchOrigin m_pinchOrigin = PinchOrigin::PinchCenter; @@ -153,11 +161,13 @@ private: qreal m_startRotation = 0; qreal m_startDistance = 0; QPointF m_startPos; + qreal m_accumulatedStartCentroidDistance = 0; QVector<PointData> m_startAngles; QMatrix4x4 m_startMatrix; QQuickMatrix4x4 m_transform; + }; QT_END_NAMESPACE diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml index f841b45431..f1591a412e 100644 --- a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml @@ -93,6 +93,7 @@ Rectangle { id: dragHandler objectName: "DragHandler" target: container + grabPermissions: PointerHandler.CanTakeOverFromItems } PinchHandler { id: pinch3 @@ -101,6 +102,7 @@ Rectangle { minimumPointCount: 3 minimumScale: 0.1 maximumScale: 10 + grabPermissions: PointerHandler.CanTakeOverFromItems } } } diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp index 5ea5a42044..1c95bb57b4 100644 --- a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp @@ -188,10 +188,15 @@ void tst_MptaInterop::touchesThenPinch() // Start moving: PinchHandler steals the exclusive grab from MPTA as soon as dragThreshold is exceeded int pinchStoleGrab = 0; - for (int i = 0; i < 8; ++i) { - p1 += QPoint(dragThreshold / 2, dragThreshold / 2); - p2 += QPoint(dragThreshold / 2, dragThreshold / 2); - p3 += QPoint(-dragThreshold / 2, dragThreshold / 2); + + const QPointF c = (p1 + p2 + p3)/3; // centroid of p1,p2,p3 + QTransform xform; // transform to rotate around the centroid + xform.translate(c.x(), c.y()).rotate(1).translate(-c.x(), -c.y()); + + for (int i = 0; i < 16; ++i) { + p1 = xform.map(p1); + p2 = xform.map(p2); + p3 = xform.map(p3); touch.move(1, p1).move(2, p2).move(3, p3).commit(); QQuickTouchUtils::flush(window); if (!pinchStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == pinch) { diff --git a/tests/manual/pointer/map.qml b/tests/manual/pointer/map.qml index d5de9eaa39..c400874d58 100644 --- a/tests/manual/pointer/map.qml +++ b/tests/manual/pointer/map.qml @@ -77,6 +77,8 @@ Item { target: map minimumScale: 0.1 maximumScale: 10 + xAxis.enabled: false + yAxis.enabled: false onActiveChanged: if (!active) reRenderIfNecessary() grabPermissions: PinchHandler.TakeOverForbidden // don't allow takeover if pinch has started } |