diff options
| author | Mikko Hallamaa <mikko.hallamaa@qt.io> | 2024-06-25 15:59:10 +0200 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2024-08-22 10:33:58 +0000 |
| commit | 2ecfee540999aeedc44405ad0f172cddcccc6a45 (patch) | |
| tree | a8d294773827c7ca87e0d6df977e416e873169c2 | |
| parent | 866f91ac19f2d2c69fbe814fbbbfc3b6cda31c60 (diff) | |
Flickable: Don't consume wheel events towards adjacent bound
When using nested Flickables, inner Flickable consumed wheel events even
when it didn't move the view. Touch input and touchpad scrolling
correctly propagated the events to the outer Flickable.
This patch adds additional logic to the code path handling wheel events
for view movement to not accept the event if the view doesn't move.
Additionally a test for this is added that checks that the outer
Flickable moves in this case.
Fixes: QTBUG-126514
Change-Id: Ia03bd1d0cda1ce417baa479dceddb4a16eafead9
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
(cherry picked from commit 98778f00b93e9fd707e646b104cfdb7ee51021e6)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 74b959e08ccc2d171e7704729bf5ba3caee2c37e)
| -rw-r--r-- | src/quick/items/qquickflickable.cpp | 40 | ||||
| -rw-r--r-- | tests/auto/quick/qquickflickable/data/nestedWheel.qml | 18 | ||||
| -rw-r--r-- | tests/auto/quick/qquickflickable/tst_qquickflickable.cpp | 69 |
3 files changed, 117 insertions, 10 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 0c0f9697da..1c4524c8ca 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1674,22 +1674,31 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. d->vMoved = true; qreal scrollPixel = (-yDelta / 120.0 * wheelScroll); + bool acceptEvent = true; // Set to false if event should propagate to parent if (scrollPixel > 0) { // Forward direction (away from user) - if (d->vData.move.value() >= minYExtent()) + if (d->vData.move.value() >= minYExtent()) { d->vMoved = false; + acceptEvent = false; + } } else { // Backward direction (towards user) - if (d->vData.move.value() <= maxYExtent()) + if (d->vData.move.value() <= maxYExtent()) { d->vMoved = false; + acceptEvent = false; + } } if (d->vMoved) { if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { const qreal estContentPos = scrollPixel + d->vData.move.value(); if (scrollPixel > 0) { // Forward direction (away from user) - if (estContentPos > minYExtent()) + if (estContentPos > minYExtent()) { scrollPixel = minYExtent() - d->vData.move.value(); + acceptEvent = false; + } } else { // Backward direction (towards user) - if (estContentPos < maxYExtent()) + if (estContentPos < maxYExtent()) { scrollPixel = maxYExtent() - d->vData.move.value(); + acceptEvent = false; + } } } d->resetTimeline(d->vData); @@ -1698,28 +1707,38 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) d->vData.fixingUp = true; d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d)); } - event->accept(); + if (acceptEvent) + event->accept(); } if (xflick() && xDelta != 0) { d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. d->hMoved = true; qreal scrollPixel = (-xDelta / 120.0 * wheelScroll); + bool acceptEvent = true; // Set to false if event should propagate to parent if (scrollPixel > 0) { // Forward direction (away from user) - if (d->hData.move.value() >= minXExtent()) + if (d->hData.move.value() >= minXExtent()) { d->hMoved = false; + acceptEvent = false; + } } else { // Backward direction (towards user) - if (d->hData.move.value() <= maxXExtent()) + if (d->hData.move.value() <= maxXExtent()) { d->hMoved = false; + acceptEvent = false; + } } if (d->hMoved) { if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { const qreal estContentPos = scrollPixel + d->hData.move.value(); if (scrollPixel > 0) { // Forward direction (away from user) - if (estContentPos > minXExtent()) + if (estContentPos > minXExtent()) { scrollPixel = minXExtent() - d->hData.move.value(); + acceptEvent = false; + } } else { // Backward direction (towards user) - if (estContentPos < maxXExtent()) + if (estContentPos < maxXExtent()) { scrollPixel = maxXExtent() - d->hData.move.value(); + acceptEvent = false; + } } } d->resetTimeline(d->hData); @@ -1728,7 +1747,8 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) d->hData.fixingUp = true; d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d)); } - event->accept(); + if (acceptEvent) + event->accept(); } } else { // wheelDeceleration is set to some reasonable value: the user or the platform wants to have diff --git a/tests/auto/quick/qquickflickable/data/nestedWheel.qml b/tests/auto/quick/qquickflickable/data/nestedWheel.qml new file mode 100644 index 0000000000..d906ee0472 --- /dev/null +++ b/tests/auto/quick/qquickflickable/data/nestedWheel.qml @@ -0,0 +1,18 @@ +import QtQuick + +MouseArea { + width: 100 + height: 100 + + Flickable { + id: inner + objectName: "innerFlickable" + property bool isInverted: false + anchors.fill: parent + contentWidth: 200 + contentHeight: 200 + contentX: isInverted ? 100 : 0 + contentY: isInverted ? 100 : 0 + boundsBehavior: Flickable.StopAtBounds + } +} diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index b003511356..5fa3e257c6 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -231,6 +231,8 @@ private slots: void proportionalWheelScrolling(); void touchCancel(); void pixelAlignedEndPoints(); + void nestedWheelEventPropagation_data(); + void nestedWheelEventPropagation(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -3432,6 +3434,73 @@ void tst_qquickflickable::pixelAlignedEndPoints() QCOMPARE(isAtEndSpy.count(), 2); QCOMPARE(isAtBeginningSpy.count(), 2);} +void tst_qquickflickable::nestedWheelEventPropagation_data() +{ + QTest::addColumn<bool>("isHorizontal"); + QTest::addColumn<bool>("isInverted"); + QTest::addColumn<bool>("offsetStart"); + QTest::newRow("top") << false << false << false; + QTest::newRow("bottom") << false << true << false; + QTest::newRow("left") << true << false << false; + QTest::newRow("right") << true << true << false; + QTest::newRow("top with offset") << false << false << true; + QTest::newRow("bottom with offset") << false << true << true; + QTest::newRow("left with offset") << true << false << true; + QTest::newRow("right with offset") << true << true << true; +} + +void tst_qquickflickable::nestedWheelEventPropagation() +{ + // Arrange + QFETCH(bool, isHorizontal); + QFETCH(bool, isInverted); + QFETCH(bool, offsetStart); + + QQuickView view; + view.setSource(testFileUrl("nestedWheel.qml")); + QTRY_COMPARE(view.status(), QQuickView::Ready); + QQuickVisualTestUtils::centerOnScreen(&view); + QQuickVisualTestUtils::moveMouseAway(&view); + view.show(); + view.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + QVERIFY(view.rootObject()); + + QQuickMouseArea *outer = qobject_cast<QQuickMouseArea *>(view.rootObject()); + QVERIFY(outer); + + QQuickFlickable *inner = outer->findChild<QQuickFlickable *>("innerFlickable"); + QVERIFY(inner); + inner->setFlickableDirection(isHorizontal ? QQuickFlickable::HorizontalFlick + : QQuickFlickable::VerticalFlick); + + // Setting isInverted property moves the Flickable viewable area to the opposite corner + inner->setProperty("isInverted", isInverted); + QCOMPARE(inner->contentX(), isInverted ? 100 : 0); + QCOMPARE(inner->contentY(), isInverted ? 100 : 0); + + if (offsetStart) { + // Set starting position slightly away from the bound + inner->setContentX(isInverted ? 99.0 : 1.0); + inner->setContentY(isInverted ? 99.0 : 1.0); + } + + QSignalSpy propagateSpy(outer, &QQuickMouseArea::wheel); + + // Act + QPoint position(50, 50); + QWheelEvent event(position, view.mapToGlobal(position), QPoint(), + QPoint(isHorizontal ? (isInverted ? -120 : 120) : 0, + !isHorizontal ? (isInverted ? -120 : 120) : 0), + Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); + event.setAccepted(false); + QGuiApplication::sendEvent(&view, &event); + + // Assert + QCOMPARE(inner->isMoving(), offsetStart); + QCOMPARE(propagateSpy.count(), 1); +} + QTEST_MAIN(tst_qquickflickable) #include "tst_qquickflickable.moc" |
