aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Arve Sæther <jan-arve.saether@qt.io>2018-05-22 14:59:08 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2018-08-02 17:48:14 +0000
commitea195452e80e4b5a70e8c8cdf96a30581a8dd456 (patch)
treee92198b486164f3373364461842bb77ad9bead33
parentfe1a259be66835bc937890f3ed62bb2e8496d0f1 (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>
-rw-r--r--src/quick/handlers/qquickpinchhandler.cpp103
-rw-r--r--src/quick/handlers/qquickpinchhandler_p.h10
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/pinchDragMPTA.qml2
-rw-r--r--tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp13
-rw-r--r--tests/manual/pointer/map.qml2
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
}