Add snapMode to DragHandler

This changes snap behavior slightly. Basically, it does not snap
anymore if the target() item is an ancestor of the parentItem().

In addition, we add a property that enables users to change the behavior.
(SnapIfPressedOutsideTarget has the old behavior)

[ChangeLog][QtQuick][Event Handlers] Added DragHandler.snapMode which can
be used to configure under which conditions the dragged item is snapped
to be below the cursor. The default mode is SnapAuto. The old behavior
can be obtained through the SnapIfPressedOutsideTarget mode.

Fixes: QTBUG-75661
Change-Id: Ibc00e8fbe31b779f8e817af1505e76425467d27a
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
Jan Arve Sæther 2019-05-08 16:56:55 +02:00 committed by Shawn Rutledge
parent 7f7d87c68d
commit f228af06c2
5 changed files with 209 additions and 2 deletions

View File

@ -114,8 +114,14 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEvent
QQuickMultiPointHandler::onGrabChanged(grabber, transition, point);
if (grabber == this && transition == QQuickEventPoint::GrabExclusive && target()) {
// In case the grab got handed over from another grabber, we might not get the Press.
if (!m_pressedInsideTarget) {
if (target() != parentItem())
auto isDescendant = [](QQuickItem *parent, QQuickItem *target) {
return (target != parent) && !target->isAncestorOf(parent);
};
if (m_snapMode == SnapAlways
|| (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget)
|| (m_snapMode == SnapAuto && !m_pressedInsideTarget && isDescendant(parentItem(), target()))
) {
m_pressTargetPos = QPointF(target()->width(), target()->height()) / 2;
} else if (m_pressTargetPos.isNull()) {
m_pressTargetPos = targetCentroidPosition();
@ -123,6 +129,33 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEvent
}
}
/*!
\qmlproperty enumeration QtQuick.DragHandler::snapMode
This property holds the snap mode.
The snap mode configures snapping of the \l target item's center to the event point.
Possible values:
\value DragHandler.SnapNever Never snap
\value DragHandler.SnapAuto The \l target snaps if the event point was pressed outside of the \l target
item \e and the \l target is a descendant of \l parentItem (default)
\value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the event point was pressed outside of the \l target
\value DragHandler.SnapAlways Always snap
*/
QQuickDragHandler::SnapMode QQuickDragHandler::snapMode() const
{
return m_snapMode;
}
void QQuickDragHandler::setSnapMode(QQuickDragHandler::SnapMode mode)
{
if (mode == m_snapMode)
return;
m_snapMode = mode;
emit snapModeChanged();
}
void QQuickDragHandler::onActiveChanged()
{
QQuickMultiPointHandler::onActiveChanged();

View File

@ -62,8 +62,17 @@ class Q_QUICK_PRIVATE_EXPORT QQuickDragHandler : public QQuickMultiPointHandler
Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT)
Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT)
Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged)
Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged)
public:
enum SnapMode {
NoSnap = 0,
SnapAuto,
SnapIfPressedOutsideTarget,
SnapAlways
};
Q_ENUM(SnapMode)
explicit QQuickDragHandler(QQuickItem *parent = nullptr);
void handlePointerEventImpl(QQuickPointerEvent *event) override;
@ -73,11 +82,14 @@ public:
QVector2D translation() const { return m_translation; }
void setTranslation(const QVector2D &trans);
QQuickDragHandler::SnapMode snapMode() const;
void setSnapMode(QQuickDragHandler::SnapMode mode);
void enforceConstraints();
Q_SIGNALS:
void translationChanged();
void snapModeChanged();
protected:
void onActiveChanged() override;
@ -97,6 +109,7 @@ private:
QQuickDragAxis m_xAxis;
QQuickDragAxis m_yAxis;
QQuickDragHandler::SnapMode m_snapMode = SnapAuto;
bool m_pressedInsideTarget = false;
friend class QQuickDragAxis;

View File

@ -0,0 +1,86 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.12
Item {
id: root
objectName: "snapMode"
width: 640
height: 480
Rectangle {
id: rect1
objectName: "rect1"
width: 90
height: 100
x: 100
y: 100
color: "teal"
Rectangle {
width: parent.width/2
height: parent.width/2
x: width/2
y: -x
color: dragHandler1.active ? "red" : "salmon"
DragHandler {
id: dragHandler1
objectName: "dragHandler1"
target: rect1
}
}
}
Rectangle {
id: rect2
objectName: "rect2"
width: 90
height: 100
x: 200
y: 100
color: "teal"
DragHandler {
id: dragHandler2
objectName: "dragHandler2"
target: rect2b
}
Rectangle {
id: rect2b
width: parent.width/2
height: parent.width/2
anchors.horizontalCenter: parent.horizontalCenter
y: -width/2
color: dragHandler2.active ? "red" : "salmon"
}
}
}

View File

@ -19,3 +19,4 @@ OTHER_FILES += data/DragAnywhereSlider.qml \
data/grabberstate.qml \
data/multipleSliders.qml \
data/reparenting.qml \
data/snapMode.qml \

View File

@ -56,6 +56,8 @@ private slots:
void touchDrag();
void mouseDrag();
void dragFromMargin();
void snapMode_data();
void snapMode();
void touchDragMulti();
void touchDragMultiSliders_data();
void touchDragMultiSliders();
@ -284,6 +286,78 @@ void tst_DragHandler::dragFromMargin() // QTBUG-74966
QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton);
}
void tst_DragHandler::snapMode_data()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QTest::addColumn<QString>("subTree");
QTest::addColumn<int>("snapMode");
QTest::addColumn<QPoint>("startDragPos");
QTest::addColumn<QPoint>("dragMovement");
QTest::addColumn<QPoint>("expectedMovement");
struct TestEntry {
const char *desc;
const char *subTree;
QQuickDragHandler::SnapMode mode;
QPoint startDragPos;
QPoint dragMovement;
QPoint expectedMovement;
};
TestEntry testdata[] = {
{"outside the target", "rect1", QQuickDragHandler::SnapAuto, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"inside the target", "rect1", QQuickDragHandler::SnapAuto, QPoint(45, 10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"outside the target", "rect1", QQuickDragHandler::SnapAlways, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, -50-10)},
{"outside the target", "rect1", QQuickDragHandler::NoSnap, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"outside the target", "rect1", QQuickDragHandler::SnapIfPressedOutsideTarget, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, -50-10)},
{"inside the target", "rect1", QQuickDragHandler::SnapIfPressedOutsideTarget, QPoint(45, 10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
//targets y pos moves from -25 to (25 + dragThreshold*2) because of snapping to center:
{"outside target, should snap", "rect2", QQuickDragHandler::SnapAuto, QPoint(45, 50), QPoint(0, dragThreshold*2), QPoint(0, 25 + 25 + dragThreshold*2)},
{"inside target, shouldn't snap", "rect2", QQuickDragHandler::SnapAuto, QPoint(45, 10), QPoint(0, dragThreshold*2), QPoint(0, dragThreshold*2)}
};
for (const TestEntry& e : testdata) {
const QMetaEnum menum = QMetaEnum::fromType<QQuickDragHandler::SnapMode>();
const QString dataTag = QString::fromLatin1("%1, %2, %3").arg(e.subTree).arg(menum.valueToKey(e.mode)).arg(e.desc);
QTest::newRow(dataTag.toUtf8().constData()) << e.subTree << (int)e.mode
<< e.startDragPos << e.dragMovement << e.expectedMovement;
}
}
void tst_DragHandler::snapMode()
{
QFETCH(QString, subTree);
QFETCH(QPoint, startDragPos);
QFETCH(QPoint, dragMovement);
QFETCH(int, snapMode);
QFETCH(QPoint, expectedMovement);
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "snapMode.qml");
QQuickView * window = windowPtr.data();
QQuickItem *rect1 = window->rootObject()->findChild<QQuickItem*>(subTree);
QVERIFY(rect1);
QQuickItem *rect1b = rect1->childItems().first();
QVERIFY(rect1b);
QQuickDragHandler *dragHandler1 = rect1->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler1);
dragHandler1->setSnapMode((QQuickDragHandler::SnapMode)snapMode);
QQuickItem *dragTarget = dragHandler1->target();
QPointF oldTargetPos = dragTarget->position();
QPoint p1 = rect1->mapToScene(QPointF(startDragPos)).toPoint();
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QVERIFY(!dragHandler1->active());
p1 += dragMovement;
QTest::mouseMove(window, p1);
QTRY_VERIFY(dragHandler1->active());
QCOMPARE(dragTarget->position(), oldTargetPos + expectedMovement);
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_VERIFY(!dragHandler1->active());
QCOMPARE(dragHandler1->centroid().pressedButtons(), Qt::NoButton);
}
void tst_DragHandler::touchDragMulti()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();