From f98dc59164010f95fa5b10bdbc7189203fe7251f Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 3 Jun 2022 10:48:04 +0200 Subject: Flickable: don't grab on press if already moving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit But rather cancelInteraction(). The Flickable stops moving, as before, which feels like correct "physics"; but the tap is also often meant for a delegate or something inside. As a drive-by, optimize slightly: check e->isPointerEvent() and do the casting only once. Fixes: QTBUG-103832 Change-Id: I16cb670a06d94b53e56dd85c910becf747a75f8a Reviewed-by: Jan Arve Sæther (cherry picked from commit 9f9ea6e1837620a0ff4b98e262cba2df44215967) Reviewed-by: Shawn Rutledge --- .../quick/qquicklistview2/data/buttonDelegate.qml | 27 +++++++ .../qquicklistview2/data/mouseAreaDelegate.qml | 30 ++++++++ .../quick/qquicklistview2/tst_qquicklistview2.cpp | 87 ++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 tests/auto/quick/qquicklistview2/data/buttonDelegate.qml create mode 100644 tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml (limited to 'tests/auto/quick') diff --git a/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml b/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml new file mode 100644 index 0000000000..a40ba1cd7e --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/buttonDelegate.qml @@ -0,0 +1,27 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +ListView { + id: root + width: 320 + height: 480 + model: 100 + + property var pressedDelegates: [] + property var releasedDelegates: [] + property var tappedDelegates: [] + property var canceledDelegates: [] + + delegate: Button { + required property int index + objectName: text + text: "button " + index + height: 100 + width: 320 + + onPressed: root.pressedDelegates.push(index) + onReleased: root.releasedDelegates.push(index) + onClicked: root.tappedDelegates.push(index) + onCanceled: root.canceledDelegates.push(index) + } +} diff --git a/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml b/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml new file mode 100644 index 0000000000..ad556913a5 --- /dev/null +++ b/tests/auto/quick/qquicklistview2/data/mouseAreaDelegate.qml @@ -0,0 +1,30 @@ +import QtQuick 2.15 + +ListView { + id: root + width: 320 + height: 480 + model: 100 + + property var pressedDelegates: [] + property var releasedDelegates: [] + property var tappedDelegates: [] + property var canceledDelegates: [] + + delegate: MouseArea { + height: 100 + width: 320 + + onPressed: root.pressedDelegates.push(index) + onReleased: root.releasedDelegates.push(index) + onClicked: root.tappedDelegates.push(index) + onCanceled: root.canceledDelegates.push(index) + + Rectangle { + id: buttonArea + anchors.fill: parent + border.color: "#41cd52" + color: parent.pressed ? "lightsteelblue" : "beige" + } + } +} diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp index 27bf389891..7749e83b47 100644 --- a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp +++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp @@ -38,6 +38,8 @@ #include #include +Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") + using namespace QQuickViewTestUtils; using namespace QQuickVisualTestUtils; @@ -60,6 +62,12 @@ private slots: void sectionsNoOverlap(); void metaSequenceAsModel(); void noCrashOnIndexChange(); + void tapDelegateDuringFlicking_data(); + void tapDelegateDuringFlicking(); + +private: + void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); + QScopedPointer touchDevice = QScopedPointer(QTest::createTouchDevice()); }; tst_QQuickListView2::tst_QQuickListView2() @@ -307,6 +315,85 @@ void tst_QQuickListView2::noCrashOnIndexChange() QCOMPARE(items->property("count").toInt(), 4); } +void tst_QQuickListView2::tapDelegateDuringFlicking_data() +{ + QTest::addColumn("qmlFile"); + QTest::addColumn("boundsBehavior"); + + QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds); + QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds); + QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds); + QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); + QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml") + << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds); +} + +void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832 +{ + QFETCH(QByteArray, qmlFile); + QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData()))); + QQuickListView *listView = qobject_cast(window.rootObject()); + QVERIFY(listView); + listView->setBoundsBehavior(boundsBehavior); + + flickWithTouch(&window, {100, 400}, {100, 100}); + QTRY_VERIFY(listView->contentY() > 501); // let it flick some distance + QVERIFY(listView->isFlicking()); // we want to test the case when it's still moving while we tap + // @y = 400 we pressed the 4th delegate; started flicking, and the press was canceled + QCOMPARE(listView->property("pressedDelegates").toList().first(), 4); + QCOMPARE(listView->property("canceledDelegates").toList().first(), 4); + + // press a delegate during flicking (at y > 501 + 100, so likely delegate 6) + QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 100}); + QQuickTouchUtils::flush(&window); + QTest::touchEvent(&window, touchDevice.data()).release(0, {100, 100}); + QQuickTouchUtils::flush(&window); + + const QVariantList pressedDelegates = listView->property("pressedDelegates").toList(); + const QVariantList releasedDelegates = listView->property("releasedDelegates").toList(); + const QVariantList tappedDelegates = listView->property("tappedDelegates").toList(); + const QVariantList canceledDelegates = listView->property("canceledDelegates").toList(); + + qCDebug(lcTests) << "pressed" << pressedDelegates; // usually [4, 6] + qCDebug(lcTests) << "released" << releasedDelegates; + qCDebug(lcTests) << "tapped" << tappedDelegates; + qCDebug(lcTests) << "canceled" << canceledDelegates; + + // which delegate received the second press, during flicking? + const int lastPressed = pressedDelegates.last().toInt(); + QVERIFY(lastPressed > 5); + QCOMPARE(releasedDelegates.last(), lastPressed); + QCOMPARE(tappedDelegates.last(), lastPressed); + QCOMPARE(canceledDelegates.count(), 1); // only the first press was canceled, not the second +} + +void tst_QQuickListView2::flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to) +{ + QTest::touchEvent(window, touchDevice.data()).press(0, from, window); + QQuickTouchUtils::flush(window); + + QPoint diff = to - from; + for (int i = 1; i <= 8; ++i) { + QTest::touchEvent(window, touchDevice.data()).move(0, from + i * diff / 8, window); + QQuickTouchUtils::flush(window); + } + QTest::touchEvent(window, touchDevice.data()).release(0, to, window); + QQuickTouchUtils::flush(window); +} + class SingletonModel : public QStringListModel { Q_OBJECT -- cgit v1.2.3