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

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

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/1de032fed2e5b145205c30b2ef16b9d528c6bc4d

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.