From f0371487ce4398e99943bd7dae0944941ef0d505 Mon Sep 17 00:00:00 2001 From: Noah Davis Date: Sat, 15 Jan 2022 14:51:34 -0500 Subject: [PATCH] QTabBar: Improve scrolling with high resolution mouse wheels The current behavior for handling the angle delta of a wheel event changes index the instant there is a change in angle delta. This works fine for mouse wheels that send events with 120 angle delta units and there is also already behavior defined for devices with pixel deltas, but there is nothing good for handling events from high resolution mouse wheels that don't have pixel deltas. This patch makes it so that the current index doesn't change until the accumulated angle delta for the X or Y axis reaches 120. [ChangeLog][QtWidgets][QTabBar] Scrolling with a high resolution mouse wheel changes the current index at a rate more like a normal mouse wheel. Task-number: QTBUG-97844 Pick-to: 6.3 Change-Id: I2e7fd88984a253f6ef8a0008deb7233e4cb4d84a Reviewed-by: Shawn Rutledge --- src/widgets/widgets/qtabbar.cpp | 16 ++++++- src/widgets/widgets/qtabbar_p.h | 3 ++ .../widgets/widgets/qtabbar/tst_qtabbar.cpp | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp index 4b1a37d6e2..376355b0d5 100644 --- a/src/widgets/widgets/qtabbar.cpp +++ b/src/widgets/widgets/qtabbar.cpp @@ -1718,6 +1718,9 @@ bool QTabBar::event(QEvent *event) update(d->hoverRect); d->hoverIndex = -1; d->hoverRect = QRect(); +#if QT_CONFIG(wheelevent) + d->accumulatedAngleDelta = QPoint(); +#endif return true; } #if QT_CONFIG(tooltip) @@ -2421,8 +2424,17 @@ void QTabBar::wheelEvent(QWheelEvent *event) } } } else { - const int delta = wheelVertical ? event->angleDelta().y() : event->angleDelta().x(); - const int offset = delta > 0 ? -1 : 1; + d->accumulatedAngleDelta += event->angleDelta(); + const int xSteps = d->accumulatedAngleDelta.x() / QWheelEvent::DefaultDeltasPerStep; + const int ySteps = d->accumulatedAngleDelta.y() / QWheelEvent::DefaultDeltasPerStep; + int offset = 0; + if (xSteps > 0 || ySteps > 0) { + offset = -1; + d->accumulatedAngleDelta = QPoint(); + } else if (xSteps < 0 || ySteps < 0) { + offset = 1; + d->accumulatedAngleDelta = QPoint(); + } const int oldCurrentIndex = d->currentIndex; d->setCurrentNextEnabledIndex(offset); if (oldCurrentIndex != d->currentIndex) { diff --git a/src/widgets/widgets/qtabbar_p.h b/src/widgets/widgets/qtabbar_p.h index 5135839f89..5bc4c15293 100644 --- a/src/widgets/widgets/qtabbar_p.h +++ b/src/widgets/widgets/qtabbar_p.h @@ -102,6 +102,9 @@ public: QRect hoverRect; QPoint dragStartPosition; QPoint mousePosition = {-1, -1}; +#if QT_CONFIG(wheelevent) + QPoint accumulatedAngleDelta; +#endif QSize iconSize; QToolButton* rightB = nullptr; // right or bottom QToolButton* leftB = nullptr; // left or top diff --git a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp index 630b5c1167..f51f0a23d5 100644 --- a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp +++ b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp @@ -103,6 +103,8 @@ private slots: void mouseWheel(); void kineticWheel_data(); void kineticWheel(); + void highResolutionWheel_data(); + void highResolutionWheel(); void scrollButtons_data(); void scrollButtons(); @@ -1106,6 +1108,49 @@ void tst_QTabBar::kineticWheel() } } +void tst_QTabBar::highResolutionWheel_data() +{ + QTest::addColumn("angleDelta"); + // Smallest angleDelta for a Logitech MX Master 3 with Linux/X11/Libinput + QTest::addRow("increment index") << -16; + QTest::addRow("decrement index") << 16; +} + +void tst_QTabBar::highResolutionWheel() +{ + TabBar tabbar; + TabBarScrollingProxyStyle proxyStyle; + tabbar.setStyle(&proxyStyle); + + tabbar.addTab("tab1"); + tabbar.addTab("tab2"); + QFETCH(int, angleDelta); + // Negative values increment, positive values decrement + int startIndex = angleDelta < 0 ? 0 : 1; + tabbar.setCurrentIndex(startIndex); + + const auto systemId = QPointingDevice::primaryPointingDevice()->systemId() + 1; + QPointingDevice hiResWheel( + "test high resolution wheel", systemId, QInputDevice::DeviceType::Mouse, + QPointingDevice::PointerType::Generic, + QInputDevice::Capability::Position | QInputDevice::Capability::Scroll, 1, 3); + + const QPoint wheelPoint = tabbar.rect().bottomRight(); + QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), QPoint(), + QPoint(angleDelta, angleDelta), Qt::NoButton, Qt::NoModifier, + Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication, + &hiResWheel); + + proxyStyle.scrolling = true; + for (int accumulated = 0; accumulated < QWheelEvent::DefaultDeltasPerStep; + accumulated += qAbs(angleDelta)) { + // verify that nothing has changed until the threshold has been reached + QVERIFY(tabbar.currentIndex() == startIndex); + QVERIFY(QApplication::sendEvent(&tabbar, &event)); + } + QVERIFY(tabbar.currentIndex() != startIndex); +} + #endif // QT_CONFIG(wheelevent) void tst_QTabBar::scrollButtons_data()