[neon/qt/qtdeclarative/Neon/release] debian: Backport upstream patch to fix Flickable wheel velocity calculation.

Dmitry Shachnev null at kde.org
Thu Jul 14 14:03:40 BST 2022


Git commit b4c0474db54d70a160b2ba9b4e20b04ad1a23e6b by Dmitry Shachnev.
Committed on 21/05/2022 at 22:00.
Pushed by jriddell into branch 'Neon/release'.

Backport upstream patch to fix Flickable wheel velocity calculation.

Thanks Wolfgang Frisch for backporting the patch to 5.15 branch!

Closes: #1011253.

M  +2    -0    debian/changelog
A  +274  -0    debian/patches/fix_flickable_wheel_velocity_calculation.patch
M  +1    -0    debian/patches/series

https://invent.kde.org/neon/qt/qtdeclarative/commit/b4c0474db54d70a160b2ba9b4e20b04ad1a23e6b

diff --git a/debian/changelog b/debian/changelog
index 2d9829f..9f06851 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -2,6 +2,8 @@ qtdeclarative-opensource-src (5.15.4+dfsg-2) UNRELEASED; urgency=medium
 
   [ Dmitry Shachnev ]
   * Use symver directive to catch all private symbols at once.
+  * Backport upstream patch to fix Flickable wheel velocity calculation
+    (closes: #1011253, thanks Wolfgang Frisch).
 
  -- Debian Qt/KDE Maintainers <debian-qt-kde at lists.debian.org>  Sun, 22 May 2022 00:35:13 +0300
 
diff --git a/debian/patches/fix_flickable_wheel_velocity_calculation.patch b/debian/patches/fix_flickable_wheel_velocity_calculation.patch
new file mode 100644
index 0000000..f17a522
--- /dev/null
+++ b/debian/patches/fix_flickable_wheel_velocity_calculation.patch
@@ -0,0 +1,274 @@
+Description: fix Flickable wheel velocity calculation
+Origin: upstream, https://code.qt.io/cgit/qt/qtdeclarative.git/commit/?id=a8fbd865140d4dd1
+Last-Update: 2022-05-22
+
+--- a/src/quick/items/qquickflickable.cpp
++++ b/src/quick/items/qquickflickable.cpp
+@@ -62,6 +62,8 @@
+ QT_BEGIN_NAMESPACE
+ 
+ Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
++Q_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel")
++Q_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity")
+ 
+ // FlickThreshold determines how far the "mouse" must have moved
+ // before we perform a flick.
+@@ -263,7 +265,8 @@ QQuickFlickablePrivate::QQuickFlickableP
+     , deceleration(QML_FLICK_DEFAULTDECELERATION)
+     , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100)
+     , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400)
+-    , flickBoost(1.0), fixupMode(Normal), vTime(0), visibleArea(nullptr)
++    , flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24)
++    , fixupMode(Normal), vTime(0), visibleArea(nullptr)
+     , flickableDirection(QQuickFlickable::AutoFlickDirection)
+     , boundsBehavior(QQuickFlickable::DragAndOvershootBounds)
+     , boundsMovement(QQuickFlickable::FollowBoundsBehavior)
+@@ -531,10 +534,14 @@ void QQuickFlickablePrivate::updateBegin
+     if (atBeginning != vData.atBeginning) {
+         vData.atBeginning = atBeginning;
+         atYBeginningChange = true;
++        if (!vData.moving && atBeginning)
++            vData.smoothVelocity.setValue(0);
+     }
+     if (atEnd != vData.atEnd) {
+         vData.atEnd = atEnd;
+         atYEndChange = true;
++        if (!vData.moving && atEnd)
++            vData.smoothVelocity.setValue(0);
+     }
+ 
+     // Horizontal
+@@ -547,10 +554,14 @@ void QQuickFlickablePrivate::updateBegin
+     if (atBeginning != hData.atBeginning) {
+         hData.atBeginning = atBeginning;
+         atXBeginningChange = true;
++        if (!hData.moving && atBeginning)
++            hData.smoothVelocity.setValue(0);
+     }
+     if (atEnd != hData.atEnd) {
+         hData.atEnd = atEnd;
+         atXEndChange = true;
++        if (!hData.moving && atEnd)
++            hData.smoothVelocity.setValue(0);
+     }
+ 
+     if (vData.extentsChanged) {
+@@ -1489,6 +1500,7 @@ void QQuickFlickable::wheelEvent(QWheelE
+         d->hData.velocity = 0;
+         d->timer.start();
+         d->maybeBeginDrag(currentTimestamp, event->position());
++        d->lastPosTime = -1;
+         break;
+     case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse
+     case Qt::ScrollUpdate:
+@@ -1515,20 +1527,34 @@ void QQuickFlickable::wheelEvent(QWheelE
+         return;
+     }
+ 
++    qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / qreal(1000);
++    if (elapsed <= 0) {
++        d->lastPosTime = currentTimestamp;
++        qCDebug(lcWheel) << "insufficient elapsed time: can't calculate velocity" << elapsed;
++        return;
++    }
++
+     if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull()) {
+-        // physical mouse wheel, so use angleDelta
++        // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta
+         int xDelta = event->angleDelta().x();
+         int yDelta = event->angleDelta().y();
++        // For a single "clicky" wheel event (angleDelta +/- 120),
++        // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
++        // The decel algo from there is
++        // qreal dist = v2 / (accel * 2.0);
++        // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
++        // now solve for dt:
++        // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
++        if (!isMoving())
++            elapsed = 120 / qSqrt(d->deceleration * 2 * d->initialWheelFlickDistance);
+         if (yflick() && yDelta != 0) {
+-            bool valid = false;
+-            if (yDelta > 0 && contentY() > -minYExtent()) {
+-                d->vData.velocity = qMax(yDelta*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4));
+-                valid = true;
+-            } else if (yDelta < 0 && contentY() < -maxYExtent()) {
+-                d->vData.velocity = qMin(yDelta*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
+-                valid = true;
+-            }
+-            if (valid) {
++            qreal instVelocity = yDelta / elapsed;
++            // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
++            if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0))
++                d->vData.velocityBuffer.clear();
++            d->vData.addVelocitySample(instVelocity, d->maxVelocity);
++            d->vData.updateVelocity();
++            if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
+                 d->flickY(d->vData.velocity);
+                 d->flickingStarted(false, true);
+                 if (d->vData.flicking) {
+@@ -1539,15 +1565,13 @@ void QQuickFlickable::wheelEvent(QWheelE
+             }
+         }
+         if (xflick() && xDelta != 0) {
+-            bool valid = false;
+-            if (xDelta > 0 && contentX() > -minXExtent()) {
+-                d->hData.velocity = qMax(xDelta*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4));
+-                valid = true;
+-            } else if (xDelta < 0 && contentX() < -maxXExtent()) {
+-                d->hData.velocity = qMin(xDelta*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
+-                valid = true;
+-            }
+-            if (valid) {
++            qreal instVelocity = xDelta / elapsed;
++            // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
++            if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0))
++                d->hData.velocityBuffer.clear();
++            d->hData.addVelocitySample(instVelocity, d->maxVelocity);
++            d->hData.updateVelocity();
++            if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
+                 d->flickX(d->hData.velocity);
+                 d->flickingStarted(true, false);
+                 if (d->hData.flicking) {
+@@ -1562,18 +1586,13 @@ void QQuickFlickable::wheelEvent(QWheelE
+         int xDelta = event->pixelDelta().x();
+         int yDelta = event->pixelDelta().y();
+ 
+-        qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / 1000.;
+-        if (elapsed <= 0) {
+-            d->lastPosTime = currentTimestamp;
+-            return;
+-        }
+         QVector2D velocity(xDelta / elapsed, yDelta / elapsed);
+-        d->lastPosTime = currentTimestamp;
+         d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta());
+         d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta,
+                 true, !d->scrollingPhase, true, velocity);
+         event->accept();
+     }
++    d->lastPosTime = currentTimestamp;
+ 
+     if (!event->isAccepted())
+         QQuickItem::wheelEvent(event);
+@@ -1744,6 +1763,10 @@ void QQuickFlickable::componentComplete(
+         setContentX(-minXExtent());
+     if (!d->vData.explicitValue && d->vData.startMargin != 0.)
+         setContentY(-minYExtent());
++    if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) {
++        d->timeline.setObjectName(QLatin1String("timeline for Flickable ") + objectName());
++        d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable ") + objectName());
++    }
+ }
+ 
+ void QQuickFlickable::viewportMoved(Qt::Orientations orient)
+@@ -2491,9 +2514,23 @@ void QQuickFlickable::setMaximumFlickVel
+ 
+ /*!
+     \qmlproperty real QtQuick::Flickable::flickDeceleration
+-    This property holds the rate at which a flick will decelerate.
+-
+-    The default value is platform dependent.
++    This property holds the rate at which a flick will decelerate:
++    the higher the number, the faster it slows down when the user stops
++    flicking via touch, touchpad or mouse wheel. For example 0.0001 is nearly
++    "frictionless", and 10000 feels quite "sticky".
++
++    The default value is platform dependent. Values of zero or less are not allowed.
++
++    \note For touchpad flicking, some platforms drive Flickable directly by
++    sending QWheelEvents with QWheelEvent::phase() being \c Qt::ScrollMomentum,
++    after the user has released all fingers from the touchpad. In that case,
++    the operating system is controlling the deceleration, and this property has
++    no effect.
++
++    \note For mouse wheel scrolling, and for gesture scrolling on touchpads
++    that do not have a momentum phase, extremely large values of
++    flickDeceleration can make Flickable very resistant to scrolling,
++    especially if \l maximumFlickVelocity is too small.
+ */
+ qreal QQuickFlickable::flickDeceleration() const
+ {
+@@ -2506,7 +2543,7 @@ void QQuickFlickable::setFlickDecelerati
+     Q_D(QQuickFlickable);
+     if (deceleration == d->deceleration)
+         return;
+-    d->deceleration = deceleration;
++    d->deceleration = qMax(0.001, deceleration);
+     emit flickDecelerationChanged();
+ }
+ 
+--- a/src/quick/items/qquickflickable_p_p.h
++++ b/src/quick/items/qquickflickable_p_p.h
+@@ -241,6 +241,7 @@ public:
+     int pressDelay;
+     int fixupDuration;
+     qreal flickBoost;
++    qreal initialWheelFlickDistance;
+ 
+     enum FixupMode { Normal, Immediate, ExtentChanged };
+     FixupMode fixupMode;
+--- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp
++++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp
+@@ -870,6 +870,7 @@ void tst_qquickflickable::wheel()
+     QVERIFY(flick != nullptr);
+     QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flick);
+     QSignalSpy moveEndSpy(flick, SIGNAL(movementEnded()));
++    quint64 timestamp = 10;
+ 
+     // test a vertical flick
+     {
+@@ -877,6 +878,7 @@ void tst_qquickflickable::wheel()
+         QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(), QPoint(0,-120),
+                           Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
+         event.setAccepted(false);
++        event.setTimestamp(timestamp);
+         QGuiApplication::sendEvent(window.data(), &event);
+     }
+ 
+@@ -887,6 +889,7 @@ void tst_qquickflickable::wheel()
+     QCOMPARE(fp->velocityTimeline.isActive(), false);
+     QCOMPARE(fp->timeline.isActive(), false);
+     QTest::qWait(50); // make sure that onContentYChanged won't sneak in again
++    timestamp += 50;
+     QCOMPARE(flick->property("movementsAfterEnd").value<int>(), 0); // QTBUG-55886
+ 
+     // get ready to test horizontal flick
+@@ -900,8 +903,8 @@ void tst_qquickflickable::wheel()
+         QPoint pos(200, 200);
+         QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(), QPoint(-120,0),
+                           Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
+-
+         event.setAccepted(false);
++        event.setTimestamp(timestamp);
+         QGuiApplication::sendEvent(window.data(), &event);
+     }
+ 
+@@ -926,11 +929,13 @@ void tst_qquickflickable::trackpad()
+     QVERIFY(flick != nullptr);
+     QSignalSpy moveEndSpy(flick, SIGNAL(movementEnded()));
+     QPoint pos(200, 200);
++    quint64 timestamp = 10;
+ 
+     {
+         QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(0,-100), QPoint(0,-120),
+                           Qt::NoButton, Qt::NoModifier, Qt::ScrollBegin, false);
+         event.setAccepted(false);
++        event.setTimestamp(timestamp++);
+         QGuiApplication::sendEvent(window.data(), &event);
+     }
+ 
+@@ -944,6 +949,7 @@ void tst_qquickflickable::trackpad()
+         QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(-100,0), QPoint(-120,0),
+                           Qt::NoButton, Qt::NoModifier, Qt::ScrollUpdate, false);
+         event.setAccepted(false);
++        event.setTimestamp(timestamp++);
+         QGuiApplication::sendEvent(window.data(), &event);
+     }
+ 
+@@ -954,6 +960,7 @@ void tst_qquickflickable::trackpad()
+         QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(0,0), QPoint(0,0),
+                           Qt::NoButton, Qt::NoModifier, Qt::ScrollEnd, false);
+         event.setAccepted(false);
++        event.setTimestamp(timestamp++);
+         QGuiApplication::sendEvent(window.data(), &event);
+     }
+ 
diff --git a/debian/patches/series b/debian/patches/series
index 9024a8b..02be2a5 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -3,6 +3,7 @@ gcc_11.patch
 tst_qmldiskcache_big_endian.patch
 support_apos_in_styled_text.patch
 fix_sweep_step.patch
+fix_flickable_wheel_velocity_calculation.patch
 
 # Debian patches
 disableopengltests.patch



More information about the Neon-commits mailing list