[plasma/plasma-mobile] kcms/navigation/ui: kcms/navigation: Navigation Buttons Animations and UI Improvements

Devin Lin <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 6e43a6d5bda88c7f24d9e90b02dcd92ad25533e8 by Devin Lin, on behalf of Micah Stanley.
Committed on 22/07/2026 at 03:51.
Pushed by devinlin into branch 'master'.

kcms/navigation: Navigation Buttons Animations and UI Improvements

- Add tutorial animations for the navbar.
- Move the tutorial pages up top and simplify the code by utilizing a ListView.
- Give the tutorial labels a fixed height so the settings do not move around when changing the tutorial page.
- Make the tutorial pages extend further out and fade out the edges, making it easier to swipe between the pages.
- Move the images used to a sub folder for cleaner file organization.
- Separate the display navbar code and extracted the dummy window code to separate files to help shrink the amount of code contained within `TutorialPhone.qml`.

![Screenshot_20260702_191753](/uploads/a34854bc27e483e2eaf04ffd2e42755a/Screenshot_20260702_191753.png){width=203 height=405}
![Screenshot_20260702_191804](/uploads/b2da1f8c79fbb27408147d1e79760c22/Screenshot_20260702_191804.png){width=203 height=405}

M  +7    -1    kcms/navigation/ui/AnimationHandler.qml
A  +47   -0    kcms/navigation/ui/DummyWindow.qml     [License: GPL(v2.0+)]
A  +175  -0    kcms/navigation/ui/NavigationBar.qml     [License: GPL(v2.0+)]
M  +385  -250  kcms/navigation/ui/TutorialPhone.qml
R  +-    --    kcms/navigation/ui/images/Mascot_konqi-base-plasma.png [from: kcms/navigation/ui/Mascot_konqi-base-plasma.png - 100% similarity]
R  +0    -0    kcms/navigation/ui/images/Mascot_konqi-base-plasma.png.license [from: kcms/navigation/ui/Mascot_konqi-base-plasma.png.license - 100% similarity]
R  +-    --    kcms/navigation/ui/images/katie.png [from: kcms/navigation/ui/katie.png - 100% similarity]
R  +0    -0    kcms/navigation/ui/images/katie.png.license [from: kcms/navigation/ui/katie.png.license - 100% similarity]
R  +-    --    kcms/navigation/ui/images/konqi_kde.png [from: kcms/navigation/ui/konqi_kde.png - 100% similarity]
R  +0    -0    kcms/navigation/ui/images/konqi_kde.png.license [from: kcms/navigation/ui/konqi_kde.png.license - 100% similarity]
M  +127  -165  kcms/navigation/ui/main.qml

https://invent.kde.org/plasma/plasma-mobile/-/commit/6e43a6d5bda88c7f24d9e90b02dcd92ad25533e8

diff --git a/kcms/navigation/ui/AnimationHandler.qml b/kcms/navigation/ui/AnimationHandler.qml
index c610fa079..b7bf1f334 100644
--- a/kcms/navigation/ui/AnimationHandler.qml
+++ b/kcms/navigation/ui/AnimationHandler.qml
@@ -51,14 +51,20 @@ Item {
         if (!running) {
             return;
         }
+
+        running = false;
         loopTimer.stop();
         stepTimer.stop();
-        animations[_step].stop();
+
+        if (_step >= 0 && _step < animations.length) {
+            animations[_step].stop();
+        }
     }
 
     function body(): void {
         if (_step >= animations.length) {
             hasFinished = true;
+            running = false;
             finished();
             return;
         }
diff --git a/kcms/navigation/ui/DummyWindow.qml b/kcms/navigation/ui/DummyWindow.qml
new file mode 100644
index 000000000..cd8ab726b
--- /dev/null
+++ b/kcms/navigation/ui/DummyWindow.qml
@@ -0,0 +1,47 @@
+/*
+    SPDX-FileCopyrightText: 2025 Luis Büchi <[email protected]>
+    SPDX-FileCopyrightText: 2026 Micah Stanley <[email protected]>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import org.kde.kirigami as Kirigami
+
+Rectangle {
+    id: root
+
+    property real windowScale: 1
+    property real offset: 0
+    property string imageSource: ""
+
+    property real baseWidth: 0
+    property real baseHeight: 0
+    property real phoneRadius: 0
+    property real phoneBorderWidth: 0
+
+    Kirigami.Theme.inherit: false
+    Kirigami.Theme.colorSet: Kirigami.Theme.Window
+
+    color: Kirigami.Theme.backgroundColor
+    width: Math.round(baseWidth * windowScale)
+    height: Math.round(baseHeight * windowScale)
+    radius: Math.max(0, phoneRadius - phoneBorderWidth)
+
+    Image {
+        source: root.imageSource
+
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.verticalCenter: parent.verticalCenter
+
+        width: {
+            if (root.width > root.height * 0.8) {
+                return Math.round(root.height * 0.6)
+            }
+            return Math.round(root.width * 0.75)
+        }
+        fillMode: Image.PreserveAspectFit
+    }
+}
diff --git a/kcms/navigation/ui/NavigationBar.qml b/kcms/navigation/ui/NavigationBar.qml
new file mode 100644
index 000000000..acffaba9f
--- /dev/null
+++ b/kcms/navigation/ui/NavigationBar.qml
@@ -0,0 +1,175 @@
+/*
+    SPDX-FileCopyrightText: 2026 Micah Stanley <[email protected]>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Layouts
+import org.kde.kirigami as Kirigami
+
+Item {
+    id: root
+
+    property bool taskSwitcher: false
+    property real phoneRadius: 0
+    property real phoneBorderWidth: 0
+
+    function animateTaskSwitcher() {
+        taskSwitcherButtonTouchFeedbackAnimation.restart();
+    }
+
+    function animateHome() {
+        homeButtonTouchFeedbackAnimation.restart();
+    }
+
+    function animateClose() {
+        closeButtonTouchFeedbackAnimation.restart();
+    }
+
+    function stopAnimations() {
+        taskSwitcherButtonTouchFeedbackAnimation.stop();
+        homeButtonTouchFeedbackAnimation.stop();
+        closeButtonTouchFeedbackAnimation.stop();
+        taskSwitcherButtonTouchFeedback.opacity = 0;
+        homeButtonTouchFeedback.opacity = 0;
+        closeButtonTouchFeedback.opacity = 0;
+    }
+
+    Rectangle {
+        id: navbarBase
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.right: parent.right
+        height: Kirigami.Units.largeSpacing * 2
+
+        radius: root.phoneRadius - root.phoneBorderWidth
+        color: root.taskSwitcher ? "black" : Kirigami.Theme.backgroundColor
+        opacity: root.taskSwitcher ? 0.1 : 1
+
+        layer.enabled: true
+
+        Rectangle {
+            width: navbarBase.width
+            height: navbarBase.radius
+            color: navbarBase.color
+        }
+    }
+
+    RowLayout {
+        id: navbarButtons
+        anchors.top: navbarBase.top
+        anchors.bottom: navbarBase.bottom
+        anchors.horizontalCenter: navbarBase.horizontalCenter
+
+        width: Math.min(navbarBase.width * 0.7, navbarBase.height * 9)
+        uniformCellSizes: true
+
+        // task switcher button
+        Item {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+
+            Rectangle {
+                id: taskSwitcherButtonTouchFeedback
+                radius: height * 0.5
+                anchors.fill: parent
+                anchors.leftMargin: -radius
+                anchors.rightMargin: -radius
+                opacity: 0
+                color: Kirigami.Theme.textColor
+            }
+
+            NumberAnimation {
+                id: taskSwitcherButtonTouchFeedbackAnimation
+                target: taskSwitcherButtonTouchFeedback
+                property: "opacity"
+                from: 0.1
+                to: 0
+                duration: Kirigami.Units.shortDuration
+                easing.type: Easing.InOutQuad
+            }
+
+            Kirigami.Icon {
+                anchors.centerIn: parent
+                implicitHeight: navbarButtons.height * 0.4
+                implicitWidth: navbarButtons.height * 0.4
+                width: implicitWidth
+                height: implicitHeight
+                source: "mobile-task-switcher"
+            }
+        }
+
+        // home button
+        Item {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+
+            Rectangle {
+                id: homeButtonTouchFeedback
+                radius: height * 0.5
+                anchors.fill: parent
+                anchors.leftMargin: -radius
+                anchors.rightMargin: -radius
+                opacity: 0
+                color: Kirigami.Theme.textColor
+            }
+
+            NumberAnimation {
+                id: homeButtonTouchFeedbackAnimation
+                target: homeButtonTouchFeedback
+                property: "opacity"
+                from: 0.1
+                to: 0
+                duration: Kirigami.Units.shortDuration
+                easing.type: Easing.InOutQuad
+            }
+
+            Kirigami.Icon {
+                anchors.centerIn: parent
+                implicitHeight: navbarButtons.height * 0.4
+                implicitWidth: navbarButtons.height * 0.4
+                width: implicitWidth
+                height: implicitHeight
+                source: "start-here-kde"
+            }
+        }
+
+        // close app button
+        Item {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+
+            Rectangle {
+                id: closeButtonTouchFeedback
+                radius: height * 0.5
+                anchors.fill: parent
+                anchors.leftMargin: -radius
+                anchors.rightMargin: -radius
+                opacity: 0
+                color: Kirigami.Theme.textColor
+            }
+
+            NumberAnimation {
+                id: closeButtonTouchFeedbackAnimation
+                target: closeButtonTouchFeedback
+                property: "opacity"
+                from: 0.1
+                to: 0
+                duration: Kirigami.Units.shortDuration
+                easing.type: Easing.InOutQuad
+            }
+
+            Kirigami.Icon {
+                anchors.centerIn: parent
+                implicitHeight: navbarButtons.height * 0.4
+                implicitWidth: navbarButtons.height * 0.4
+                width: implicitWidth
+                height: implicitHeight
+                source: "mobile-close-app"
+            }
+        }
+    }
+}
diff --git a/kcms/navigation/ui/TutorialPhone.qml b/kcms/navigation/ui/TutorialPhone.qml
index 52014b70a..09fc90119 100644
--- a/kcms/navigation/ui/TutorialPhone.qml
+++ b/kcms/navigation/ui/TutorialPhone.qml
@@ -5,6 +5,7 @@ pragma ComponentBehavior: Bound
 
 import QtQuick
 import QtQuick.Layouts
+import QtQuick.Effects
 
 import org.kde.plasma.components 3.0 as PlasmaComponents
 import org.kde.kirigami as Kirigami
@@ -18,16 +19,101 @@ Item {
     Layout.preferredWidth: phoneWidth
     Layout.preferredHeight: phoneHeight
 
-    property bool showBackground: true
     property int fingerSize: 20
     property int _endTimeout: 2000
+    property bool _gesturesTutorial: true
+    property int _activeIndex: 0
+
+    function playTutorial(gestureMode: bool, index: int): void {
+        _gesturesTutorial = gestureMode;
+        stopAllAnimations(gestureMode);
+
+        if (_gesturesTutorial) {
+            playGestureTutorial(index);
+        } else {
+            playButtonTutorial(index);
+        }
+    }
+
+    function stopAllAnimations(gestureMode: bool): void {
+        _gesturesTutorial = gestureMode;
+
+        switcherAnimation.finished.disconnect(loopCurrentAnimation);
+        flickAnimation.finished.disconnect(loopCurrentAnimation);
+        scrubAnimation.finished.disconnect(loopCurrentAnimation);
+        buttonsTaskSwitcherAnimation.finished.disconnect(loopCurrentAnimation);
+        buttonsHomeAnimation.finished.disconnect(loopCurrentAnimation);
+        buttonsCloseAnimation.finished.disconnect(loopCurrentAnimation);
+
+        switcherAnimation.stop();
+        windowSwitcherAnimation.stop();
+        flickAnimation.stop();
+        windowFlickAnimation.stop();
+        scrubAnimation.stop();
+        windowScrubAnimation.stop();
+        buttonsTaskSwitcherAnimation.stop();
+        buttonsHomeAnimation.stop();
+        buttonsCloseAnimation.stop();
+
+        touchOnAnim.stop();
+        touchOffAnim.stop();
+        homeScreenBackgroundOpacityAnimation.stop();
+        homeScreenBackgroundScaleAnimation.stop();
+
+        navigationBar.stopAnimations();
+
+        switcherAnimation.reset();
+        windowSwitcherAnimation.reset();
+        flickAnimation.reset();
+        windowFlickAnimation.reset();
+        scrubAnimation.reset();
+        windowScrubAnimation.reset();
+        buttonsTaskSwitcherAnimation.reset();
+        buttonsHomeAnimation.reset();
+        buttonsCloseAnimation.reset();
+    }
+
+    function loopCurrentAnimation(): void {
+        playTutorial(_gesturesTutorial, _activeIndex);
+    }
+
+    function playGestureTutorial(index: int): void {
+        _activeIndex = index;
+        if (index === 0) {
+            switcherAnimation.finished.connect(loopCurrentAnimation);
+            switcherAnimation.start();
+            windowSwitcherAnimation.start();
+        } else if (index === 1) {
+            flickAnimation.finished.connect(loopCurrentAnimation);
+            flickAnimation.start();
+            windowFlickAnimation.start();
+        } else if (index === 2) {
+            scrubAnimation.finished.connect(loopCurrentAnimation);
+            scrubAnimation.start();
+            windowScrubAnimation.start();
+        }
+    }
+
+    function playButtonTutorial(index: int): void {
+        _activeIndex = index;
+
+        if (index === 0) {
+            buttonsTaskSwitcherAnimation.finished.connect(loopCurrentAnimation);
+            buttonsTaskSwitcherAnimation.start();
+        } else if (index === 1) {
+            buttonsHomeAnimation.finished.connect(loopCurrentAnimation);
+            buttonsHomeAnimation.start();
+        } else if (index === 2) {
+            buttonsCloseAnimation.finished.connect(loopCurrentAnimation);
+            buttonsCloseAnimation.start();
+        }
+    }
 
     Rectangle {
         id: phone
         width: root.phoneWidth
         height: root.phoneHeight
 
-
         border.color: {
             let color = Kirigami.Theme.textColor
             // note: luminance calculation from https://en.wikipedia.org/wiki/Relative_luminance
@@ -42,144 +128,123 @@ Item {
         radius: Kirigami.Units.largeSpacing
 
         Kirigami.Icon {
-            visible: root.showBackground
+            id: homeScreenBackground
+            opacity: 0
 
             source: "start-here-kde"
             smooth: true
 
             anchors.verticalCenter: phone.verticalCenter
             anchors.horizontalCenter: phone.horizontalCenter
+
+            NumberAnimation {
+                id: homeScreenBackgroundOpacityAnimation
+                target: homeScreenBackground
+                property: "opacity"
+                from: 0
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+
+            NumberAnimation {
+                id: homeScreenBackgroundScaleAnimation
+                target: homeScreenBackground
+                property: "scale"
+                from: 1.5
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            }
+
+            function homeScreenBackgroundAnimation() {
+                homeScreenBackgroundOpacityAnimation.restart();
+                homeScreenBackgroundScaleAnimation.restart();
+            }
         }
 
         Item {
             id: phoneContent
-
             clip: true
-
-            anchors.horizontalCenter: phone.horizontalCenter
-            anchors.verticalCenter: phone.verticalCenter
-
+            anchors.centerIn: parent
             width: phone.width - phone.border.width * 2
             height: phone.height - phone.border.width * 2
 
-            Rectangle {
+            DummyWindow {
                 id: dummyWindow
-
-                Kirigami.Theme.inherit: false
-                Kirigami.Theme.colorSet: Kirigami.Theme.Window
-
-                property real scale: 1
-                property real offset: 0
-
-                color: Kirigami.Theme.backgroundColor
-                width: Math.round(phoneContent.width * scale)
-                height: Math.round(phoneContent.height * scale)
-                radius: Math.max(0, phone.radius - phone.border.width)
+                imageSource: "images/konqi_kde.png"
+                baseWidth: phoneContent.width
+                baseHeight: phoneContent.height
+                phoneRadius: phone.radius
+                phoneBorderWidth: phone.border.width
 
                 anchors.verticalCenter: phoneContent.verticalCenter
                 anchors.horizontalCenter: phoneContent.horizontalCenter
                 anchors.horizontalCenterOffset: Math.round(phoneContent.width * offset)
-
-                Image {
-                    source: "konqi_kde.png"
-
-                    anchors.horizontalCenter: dummyWindow.horizontalCenter
-                    anchors.verticalCenter: dummyWindow.verticalCenter
-
-                    width: {
-                        if (dummyWindow.width > dummyWindow.height * 0.8) {
-                            return Math.round(dummyWindow.height * 0.6)
-                        }
-                        return Math.round(dummyWindow.width * 0.75)
-                    }
-                    fillMode: Image.PreserveAspectFit
-                }
             }
 
-            Rectangle {
+            DummyWindow {
                 id: dummyWindow2
-
-                Kirigami.Theme.inherit: false
-                Kirigami.Theme.colorSet: Kirigami.Theme.Window
-
-                property real scale: dummyWindow.scale
-                property real offset: 0.5
-
-                color: Kirigami.Theme.backgroundColor
-                width: phoneContent.width * scale
-                height: phoneContent.height * scale
-                radius: Math.max(0, phone.radius - phone.border.width)
+                windowScale: dummyWindow.windowScale
+                offset: 0.5
+                imageSource: "images/katie.png"
+                baseWidth: phoneContent.width
+                baseHeight: phoneContent.height
+                phoneRadius: phone.radius
+                phoneBorderWidth: phone.border.width
 
                 anchors.verticalCenter: phoneContent.verticalCenter
                 anchors.horizontalCenter: phoneContent.horizontalCenter
                 anchors.horizontalCenterOffset: phoneContent.width * (-offset - 0.6 + dummyWindow.offset)
-
-                Image {
-                    source: "katie.png"
-
-                    anchors.horizontalCenter: dummyWindow2.horizontalCenter
-                    anchors.verticalCenter: dummyWindow2.verticalCenter
-
-                    width: {
-                        if (dummyWindow.width > dummyWindow.height * 0.8) {
-                            return Math.round(dummyWindow.height * 0.6)
-                        }
-                        return Math.round(dummyWindow.width * 0.75)
-                    }
-                    fillMode: Image.PreserveAspectFit
-                }
             }
 
-            Rectangle {
+            DummyWindow {
                 id: dummyWindow3
-
-                Kirigami.Theme.inherit: false
-                Kirigami.Theme.colorSet: Kirigami.Theme.Window
-
-                property real scale: dummyWindow.scale
-                property real offset: 1.2
-
-                color: Kirigami.Theme.backgroundColor
-                width: phoneContent.width * scale
-                height: phoneContent.height * scale
-                radius: Math.max(0, phone.radius - phone.border.width)
+                windowScale: dummyWindow.windowScale
+                offset: 1.2
+                imageSource: "images/Mascot_konqi-base-plasma.png"
+                baseWidth: phoneContent.width
+                baseHeight: phoneContent.height
+                phoneRadius: phone.radius
+                phoneBorderWidth: phone.border.width
 
                 anchors.verticalCenter: phoneContent.verticalCenter
                 anchors.horizontalCenter: phoneContent.horizontalCenter
                 anchors.horizontalCenterOffset: phoneContent.width * (-offset + dummyWindow.offset - dummyWindow2.offset)
+            }
 
-                Image {
-                    source: "Mascot_konqi-base-plasma.png"
+            NavigationBar {
+                id: navigationBar
+                anchors.fill: parent
+                visible: !root._gesturesTutorial
+                phoneRadius: phone.radius
+                phoneBorderWidth: phone.border.width
+            }
 
-                    anchors.horizontalCenter: dummyWindow3.horizontalCenter
-                    anchors.verticalCenter: dummyWindow3.verticalCenter
+            // phone border to overlay over any imperfections
+            Rectangle {
+                anchors.fill: parent
+                anchors.margins: -phone.border.width
 
-                    width: {
-                        if (dummyWindow.width > dummyWindow.height * 0.8) {
-                            return Math.round(dummyWindow.height * 0.6)
-                        }
-                        return Math.round(dummyWindow.width * 0.75)
-                    }
-                    fillMode: Image.PreserveAspectFit
-                }
+                border.color: phone.border.color
+                border.width: phone.border.width
+                color: "transparent"
+                radius: Kirigami.Units.largeSpacing
             }
         }
 
         Rectangle {
             id: touchPoint
-
             property int size: root.fingerSize
+            property real yPosition: 0
+            property real xPosition: 0
 
             width: size
             height: size
             radius: size / 2
 
-            property real yPosition: 0
-            property real xPosition: 0
-
             anchors.verticalCenter: phone.bottom
             anchors.verticalCenterOffset: Math.round(-yPosition * root.phoneHeight / 6)
-
             anchors.horizontalCenter: phone.horizontalCenter
             anchors.horizontalCenterOffset: Math.round(xPosition * root.phoneWidth * 0.3)
 
@@ -189,7 +254,7 @@ Item {
         }
     }
 
-    // into task switcher animation
+    // gestures - into task switcher animation
     AnimationHandler {
         id: switcherAnimation
 
@@ -198,53 +263,47 @@ Item {
         function reset(): void {
             touchPoint.yPosition = 0;
             touchPoint.xPosition = 0;
+            touchPoint.visible = true;
         }
 
         animations: [
             NumberAnimation {
                 target: touchPoint
                 property: "yPosition"
-
                 from: 0
                 to: 1
+                duration: 1500
+                easing.type: Easing.InOutQuad
 
                 onStarted: {
                     switcherAnimation.reset();
-                    root.touchOnAnim.start()
+                    root.touchOnAnim.start();
                 }
-
-                duration: 1500
-                easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: touchPoint
                 property: "size"
-
                 to: 0
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InQuad
             },
             NumberAnimation {
                 target: touchPoint
                 property: "xPosition"
-
                 from: 0
                 to: 1.5
+                duration: 500
+                easing.type: Easing.InOutQuad
 
                 onStarted: {
                     touchPoint.yPosition = 2;
-                    root.touchOnAnim.start()
+                    root.touchOnAnim.start();
                 }
-                duration: 500
-                easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: touchPoint
                 property: "size"
-
                 to: 0
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InQuad
             }
@@ -252,60 +311,51 @@ Item {
         delays: [
             500,
             500,
-            -Kirigami.Units.longDuration * 2,
+            -Kirigami.Units.longDuration * 2
         ]
     }
 
     AnimationHandler {
         id: windowSwitcherAnimation
-
         endTimeout: root._endTimeout
-
         function reset(): void {
             dummyWindow.offset = 0;
-            dummyWindow.scale = 1;
+            dummyWindow.windowScale = 1;
             dummyWindow2.offset = 0.5;
+            homeScreenBackground.opacity = 0;
         }
 
         animations: [
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 from: 1
                 to: 0.5
+                duration: switcherAnimation.animations[0].duration
+                easing.type: switcherAnimation.animations[0].easing.type
 
                 onStarted: {
                     windowSwitcherAnimation.reset();
                 }
-
-                duration: switcherAnimation.animations[0].duration
-                easing.type: switcherAnimation.animations[0].easing.type
             },
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 to: 0.55
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: dummyWindow2
                 property: "offset"
-
                 to: 0
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: dummyWindow
                 property: "offset"
-
                 to: 0.6
-
                 duration: switcherAnimation.animations[2].duration
                 easing.type: switcherAnimation.animations[2].easing.type
             }
@@ -313,154 +363,138 @@ Item {
         delays: [
             switcherAnimation.delays[0],
             immediate,
-            switcherAnimation.delays[1],
+            switcherAnimation.delays[1]
         ]
     }
 
-    // flick to home animation
+    // gestures - flick to home animation
     AnimationHandler {
         id: flickAnimation
-
         endTimeout: root._endTimeout
-
         function reset(): void {
             touchPoint.yPosition = 0;
+            touchPoint.visible = true;
         }
 
         animations: [
             NumberAnimation {
                 target: touchPoint
                 property: "yPosition"
-
                 from: 0
                 to: 1
+                duration: 900
+                easing.type: Easing.InQuart
 
                 onStarted: {
-                    root.touchOnAnim.start()
+                    flickAnimation.reset();
+                    root.touchOnAnim.start();
                 }
-
-                duration: 900
-                easing.type: Easing.InQuart
             },
             NumberAnimation {
                 target: touchPoint
                 property: "size"
-
                 to: 0
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InQuad
             }
         ]
         delays: [
-            -Kirigami.Units.longDuration,
+            -Kirigami.Units.longDuration
         ]
     }
 
     AnimationHandler {
         id: windowFlickAnimation
-
         endTimeout: root._endTimeout
-
         function reset(): void {
             dummyWindow.offset = 0;
             dummyWindow.opacity = 1;
-            dummyWindow.scale = 1;
+            dummyWindow.windowScale = 1;
+            homeScreenBackground.opacity = 0;
         }
 
         animations: [
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 from: 1
                 to: 0.5
+                duration: flickAnimation.animations[0].duration
+                easing.type: flickAnimation.animations[0].easing.type
 
                 onStarted: {
                     windowFlickAnimation.reset();
                     dummyWindow2.offset = 0.5;
                 }
-
-                duration: flickAnimation.animations[0].duration
-                easing.type: flickAnimation.animations[0].easing.type
             },
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 to: 0.1
-
                 duration: 300
                 easing.type: Easing.InQuad
+
+                onFinished: {
+                    homeScreenBackground.homeScreenBackgroundAnimation();
+                }
             },
             NumberAnimation {
                 target: dummyWindow
                 property: "opacity"
-
                 to: 0
-
                 duration: 300
                 easing.type: Easing.Linear
             }
         ]
         delays: [
             1, // for some reason setting this to 0 creates a runtime error
-            immediate,
+            immediate
         ]
     }
 
-    // scrub animation
+    // gestures - scrub animation
     AnimationHandler {
         id: scrubAnimation
-
         endTimeout: root._endTimeout
-
         function reset(): void {
             touchPoint.yPosition = 0;
             touchPoint.xPosition = 0;
+            touchPoint.visible = true;
         }
 
         animations: [
             NumberAnimation {
                 target: touchPoint
                 property: "yPosition"
-
                 from: 0
                 to: 0.2
+                duration: 900
+                easing.type: Easing.InOutQuart
 
                 onStarted: {
                     scrubAnimation.reset();
-                    root.touchOnAnim.start()
+                    root.touchOnAnim.start();
                 }
-
-                duration: 900
-                easing.type: Easing.InOutQuart
             },
             NumberAnimation {
                 target: touchPoint
                 property: "xPosition"
-
                 from: 0
                 to: 1
-
                 duration: 1500
                 easing.type: Easing.InOutQuart
             },
             NumberAnimation {
                 target: touchPoint
                 property: "xPosition"
-
                 to: 0.5
-
                 duration: 700
                 easing.type: Easing.InOutCubic
             },
             NumberAnimation {
                 target: touchPoint
                 property: "size"
-
                 to: 0
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InQuad
             }
@@ -474,14 +508,13 @@ Item {
 
     AnimationHandler {
         id: windowScrubAnimation
-
         endTimeout: root._endTimeout
-
         function reset(): void {
-            dummyWindow.scale = 1;
+            dummyWindow.windowScale = 1;
             dummyWindow.offset = 0;
             dummyWindow2.offset = 0.5
             dummyWindow3.offset = 1.2;
+            homeScreenBackground.opacity = 0;
         }
 
         animations: [
@@ -489,85 +522,66 @@ Item {
                 // this is just to add some delay at the start when starting at the same time as the touch point animation
                 target: dummyWindow
                 property: "opacity"
-
                 to: 1
+                duration: 0
 
                 onStarted: {
                     windowScrubAnimation.reset();
                 }
-
-                duration: 0
             },
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 to: 0.55
-
-
                 duration: 300
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: dummyWindow2
                 property: "offset"
-
                 to: 0
-
                 duration: 300
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation {
                 target: dummyWindow
                 property: "offset"
-
                 to: 1.2
-
                 duration: scrubAnimation.animations[1].duration
                 easing.type: Easing.InOutQuint
             },
             NumberAnimation {
                 target: dummyWindow
                 property: "offset"
-
                 to: 0.6
-
                 duration: scrubAnimation.animations[2].duration
                 easing.type: Easing.InOutQuart
             },
             NumberAnimation {
                 target: dummyWindow
-                property: "scale"
-
+                property: "windowScale"
                 to: 1
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation { // move leftmost window out of the way, otherwise it overlaps
                 target: dummyWindow3
                 property: "offset"
-
                 to: 1.8
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.InOutQuad
             },
             NumberAnimation { // move middle window (that's to be focused) a bit to the side to counteract moving of the first window
                 target: dummyWindow2
                 property: "offset"
-
                 to: 0.4
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.Linear
             },
             NumberAnimation { // move first (rightmost) window to get a bit more space between it and the middle one during the animation
                 target: dummyWindow
                 property: "offset"
-
                 to: 1
-
                 duration: Kirigami.Units.longDuration
                 easing.type: Easing.Linear
             }
@@ -584,88 +598,209 @@ Item {
         ]
     }
 
-    function stopAnimation(): void {
-        switcherAnimation.finished.disconnect(startSwitcherAnimation);
-        flickAnimation.finished.disconnect(startFlickAnimation);
-        scrubAnimation.finished.disconnect(startScrubAnimation);
-
-        switcherAnimation.stop();
-        flickAnimation.stop();
-        scrubAnimation.stop();
+    NumberAnimation {
+        id: touchOffAnim
+        target: touchPoint
+        property: "size"
+        to: 0
+        duration: Kirigami.Units.longDuration
+        easing.type: Easing.InQuad
     }
 
-    function startSwitcherAnimation(): void {
-        switcherAnimation.start();
-        windowSwitcherAnimation.start();
+    property var touchOnAnim: NumberAnimation {
+        target: touchPoint
+        property: "size"
+        to: root.fingerSize
+        duration: Kirigami.Units.longDuration
+        easing.type: Easing.OutQuad
     }
 
-    function loopSwitcherAnimation(): void {
-        switcherAnimation.finished.connect(startSwitcherAnimation);
-        startSwitcherAnimation();
-    }
+    // buttons - open task switcher animation
+    AnimationHandler {
+        id: buttonsTaskSwitcherAnimation
+        endTimeout: root._endTimeout
+        function reset(): void {
+            dummyWindow.windowScale = 1;
+            dummyWindow.offset = 0;
+            dummyWindow2.offset = 0.5
+            dummyWindow3.offset = 1.2;
+            homeScreenBackground.opacity = 0;
+            navigationBar.taskSwitcher = false;
+            touchPoint.visible = false;
+        }
 
-    function startFlickAnimation(): void {
-        flickAnimation.start();
-        windowFlickAnimation.start();
-    }
+        animations: [
+            NumberAnimation {
+                // this is just to add some delay at the start when starting at the same time as the touch point animation
+                target: dummyWindow
+                property: "opacity"
+                to: 1
+                duration: 0
 
-    function loopFlickAnimation(): void {
-        flickAnimation.finished.connect(startFlickAnimation);
-        startFlickAnimation();
+                onStarted: {
+                    buttonsTaskSwitcherAnimation.reset();
+                }
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "windowScale"
+                to: 0.55
+                duration: 300
+                easing.type: Easing.InOutQuad
 
-    }
+                onStarted: {
+                    navigationBar.taskSwitcher = true;
+                    navigationBar.animateTaskSwitcher();
 
-    function startScrubAnimation(): void {
-        scrubAnimation.start();
-        windowScrubAnimation.start();
-    }
+                }
+            },
+            NumberAnimation {
+                target: dummyWindow2
+                property: "offset"
+                to: 0
+                duration: 300
+                easing.type: Easing.InOutQuad
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "offset"
+                to: 0.6
+                duration: 700
+                easing.type: Easing.InOutQuint
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "offset"
+                to: 0
+                duration: 700
+                easing.type: Easing.InOutQuart
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "windowScale"
+                to: 1
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
 
-    function loopScrubAnimation(): void {
-        scrubAnimation.finished.connect(startScrubAnimation);
-        startScrubAnimation();
+                onFinished: {
+                    navigationBar.taskSwitcher = false;
+                }
+            },
+            NumberAnimation {
+                target: dummyWindow3
+                property: "offset"
+                to: 1.2
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.InOutQuad
+            },
+            NumberAnimation {
+                target: dummyWindow2
+                property: "offset"
+                to: 0.5
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.Linear
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "offset"
+                to: 0
+                duration: Kirigami.Units.longDuration
+                easing.type: Easing.Linear
+            }
+        ]
+        delays: [
+            400,
+            immediate,
+            200,
+            700,
+            700,
+            immediate,
+            immediate,
+            immediate
+        ]
     }
 
-    function startCyclingAnimations(): void {
-        switcherAnimation.finished.connect(() => {
-            switcherAnimation.reset();
-            windowSwitcherAnimation.reset();
-
-            startFlickAnimation();
-        });
-        flickAnimation.finished.connect(() => {
-            flickAnimation.reset();
-            windowFlickAnimation.reset();
+    // buttons - return home animation
+    AnimationHandler {
+        id: buttonsHomeAnimation
+        endTimeout: root._endTimeout
+        function reset(): void {
+            dummyWindow.offset = 0;
+            dummyWindow.opacity = 1;
+            dummyWindow.windowScale = 1;
+            homeScreenBackground.opacity = 0;
+            navigationBar.taskSwitcher = false;
+            touchPoint.visible = false;
+        }
 
-            startScrubAnimation();
-        });
-        scrubAnimation.finished.connect(() => {
-            scrubAnimation.reset();
-            windowScrubAnimation.reset();
+        animations: [
+            NumberAnimation {
+                // this is just to add some delay at the start when starting at the same time as the touch point animation
+                target: dummyWindow
+                property: "opacity"
+                to: 1
+                duration: 0
 
-            startSwitcherAnimation();
-        });
+                onStarted: {
+                    buttonsHomeAnimation.reset();
+                }
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "opacity"
+                to: 0
+                duration: 0
 
-        startSwitcherAnimation();
+                onStarted: {
+                    navigationBar.animateHome();
+                    homeScreenBackground.homeScreenBackgroundAnimation();
+                }
+            }
+        ]
+        delays: [
+            500
+        ]
     }
 
-    NumberAnimation {
-        id: touchOffAnim
-        target: touchPoint
-        property: "size"
-
-        to: 0
-
-        duration: Kirigami.Units.longDuration
-        easing.type: Easing.InQuad
-    }
+    // buttons - close app animation
+    AnimationHandler {
+        id: buttonsCloseAnimation
+        endTimeout: root._endTimeout
+        function reset(): void {
+            dummyWindow.offset = 0;
+            dummyWindow.opacity = 1;
+            dummyWindow.windowScale = 1;
+            homeScreenBackground.opacity = 0;
+            navigationBar.taskSwitcher = false;
+            touchPoint.visible = false;
+        }
 
-    property var touchOnAnim: NumberAnimation {
-        target: touchPoint
-        property: "size"
+        animations: [
+            NumberAnimation {
+                // this is just to add some delay at the start when starting at the same time as the touch point animation
+                target: dummyWindow
+                property: "opacity"
+                to: 1
+                duration: 0
 
-        to: root.fingerSize
+                onStarted: {
+                    buttonsCloseAnimation.reset();
+                }
+            },
+            NumberAnimation {
+                target: dummyWindow
+                property: "opacity"
+                to: 0
+                duration: 0
 
-        duration: Kirigami.Units.longDuration
-        easing.type: Easing.OutQuad
+                onStarted: {
+                    navigationBar.animateClose();
+                    homeScreenBackground.homeScreenBackgroundAnimation();
+                }
+            }
+        ]
+        delays: [
+            500
+        ]
     }
 }
diff --git a/kcms/navigation/ui/Mascot_konqi-base-plasma.png b/kcms/navigation/ui/images/Mascot_konqi-base-plasma.png
similarity index 100%
rename from kcms/navigation/ui/Mascot_konqi-base-plasma.png
rename to kcms/navigation/ui/images/Mascot_konqi-base-plasma.png
diff --git a/kcms/navigation/ui/Mascot_konqi-base-plasma.png.license b/kcms/navigation/ui/images/Mascot_konqi-base-plasma.png.license
similarity index 100%
rename from kcms/navigation/ui/Mascot_konqi-base-plasma.png.license
rename to kcms/navigation/ui/images/Mascot_konqi-base-plasma.png.license
diff --git a/kcms/navigation/ui/katie.png b/kcms/navigation/ui/images/katie.png
similarity index 100%
rename from kcms/navigation/ui/katie.png
rename to kcms/navigation/ui/images/katie.png
diff --git a/kcms/navigation/ui/katie.png.license b/kcms/navigation/ui/images/katie.png.license
similarity index 100%
rename from kcms/navigation/ui/katie.png.license
rename to kcms/navigation/ui/images/katie.png.license
diff --git a/kcms/navigation/ui/konqi_kde.png b/kcms/navigation/ui/images/konqi_kde.png
similarity index 100%
rename from kcms/navigation/ui/konqi_kde.png
rename to kcms/navigation/ui/images/konqi_kde.png
diff --git a/kcms/navigation/ui/konqi_kde.png.license b/kcms/navigation/ui/images/konqi_kde.png.license
similarity index 100%
rename from kcms/navigation/ui/konqi_kde.png.license
rename to kcms/navigation/ui/images/konqi_kde.png.license
diff --git a/kcms/navigation/ui/main.qml b/kcms/navigation/ui/main.qml
index 64ec62a82..defb5243c 100644
--- a/kcms/navigation/ui/main.qml
+++ b/kcms/navigation/ui/main.qml
@@ -4,9 +4,12 @@
  * SPDX-License-Identifier: LGPL-2.0-or-later
  */
 
-import QtQuick 2.15
-import QtQuick.Layouts 1.15
-import QtQuick.Controls 2.15 as Controls
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls as Controls
+import QtQuick.Effects
+
+import Qt5Compat.GraphicalEffects
 
 import org.kde.kirigami as Kirigami
 import org.kde.kcmutils as KCM
@@ -24,205 +27,164 @@ KCM.SimpleKCM {
     rightPadding: 0
 
     ColumnLayout {
-        FormCard.FormHeader {
-            title: i18nc("@label", "Navigation Mode")
-        }
-        FormCard.FormCard {
-            id: gestureSelectionCard
-
-            Controls.ButtonGroup {
-                id: positionGroup
-                buttons: [navPanelRadio, gesturesRadio]
-            }
-
-            FormCard.FormRadioDelegate {
-                id: navPanelRadio
-                text: i18nc("Nav Panel Navigation Mode", "Panel")
-                checked: ShellSettings.Settings.navigationPanelEnabled
-                onClicked: ShellSettings.Settings.navigationPanelEnabled = true;
-            }
-
-            FormCard.FormRadioDelegate {
-                id: gesturesRadio
-                text: i18nc("Gestures Navigation Mode", "Gestures")
-                checked: !ShellSettings.Settings.navigationPanelEnabled
-                onClicked: ShellSettings.Settings.navigationPanelEnabled = false;
-            }
-
-            FormCard.FormDelegateSeparator {
-                visible: !ShellSettings.Settings.navigationPanelEnabled
-                above: gesturesRadio
-                below: tutorialContainer
-            }
+        ListView {
+            id: tutorialView
 
-            Item {
-                id: tutorialContainer
-                visible: !ShellSettings.Settings.navigationPanelEnabled
+            property int phoneWidth: Math.min(Screen.width * 0.25, gestureSelectionCard.maximumWidth * 0.65)
+            property int phoneHeight: phoneWidth * Screen.height / Screen.width
+            property int count: 3
+            property int fingerSize: 20
 
-                property int phoneWidth: Math.min(Window.width * 0.4, gestureSelectionCard.maximumWidth * 0.9)
-                property int phoneHeight: phoneWidth * Window.height / Window.width
-                property int count: 3
-                property int fingerSize: 20
+            Layout.alignment: Qt.AlignCenter
+            Layout.fillWidth: true
+            Layout.preferredHeight: phoneHeight + fingerSize / 2
+            Layout.topMargin: Kirigami.Units.largeSpacing * 2
+            Layout.leftMargin: Kirigami.Units.largeSpacing * 2
+            Layout.rightMargin: Kirigami.Units.largeSpacing * 2
 
-                Layout.alignment: Qt.AlignCenter
-                Layout.preferredWidth: phoneWidth
-                Layout.preferredHeight: phoneHeight + fingerSize / 2
-                Layout.topMargin: Kirigami.Units.largeSpacing * 2
+            clip: true
+            orientation: ListView.Horizontal
+            spacing: Math.min(Math.round((tutorialView.width - phoneWidth) * 0.5), Kirigami.Units.largeSpacing * 6)
 
-                clip: true
+            snapMode: ListView.SnapOneItem
+            highlightRangeMode: ListView.StrictlyEnforceRange
+            preferredHighlightBegin: width / 2 - phoneWidth / 2
+            preferredHighlightEnd: width / 2 + phoneWidth / 2
 
-                MouseArea {
-                    id: tutorialSwitcherInput
+            property int activeTutorialIndex: currentIndex
 
-                    anchors.fill: tutorialContainer
+            model: tutorialView.count
+            delegate: Item {
+                id: delegateItem
 
-                    preventStealing: true
+                required property int index
 
-                    property int activeTutorialIndex: 0
+                width: tutorialView.phoneWidth
+                height: tutorialView.phoneHeight
 
-                    property real lastX
-                    property real lastDelta
+                property bool isCurrentItem: ListView.isCurrentItem
+                property bool isGestureMode: !ShellSettings.Settings.navigationPanelEnabled
 
-                    onPressedChanged: {
-                        if (pressed) {
-                            lastX = mouseX;
-                        }
-                        else {
-                            let moveOffset = lastDelta > 0 ? -0.45 : 0.45
-                            let candidateIndex = Math.round(-tutorialLayout.offset / (tutorialLayout.spacing + tutorialContainer.phoneWidth) + moveOffset);
-                            activeTutorialIndex = Math.max(0, Math.min(tutorialContainer.count - 1, candidateIndex));
-                            updateActiveTutorial();
-                            releaseAnim.start()
-                        }
+                onIsCurrentItemChanged: {
+                    if (isCurrentItem) {
+                        tutorialPhone.playTutorial(isGestureMode, delegateItem.index);
+                    } else {
+                        tutorialPhone.stopAllAnimations(isGestureMode);
                     }
+                }
 
-                    function updateActiveTutorial(): void {
-                        switch (activeTutorialIndex) {
-                            case 0:
-                                switchTutorial.loopSwitcherAnimation();
-                                flickTutorial.stopAnimation();
-                                scrubTutorial.stopAnimation();
-                                break;
-                            case 1:
-                                switchTutorial.stopAnimation();
-                                flickTutorial.loopFlickAnimation();
-                                scrubTutorial.stopAnimation();
-                                break;
-                            case 2:
-                                switchTutorial.stopAnimation();
-                                flickTutorial.stopAnimation();
-                                scrubTutorial.loopScrubAnimation();
-                                break;
-                        }
+                onIsGestureModeChanged: {
+                    if (isCurrentItem) {
+                        tutorialPhone.playTutorial(isGestureMode, delegateItem.index);
+                    } else {
+                        tutorialPhone.stopAllAnimations(isGestureMode);
                     }
+                }
 
-                    onPositionChanged: (mouse) => {
-                        let delta = mouse.x - lastX;
-                        let endOffset = -(tutorialContainer.count - 1) * (tutorialContainer.phoneWidth + tutorialLayout.spacing);
-                        if (activeTutorialIndex == 0 && tutorialLayout.offset > 0 && delta > 0) {
-                            // handle overshoot on the left
-                            delta /= tutorialLayout.x;
-                        }
-                        else if (activeTutorialIndex == tutorialContainer.count - 1 && tutorialLayout.offset < endOffset) {
-                            // handle overhoot on the right
-                            delta /= Math.abs(tutorialLayout.x - endOffset);
+                function playTutorial(gestureMode, idx) {
+                    tutorialPhone.playTutorial(gestureMode, idx);
+                }
+
+                TutorialPhone {
+                    id: tutorialPhone
+                    anchors.fill: parent
+                    phoneWidth: delegateItem.width
+                    phoneHeight: delegateItem.height
+                    fingerSize: tutorialView.fingerSize
+
+                    Component.onCompleted: {
+                        if (delegateItem.index === tutorialView.activeTutorialIndex) {
+                            tutorialPhone.playTutorial(isGestureMode, delegateItem.index);
+                        } else {
+                            tutorialPhone.stopAllAnimations(isGestureMode);
                         }
-                        tutorialLayout.offset += delta
-                        lastDelta = delta;
-                        lastX = mouse.x
                     }
+                }
+            }
 
-                    NumberAnimation {
-                        id: releaseAnim
+            layer.enabled: true
+            layer.effect: OpacityMask {
+                maskSource: Rectangle {
+                    id: maskRect
+                    width: tutorialView.width
+                    height: tutorialView.height
 
-                        target: tutorialLayout
-                        property: "offset"
+                    property real gradientBoundaries: Math.max((tutorialView.width * 0.5) - (tutorialView.phoneWidth * 0.5) - (Kirigami.Units.gridUnit * 6), 0) / Math.max(1, width)
+                    property real gradientDistance: Math.max(((tutorialView.width * 0.5) - (tutorialView.phoneWidth * 0.5)) / Math.max(1, width), maskRect.gradientBoundaries)
 
-                        to: -tutorialSwitcherInput.activeTutorialIndex * (tutorialContainer.phoneWidth + tutorialLayout.spacing)
+                    gradient: Gradient {
+                        orientation: Gradient.Horizontal
 
-                        duration: Kirigami.Units.longDuration
-                        easing.type: Easing.InOutQuad
+                        GradientStop { position: maskRect.gradientBoundaries; color: 'transparent' }
+                        GradientStop { position: 0 + maskRect.gradientDistance; color: 'white' }
+                        GradientStop { position: 1.0 - maskRect.gradientDistance; color: 'white' }
+                        GradientStop { position: 1.0 - maskRect.gradientBoundaries; color: 'transparent' }
                     }
                 }
+            }
+        }
 
-                Item {
-                    Layout.alignment: Qt.AlignCenter
-                    Layout.preferredWidth: tutorialContainer.phoneWidth
-                    Layout.preferredHeight: tutorialContainer.phoneHeight + tutorialContainer.fingerSize / 2
-
-                    RowLayout {
-                        id: tutorialLayout
-
-                        property int offset: 0
-                        x: offset
-
-                        width: tutorialContainer.phoneWidth * tutorialContainer.count + Kirigami.Units.largeSpacing * (tutorialContainer.count - 1)
-                        height: tutorialContainer.phoneHeight
-                        spacing: Kirigami.Units.largeSpacing
-
-                        TutorialPhone {
-                            id: switchTutorial
-
-                            phoneWidth: tutorialContainer.phoneWidth
-                            phoneHeight: tutorialContainer.phoneHeight
-                            fingerSize: tutorialContainer.fingerSize
-                            showBackground: false
-
-                            Layout.alignment: Qt.AlignCenter
-
-                            Component.onCompleted: {
-                                loopSwitcherAnimation();
-                            }
-                        }
-
-                        TutorialPhone {
-                            id: flickTutorial
+        Controls.PageIndicator {
+            currentIndex: tutorialView.activeTutorialIndex
+            count: tutorialView.count
 
-                            phoneWidth: tutorialContainer.phoneWidth
-                            phoneHeight: tutorialContainer.phoneHeight
-                            fingerSize: tutorialContainer.fingerSize
+            Layout.alignment: Qt.AlignCenter
+        }
 
-                            Layout.alignment: Qt.AlignCenter
-                        }
+        FormCard.FormSectionText {
+            id: tutorialText
+            verticalAlignment: Text.AlignTop
+            Layout.preferredHeight: textMetrics.lineSpacing * 2
 
-                        TutorialPhone {
-                            id: scrubTutorial
+            property bool isGestureMode: !ShellSettings.Settings.navigationPanelEnabled
 
-                            phoneWidth: tutorialContainer.phoneWidth
-                            phoneHeight: tutorialContainer.phoneHeight
-                            fingerSize: tutorialContainer.fingerSize
-                            showBackground: false
+            FontMetrics {
+                id: textMetrics
+                font: tutorialText.font
+            }
 
-                            Layout.alignment: Qt.AlignCenter
-                        }
+            text: {
+                if (isGestureMode) {
+                    switch (tutorialView.activeTutorialIndex) {
+                        case 0: return i18n("Swipe up, hold, then release to enter the Task Switcher.");
+                        case 1: return i18n("Swipe up from the bottom to return to Home Screen.");
+                        case 2: return i18n("Swipe horizontally near the bottom to scrub through open tasks. Release to select.");
+                        default: return "";
+                    }
+                } else {
+                    switch (tutorialView.activeTutorialIndex) {
+                        case 0: return i18n("Tap the square button to enter the Task Switcher. Double tap to switch to the last used application.");
+                        case 1: return i18n("Tap the center button to return to the Home Screen.");
+                        case 2: return i18n("Tap the X button to close the current application.");
+                        default: return "";
                     }
                 }
             }
+        }
 
-            Controls.PageIndicator {
-                visible: !ShellSettings.Settings.navigationPanelEnabled
-                currentIndex: tutorialSwitcherInput.activeTutorialIndex
-                count: tutorialContainer.count
-
-                Layout.alignment: Qt.AlignCenter
-            }
-
-            FormCard.FormSectionText {
-                visible: !ShellSettings.Settings.navigationPanelEnabled && tutorialSwitcherInput.activeTutorialIndex == 0
+        FormCard.FormHeader {
+            title: i18nc("@label", "Navigation Mode")
+        }
+        FormCard.FormCard {
+            id: gestureSelectionCard
 
-                text: i18n("Swipe up, hold then release to enter Task Switcher")
+            Controls.ButtonGroup {
+                id: positionGroup
+                buttons: [navPanelRadio, gesturesRadio]
             }
 
-            FormCard.FormSectionText {
-                visible: !ShellSettings.Settings.navigationPanelEnabled && tutorialSwitcherInput.activeTutorialIndex == 1
-
-                text: i18n("Swipe up and release to go to Home Screen")
+            FormCard.FormRadioDelegate {
+                id: navPanelRadio
+                text: i18nc("Nav Panel Navigation Mode", "Buttons")
+                checked: ShellSettings.Settings.navigationPanelEnabled
+                onClicked: ShellSettings.Settings.navigationPanelEnabled = true;
             }
 
-            FormCard.FormSectionText {
-                visible: !ShellSettings.Settings.navigationPanelEnabled && tutorialSwitcherInput.activeTutorialIndex == 2
-
-                text: i18n("Swipe left and right near the bottom screen edge to scrub through open tasks. Release to select a task to focus")
+            FormCard.FormRadioDelegate {
+                id: gesturesRadio
+                text: i18nc("Gestures Navigation Mode", "Swipe Gestures")
+                checked: !ShellSettings.Settings.navigationPanelEnabled
+                onClicked: ShellSettings.Settings.navigationPanelEnabled = false;
             }
 
             FormCard.FormDelegateSeparator { visible: keyboardToggleDelegate.visible; above: gesturesRadio; below: keyboardToggleDelegate }
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.