[plasma-mobile/qmlkonsole] src: Implement inertial scrolling for touch in terminal view

Devin Lin <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit b0d72eb8eb70e9c739469dc4dc79b220fa99f90f by Devin Lin.
Committed on 19/07/2026 at 16:18.
Pushed by devinlin into branch 'master'.

Implement inertial scrolling for touch in terminal view

M  +3    -12   src/contents/ui/TerminalPage.qml
A  +125  -0    src/contents/ui/TerminalTouchScrollHandler.qml     [License: GPL(v2.0+)]
M  +1    -0    src/resources.qrc

https://invent.kde.org/plasma-mobile/qmlkonsole/-/commit/b0d72eb8eb70e9c739469dc4dc79b220fa99f90f

diff --git a/src/contents/ui/TerminalPage.qml b/src/contents/ui/TerminalPage.qml
index 42f81fe..502b171 100644
--- a/src/contents/ui/TerminalPage.qml
+++ b/src/contents/ui/TerminalPage.qml
@@ -546,19 +546,10 @@ Kirigami.Page {
                             onLongPressed: terminal.touchSelectionMode = true
                         }
 
-                        // simulate scrolling for touch (TODO velocity)
-                        DragHandler {
-                            acceptedDevices: PointerDevice.TouchScreen
+                        TerminalTouchScrollHandler {
+                            anchors.fill: parent
                             enabled: !terminal.touchSelectionMode
-
-                            property real previousY
-                            onActiveChanged: {
-                                previousY = 0;
-                            }
-                            onTranslationChanged: {
-                                terminal.simulateWheel(0, 0, 0, 0, Qt.point(0, (translation.y - previousY) * 2));
-                                previousY = translation.y;
-                            }
+                            terminalItem: terminal
                         }
                     }
                 }
diff --git a/src/contents/ui/TerminalTouchScrollHandler.qml b/src/contents/ui/TerminalTouchScrollHandler.qml
new file mode 100644
index 0000000..3bc16b4
--- /dev/null
+++ b/src/contents/ui/TerminalTouchScrollHandler.qml
@@ -0,0 +1,125 @@
+// SPDX-FileCopyrightText: 2019-2020 Jonah Brüchert <[email protected]>
+// SPDX-FileCopyrightText: 2021-2022 Devin Lin <[email protected]>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick
+
+import org.kde.konsoleqml
+
+Item {
+    id: root
+
+    required property TerminalEmulator terminalItem
+
+    onEnabledChanged: {
+        if (!enabled) {
+            touchScrollHandler.stopInertia();
+        }
+    }
+
+    TapHandler {
+        acceptedDevices: PointerDevice.TouchScreen
+        onPressedChanged: {
+            if (pressed) {
+                touchScrollHandler.pauseInertia();
+            }
+        }
+        onTapped: touchScrollHandler.stopInertia()
+    }
+
+    DragHandler {
+        id: touchScrollHandler
+
+        acceptedDevices: PointerDevice.TouchScreen
+        target: null
+        xAxis.enabled: false
+
+        property real inertialY
+        property real previousInertialY
+        property real wheelRemainder
+        property real carriedVelocity
+        property real initialInertialVelocity
+        property double inertiaStartedAt
+
+        readonly property real maximumVelocity: 10000
+        readonly property real minimumVelocity: 50
+        readonly property real deceleration: 2500
+
+        function scrollBy(pixelDelta) {
+            wheelRemainder += pixelDelta * 2;
+
+            const wheelDelta = wheelRemainder < 0 ? Math.ceil(wheelRemainder) : Math.floor(wheelRemainder);
+            if (wheelDelta === 0) {
+                return;
+            }
+
+            root.terminalItem.simulateWheel(0, 0, 0, 0, Qt.point(0, wheelDelta));
+            wheelRemainder -= wheelDelta;
+        }
+
+        function pauseInertia() {
+            if (!touchScrollAnimation.running) {
+                return;
+            }
+
+            const elapsed = (Date.now() - inertiaStartedAt) / 1000;
+            carriedVelocity = Math.sign(initialInertialVelocity)
+                * Math.max(0, Math.abs(initialInertialVelocity) - deceleration * elapsed);
+            touchScrollAnimation.stop();
+        }
+
+        function stopInertia() {
+            touchScrollAnimation.stop();
+            carriedVelocity = 0;
+            initialInertialVelocity = 0;
+        }
+
+        onActiveChanged: {
+            if (active) {
+                pauseInertia();
+                return;
+            }
+
+            let velocity = centroid.velocity.y;
+            if (Math.abs(velocity) < minimumVelocity) {
+                stopInertia();
+                return;
+            }
+
+            if (velocity * carriedVelocity > 0) {
+                velocity += carriedVelocity;
+            }
+            velocity = Math.max(-maximumVelocity, Math.min(maximumVelocity, velocity));
+
+            carriedVelocity = 0;
+            initialInertialVelocity = velocity;
+            inertiaStartedAt = Date.now();
+            previousInertialY = 0;
+            inertialY = 0;
+            touchScrollAnimation.to = velocity * Math.abs(velocity) / (2 * deceleration);
+            touchScrollAnimation.duration = Math.round(Math.abs(velocity) / deceleration * 1000);
+            touchScrollAnimation.restart();
+        }
+        yAxis.onActiveValueChanged: delta => {
+            if (active) {
+                scrollBy(delta);
+            }
+        }
+        onInertialYChanged: {
+            scrollBy(inertialY - previousInertialY);
+            previousInertialY = inertialY;
+        }
+    }
+
+    NumberAnimation {
+        id: touchScrollAnimation
+        target: touchScrollHandler
+        property: "inertialY"
+        easing.type: Easing.OutQuad
+        onFinished: {
+            touchScrollHandler.carriedVelocity = 0;
+            touchScrollHandler.initialInertialVelocity = 0;
+        }
+    }
+}
diff --git a/src/resources.qrc b/src/resources.qrc
index d8e5825..30d5e63 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -10,6 +10,7 @@
         <file alias="SettingsPage.qml">contents/ui/SettingsPage.qml</file>
         <file alias="SettingsDialog.qml">contents/ui/SettingsDialog.qml</file>
         <file alias="TerminalPage.qml">contents/ui/TerminalPage.qml</file>
+        <file alias="TerminalTouchScrollHandler.qml">contents/ui/TerminalTouchScrollHandler.qml</file>
         <file alias="TerminalKeyButton.qml">contents/ui/TerminalKeyButton.qml</file>
         <file alias="TerminalKeyToolBar.qml">contents/ui/TerminalKeyToolBar.qml</file>
         <file alias="SavedCommandsSettings.qml">contents/ui/SavedCommandsSettings.qml</file>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.