diff -Naur a/qtdeclarative/src/quick/items/qquickflickable.cpp b/qtdeclarative/src/quick/items/qquickflickable.cpp
--- a/qtdeclarative/src/quick/items/qquickflickable.cpp	2021-05-25 23:03:09.000000000 +0200
+++ b/qtdeclarative/src/quick/items/qquickflickable.cpp	2021-06-28 12:09:11.301675111 +0200
@@ -53,6 +53,7 @@
 #include <QtGui/qguiapplication.h>
 #include <QtGui/private/qguiapplication_p.h>
 #include <QtGui/qstylehints.h>
+#include <QtGui/qpa/qplatformtheme.h>
 #include <QtCore/qmath.h>
 #include "qplatformdefs.h"
 
@@ -248,7 +249,6 @@
     delete transitionToBounds;
 }
 
-
 QQuickFlickablePrivate::QQuickFlickablePrivate()
   : contentItem(new QQuickItem)
     , hData(this, &QQuickFlickablePrivate::setViewportX)
@@ -1135,6 +1135,7 @@
             if (!vMoved)
                 vData.dragStartOffset = dy;
             qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset);
+
             // Recalculate bounds in case margins have changed, but use the content
             // size estimate taken at the start of the drag in case the drag causes
             // the estimate to be altered
@@ -1194,8 +1195,9 @@
                 vData.move.setValue(newY);
                 vMoved = true;
             }
-            if (!rejectY && overThreshold)
+            if (!rejectY && overThreshold) {
                 stealY = true;
+            }
 
             if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0)
                         || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) {
@@ -1269,8 +1271,9 @@
                 hMoved = true;
             }
 
-            if (!rejectX && overThreshold)
+            if (!rejectX && overThreshold) {
                 stealX = true;
+            }
 
             if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0)
                         || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) {
@@ -1282,8 +1285,9 @@
 
     stealMouse = stealX || stealY;
     if (stealMouse) {
-        if ((stealX && keepX) || (stealY && keepY))
+        if ((stealX && keepX) || (stealY && keepY)) {
             q->setKeepMouseGrab(true);
+        }
         clearDelayedPress();
     }
 
@@ -1425,8 +1429,9 @@
 {
     Q_D(QQuickFlickable);
     if (d->interactive && d->wantsPointerEvent(event)) {
-        if (!d->pressed)
+        if (!d->pressed) {
             d->handleMousePressEvent(event);
+        }
         event->accept();
     } else {
         QQuickItem::mousePressEvent(event);
@@ -1471,6 +1476,21 @@
     }
 }
 
+enum class WheelMomentumSetting {
+    Default = -1,
+    Disabled = 0,
+    Enabled
+};
+
+static WheelMomentumSetting wheelMomentumEnabled()
+{
+    bool envIsSet = true;
+    const bool wheelMomentumEnabled = qEnvironmentVariableIntValue("QT_QUICK_FLICKABLE_WHEEL_MOMENTUM_ENABLED", &envIsSet);
+    if (!envIsSet)
+        return WheelMomentumSetting::Default;
+    return wheelMomentumEnabled ? WheelMomentumSetting::Enabled : WheelMomentumSetting::Disabled;
+}
+
 #if QT_CONFIG(wheelevent)
 void QQuickFlickable::wheelEvent(QWheelEvent *event)
 {
@@ -1515,50 +1535,93 @@
         return;
     }
 
+
+
     if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull()) {
         // physical mouse wheel, so use angleDelta
         int xDelta = event->angleDelta().x();
         int yDelta = event->angleDelta().y();
-        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) {
-                d->flickY(d->vData.velocity);
-                d->flickingStarted(false, true);
-                if (d->vData.flicking) {
-                    d->vMoved = true;
-                    movementStarting();
+
+        /*! \internal
+            Will we get smooth scrolling with momentum, or not?
+
+            |env var|enabled |delta|momentum|
+            |-------|--------|-----|--------|
+            |not set|Default |120x |y       |
+            |not set|Default |other|n       |
+            |0      |Disabled|120x |n       |
+            |0      |Disabled|other|n       |
+            |1      |Enabled |120x |y       |
+            |1      |Enabled |other|y       |
+        */
+        static const WheelMomentumSetting momentumEnvEnabled = wheelMomentumEnabled();
+        bool momentumEnabled = m_momentumEnabled && (momentumEnvEnabled != WheelMomentumSetting::Disabled) && !(xDelta % 120 || yDelta % 120);
+        if (momentumEnabled) {
+            // engage the velocity timeline for smooth scrolling
+            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) {
+                    d->flickY(d->vData.velocity);
+                    d->flickingStarted(false, true);
+                    if (d->vData.flicking) {
+                        d->vMoved = true;
+                        movementStarting();
+                    }
+                    event->accept();
                 }
-                event->accept();
-            }
-        }
-        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) {
-                d->flickX(d->hData.velocity);
-                d->flickingStarted(true, false);
-                if (d->hData.flicking) {
-                    d->hMoved = true;
-                    movementStarting();
+            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) {
+                    d->flickX(d->hData.velocity);
+                    d->flickingStarted(true, false);
+                    if (d->hData.flicking) {
+                        d->hMoved = true;
+                        movementStarting();
+                    }
+                    event->accept();
                 }
+            }
+        } else {
+            m_momentumEnabled = false;
+
+            // if it's a laptop touchpad, ignore velocity and make scroll distance directly proportional to angleDelta
+            if (yDelta > 0)
+                yDelta = qMin(yDelta, int(contentY() + minYExtent()));
+            else if (yDelta < 0)
+                yDelta = qMax(yDelta, int(contentY() + maxYExtent()));
+            if (xDelta > 0)
+                xDelta = qMin(xDelta, int(contentX() + minXExtent()));
+            else if (xDelta < 0)
+                xDelta = qMax(xDelta, int(contentX() + maxXExtent()));
+
+            if ((xflick() && xDelta != 0) || (yflick() && yDelta != 0)) {
+                // if QStyleHints::wheelScrollLines() != 3, scale it accordingly
+                // (Flickable has no idea how many "lines" are being scrolled: depends on font size etc.)
+                static const float defaultWheelScrollLines = QPlatformTheme::defaultThemeHint(QPlatformTheme::WheelScrollLines).toFloat();
+                const float configuredWheelScrollLines = qGuiApp->styleHints()->wheelScrollLines();
+                const float deltaRatio = configuredWheelScrollLines / defaultWheelScrollLines;
+                d->accumulatedWheelPixelDelta += QVector2D(xDelta * deltaRatio, yDelta * deltaRatio);
+                d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta, true, false, false, {});
                 event->accept();
             }
         }
     } else {
-        // use pixelDelta (probably from a trackpad)
+        // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually
         int xDelta = event->pixelDelta().x();
         int yDelta = event->pixelDelta().y();
 
@@ -1575,8 +1638,9 @@
         event->accept();
     }
 
-    if (!event->isAccepted())
+    if (!event->isAccepted()) {
         QQuickItem::wheelEvent(event);
+    }
 }
 #endif
 
@@ -1634,8 +1698,9 @@
             QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(w);
             wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay
             replayingPressEvent = true;
-            if (w->mouseGrabberItem() == q)
+            if (w->mouseGrabberItem() == q) {
                 q->ungrabMouse();
+            }
 
             // Use the event handler that will take care of finding the proper item to propagate the event
             QCoreApplication::sendEvent(w, mouseEvent.data());
@@ -2345,8 +2410,9 @@
     Q_D(QQuickFlickable);
     // if our mouse grab has been removed (probably by another Flickable),
     // fix our state
-    if (!d->replayingPressEvent)
+    if (!d->replayingPressEvent) {
         d->cancelInteraction();
+    }
 }
 
 void QQuickFlickablePrivate::cancelInteraction()
diff -Naur a/qtdeclarative/src/quick/items/qquickflickable_p.h b/qtdeclarative/src/quick/items/qquickflickable_p.h
--- a/qtdeclarative/src/quick/items/qquickflickable_p.h	2021-05-25 23:03:09.000000000 +0200
+++ b/qtdeclarative/src/quick/items/qquickflickable_p.h	2021-06-28 12:09:24.300784394 +0200
@@ -319,6 +319,8 @@
     friend class QQuickFlickableContentItem;
     friend class QQuickFlickableVisibleArea;
     friend class QQuickFlickableReboundTransition;
+
+    bool m_momentumEnabled = true;
 };
 
 QT_END_NAMESPACE
