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:
parent
7f7d87c68d
commit
f228af06c2
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,3 +19,4 @@ OTHER_FILES += data/DragAnywhereSlider.qml \
|
|||
data/grabberstate.qml \
|
||||
data/multipleSliders.qml \
|
||||
data/reparenting.qml \
|
||||
data/snapMode.qml \
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue