aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/handlers/qquickdraghandler.cpp
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2018-06-11 07:38:46 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2018-07-19 10:56:06 +0000
commitca7cdd71ee33f0d77eb6bf1367d2532e26155cb2 (patch)
treeaf1b30bffea87bedb23e8cd795f23692f29b50a9 /src/quick/handlers/qquickdraghandler.cpp
parentd310ca768bb5f45bae4fcec9a5d8151b6a366b8d (diff)
Make DragHandler a MultiPointHandler
That is, minimumPointCount can now be set to a value > 1 to require multiple fingers to do the dragging, or to track the displacement of multiple fingers to adjust some value (such as the tilt of a map). Task-number: QTBUG-68106 Change-Id: Ib35823e36deb81c8b277d3070fcc758c7c019564 Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
Diffstat (limited to 'src/quick/handlers/qquickdraghandler.cpp')
-rw-r--r--src/quick/handlers/qquickdraghandler.cpp190
1 files changed, 125 insertions, 65 deletions
diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp
index 8bb8612fb3..707b247f72 100644
--- a/src/quick/handlers/qquickdraghandler.cpp
+++ b/src/quick/handlers/qquickdraghandler.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
@@ -43,6 +43,10 @@
QT_BEGIN_NAMESPACE
+static const qreal DragAngleToleranceDegrees = 10;
+
+Q_LOGGING_CATEGORY(lcDragHandler, "qt.quick.handler.drag")
+
/*!
\qmltype DragHandler
\instantiates QQuickDragHandler
@@ -71,13 +75,21 @@ QT_BEGIN_NAMESPACE
\snippet pointerHandlers/dragHandlerNullTarget.qml 0
+ If minimumPointCount and maximumPointCount are set to values larger than 1,
+ the user will need to drag that many fingers in the same direction to start
+ dragging. A multi-finger drag gesture can be detected independently of both
+ a (default) single-finger DragHandler and a PinchHandler on the same Item,
+ and thus can be used to adjust some other feature independently of the
+ usual pinch behavior: for example adjust a tilt transformation, or adjust
+ some other numeric value.
+
At this time, drag-and-drop is not yet supported.
\sa Drag, MouseArea
*/
QQuickDragHandler::QQuickDragHandler(QObject *parent)
- : QQuickSinglePointHandler(parent)
+ : QQuickMultiPointHandler(parent, 1, 1)
{
}
@@ -85,22 +97,15 @@ QQuickDragHandler::~QQuickDragHandler()
{
}
-bool QQuickDragHandler::wantsEventPoint(QQuickEventPoint *point)
-{
- // If we've already been interested in a point, stay interested, even if it has strayed outside bounds.
- return ((point->state() != QQuickEventPoint::Pressed && this->point().id() == point->pointId())
- || QQuickSinglePointHandler::wantsEventPoint(point));
-}
-
-bool QQuickDragHandler::targetContains(QQuickEventPoint *point)
+bool QQuickDragHandler::targetContainsCentroid()
{
Q_ASSERT(parentItem() && target());
- return target()->contains(localTargetPosition(point));
+ return target()->contains(targetCentroidPosition());
}
-QPointF QQuickDragHandler::localTargetPosition(QQuickEventPoint *point)
+QPointF QQuickDragHandler::targetCentroidPosition()
{
- QPointF pos = point->position();
+ QPointF pos = m_centroid.position();
if (target() != parentItem())
pos = parentItem()->mapToItem(target(), pos);
return pos;
@@ -108,82 +113,137 @@ QPointF QQuickDragHandler::localTargetPosition(QQuickEventPoint *point)
void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point)
{
+ QQuickMultiPointHandler::onGrabChanged(grabber, stateChange, point);
if (grabber == this && stateChange == QQuickEventPoint::GrabExclusive) {
// In case the grab got handed over from another grabber, we might not get the Press.
if (!m_pressedInsideTarget) {
if (target())
m_pressTargetPos = QPointF(target()->width(), target()->height()) / 2;
- m_pressScenePos = point->scenePosition();
} else if (m_pressTargetPos.isNull()) {
if (target())
- m_pressTargetPos = localTargetPosition(point);
- m_pressScenePos = point->scenePosition();
+ m_pressTargetPos = targetCentroidPosition();
}
}
- QQuickSinglePointHandler::onGrabChanged(grabber, stateChange, point);
}
void QQuickDragHandler::onActiveChanged()
{
- if (!active()) {
+ QQuickMultiPointHandler::onActiveChanged();
+ if (active()) {
+ if (auto parent = parentItem()) {
+ if (currentEvent()->asPointerTouchEvent())
+ parent->setKeepTouchGrab(true);
+ // tablet and mouse are treated the same by Item's legacy event handling, and
+ // touch becomes synth-mouse for Flickable, so we need to prevent stealing
+ // mouse grab too, whenever dragging occurs in an enabled direction
+ parent->setKeepMouseGrab(true);
+ }
+ } else {
m_pressTargetPos = QPointF();
- m_pressScenePos = m_pressTargetPos;
m_pressedInsideTarget = false;
+ if (auto parent = parentItem()) {
+ parent->setKeepTouchGrab(false);
+ parent->setKeepMouseGrab(false);
+ }
}
}
-void QQuickDragHandler::handleEventPoint(QQuickEventPoint *point)
+void QQuickDragHandler::handlePointerEventImpl(QQuickPointerEvent *event)
{
- point->setAccepted();
- switch (point->state()) {
- case QQuickEventPoint::Pressed:
- if (target()) {
- m_pressedInsideTarget = targetContains(point);
- m_pressTargetPos = localTargetPosition(point);
- }
- m_pressScenePos = point->scenePosition();
- setPassiveGrab(point);
- break;
- case QQuickEventPoint::Updated: {
- QVector2D accumulatedDragDelta = QVector2D(point->scenePosition() - m_pressScenePos);
- if (active()) {
- // update translation property. Make sure axis is respected for it.
- if (!m_xAxis.enabled())
+ QQuickMultiPointHandler::handlePointerEventImpl(event);
+ event->setAccepted(true);
+
+ if (active()) {
+ // Calculate drag delta, taking into account the axis enabled constraint
+ // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
+ QVector2D accumulatedDragDelta = QVector2D(m_centroid.scenePosition() - m_centroid.scenePressPosition());
+ if (!m_xAxis.enabled())
+ accumulatedDragDelta.setX(0);
+ if (!m_yAxis.enabled())
+ accumulatedDragDelta.setY(0);
+ setTranslation(accumulatedDragDelta);
+ } else {
+ // Check that all points have been dragged past the drag threshold,
+ // to the extent that the constraints allow,
+ // and in approximately the same direction
+ qreal minAngle = 361;
+ qreal maxAngle = -361;
+ bool allOverThreshold = !event->isReleaseEvent();
+ QVector <QQuickEventPoint *> chosenPoints;
+
+ if (event->isPressEvent())
+ m_pressedInsideTarget = target() && m_currentPoints.count() > 0;
+
+ for (const QQuickHandlerPoint &p : m_currentPoints) {
+ if (!allOverThreshold)
+ break;
+ QQuickEventPoint *point = event->pointById(p.id());
+ chosenPoints << point;
+ setPassiveGrab(point);
+ // Calculate drag delta, taking into account the axis enabled constraint
+ // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
+ QVector2D accumulatedDragDelta = QVector2D(point->scenePosition() - point->scenePressPosition());
+ if (!m_xAxis.enabled()) {
+ // If horizontal dragging is disallowed, but the user is dragging
+ // mostly horizontally, then don't activate.
+ if (qAbs(accumulatedDragDelta.x()) > qAbs(accumulatedDragDelta.y()))
+ accumulatedDragDelta.setY(0);
accumulatedDragDelta.setX(0);
- if (!m_yAxis.enabled())
+ }
+ if (!m_yAxis.enabled()) {
+ // If vertical dragging is disallowed, but the user is dragging
+ // mostly vertically, then don't activate.
+ if (qAbs(accumulatedDragDelta.y()) > qAbs(accumulatedDragDelta.x()))
+ accumulatedDragDelta.setX(0);
accumulatedDragDelta.setY(0);
- setTranslation(accumulatedDragDelta);
-
- if (target() && target()->parentItem()) {
- const QPointF newTargetTopLeft = localTargetPosition(point) - m_pressTargetPos;
- const QPointF xformOrigin = target()->transformOriginPoint();
- const QPointF targetXformOrigin = newTargetTopLeft + xformOrigin;
- QPointF pos = target()->parentItem()->mapFromItem(target(), targetXformOrigin);
- pos -= xformOrigin;
- QPointF targetItemPos = target()->position();
- if (!m_xAxis.enabled())
- pos.setX(targetItemPos.x());
- if (!m_yAxis.enabled())
- pos.setY(targetItemPos.y());
- enforceAxisConstraints(&pos);
- moveTarget(pos, point);
}
- } else if (!point->exclusiveGrabber() &&
- ((m_xAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(accumulatedDragDelta.x(), Qt::XAxis, point)) ||
- (m_yAxis.enabled() && QQuickWindowPrivate::dragOverThreshold(accumulatedDragDelta.y(), Qt::YAxis, point)))) {
- setExclusiveGrab(point);
- if (auto parent = parentItem()) {
- if (point->pointerEvent()->asPointerTouchEvent())
- parent->setKeepTouchGrab(true);
- // tablet and mouse are treated the same by Item's legacy event handling, and
- // touch becomes synth-mouse for Flickable, so we need to prevent stealing
- // mouse grab too, whenever dragging occurs in an enabled direction
- parent->setKeepMouseGrab(true);
+ qreal angle = std::atan2(accumulatedDragDelta.y(), accumulatedDragDelta.x()) * 180 / M_PI;
+ bool overThreshold = QQuickWindowPrivate::dragOverThreshold(accumulatedDragDelta);
+ qCDebug(lcDragHandler) << "movement" << accumulatedDragDelta << "angle" << angle << "of point" << point
+ << "pressed @" << point->scenePressPosition() << "over threshold?" << overThreshold;
+ minAngle = qMin(angle, minAngle);
+ maxAngle = qMax(angle, maxAngle);
+ if (allOverThreshold && !overThreshold)
+ allOverThreshold = false;
+
+ if (event->isPressEvent()) {
+ // m_pressedInsideTarget should stay true iff ALL points in which DragHandler is interested
+ // have been pressed inside the target() Item. (E.g. in a Slider the parent might be the
+ // whole control while the target is just the knob.)
+ if (target()) {
+ const QPointF localPressPos = target()->mapFromScene(point->scenePressPosition());
+ m_pressedInsideTarget &= target()->contains(localPressPos);
+ m_pressTargetPos = targetCentroidPosition();
+ }
+ // QQuickWindowPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered.
+ // (That affects behavior for mouse but not for touch, because Flickable only handles mouse.)
+ // So we have to compensate by accepting the event here to avoid any parent Flickable from
+ // getting the event via direct delivery and grabbing too soon.
+ point->setAccepted(event->asPointerMouseEvent()); // stop propagation iff it's a mouse event
}
}
- } break;
- default:
- break;
+ if (allOverThreshold) {
+ qreal angleDiff = maxAngle - minAngle;
+ if (angleDiff > 180)
+ angleDiff = 360 - angleDiff;
+ qCDebug(lcDragHandler) << "angle min" << minAngle << "max" << maxAngle << "range" << angleDiff;
+ if (angleDiff < DragAngleToleranceDegrees && grabPoints(chosenPoints))
+ setActive(true);
+ }
+ }
+ if (active() && target() && target()->parentItem()) {
+ const QPointF newTargetTopLeft = targetCentroidPosition() - m_pressTargetPos;
+ const QPointF xformOrigin = target()->transformOriginPoint();
+ const QPointF targetXformOrigin = newTargetTopLeft + xformOrigin;
+ QPointF pos = target()->parentItem()->mapFromItem(target(), targetXformOrigin);
+ pos -= xformOrigin;
+ QPointF targetItemPos = target()->position();
+ if (!m_xAxis.enabled())
+ pos.setX(targetItemPos.x());
+ if (!m_yAxis.enabled())
+ pos.setY(targetItemPos.y());
+ enforceAxisConstraints(&pos);
+ moveTarget(pos);
}
}