aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikko Hallamaa <mikko.hallamaa@qt.io>2024-06-25 15:59:10 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2024-08-22 10:33:58 +0000
commit2ecfee540999aeedc44405ad0f172cddcccc6a45 (patch)
treea8d294773827c7ca87e0d6df977e416e873169c2
parent866f91ac19f2d2c69fbe814fbbbfc3b6cda31c60 (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.cpp40
-rw-r--r--tests/auto/quick/qquickflickable/data/nestedWheel.qml18
-rw-r--r--tests/auto/quick/qquickflickable/tst_qquickflickable.cpp69
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"