From 8767df8b1072435de455d4d6813d73240ea51b96 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Thu, 21 May 2026 11:12:20 +0200 Subject: [PATCH] Add shared mobile shell motion primitives Introduce a central Motion singleton for animation durations, easing, and press scale tokens. Add typed NumberAnimation/ColorAnimation wrappers and a reusable state-layer component so runtime QML can share the same animation policy. --- components/mobileshell/CMakeLists.txt | 5 + .../mobileshell/qml/components/Motion.qml | 103 ++++++++++++++++++ .../qml/components/MotionColorAnimation.qml | 8 ++ .../qml/components/MotionNumberAnimation.qml | 8 ++ .../qml/components/MotionStateLayer.qml | 47 ++++++++ 5 files changed, 171 insertions(+) create mode 100644 components/mobileshell/qml/components/Motion.qml create mode 100644 components/mobileshell/qml/components/MotionColorAnimation.qml create mode 100644 components/mobileshell/qml/components/MotionNumberAnimation.qml create mode 100644 components/mobileshell/qml/components/MotionStateLayer.qml diff --git a/components/mobileshell/CMakeLists.txt b/components/mobileshell/CMakeLists.txt index 31fcbaad..5e71d813 100644 --- a/components/mobileshell/CMakeLists.txt +++ b/components/mobileshell/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(mobileshellplugin PRIVATE ${mobileshellplugin_SRCS}) set_source_files_properties( qml/components/AppLaunch.qml qml/components/Constants.qml + qml/components/Motion.qml qml/dataproviders/AudioInfo.qml qml/dataproviders/BatteryInfo.qml qml/dataproviders/BluetoothInfo.qml @@ -51,6 +52,10 @@ ecm_target_qml_sources(mobileshellplugin SOURCES qml/components/HapticsEffect.qml qml/components/ListView.qml qml/components/MarqueeLabel.qml + qml/components/Motion.qml + qml/components/MotionColorAnimation.qml + qml/components/MotionNumberAnimation.qml + qml/components/MotionStateLayer.qml qml/components/PanelBackground.qml qml/components/ScreenEdgeDragEffect.qml qml/components/StartupFeedbackPanelFill.qml diff --git a/components/mobileshell/qml/components/Motion.qml b/components/mobileshell/qml/components/Motion.qml new file mode 100644 index 00000000..64ad39e0 --- /dev/null +++ b/components/mobileshell/qml/components/Motion.qml @@ -0,0 +1,103 @@ +import QtQuick + +import org.kde.kirigami as Kirigami +import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings + +pragma Singleton + +QtObject { + id: root + + enum Type { + Press, + Standard, + StandardAccel, + StandardDecel, + Emphasized, + EmphasizedAccel, + EmphasizedDecel, + SpatialFast, + SpatialDefault, + SpatialSlow, + SpatialExtended, + SpatialVerySlow, + EffectsFast, + EffectsDefault, + EffectsSlow + } + + readonly property bool enabled: ShellSettings.Settings.animationsEnabled + + readonly property int pressDuration: enabled ? Math.round(Kirigami.Units.shortDuration / 2) : 0 + readonly property int standardDuration: enabled ? Kirigami.Units.longDuration : 0 + readonly property int emphasizedDuration: enabled ? Kirigami.Units.longDuration + Kirigami.Units.shortDuration : 0 + readonly property int spatialFastDuration: enabled ? Kirigami.Units.shortDuration : 0 + readonly property int spatialDefaultDuration: enabled ? Kirigami.Units.longDuration : 0 + readonly property int spatialSlowDuration: enabled ? Kirigami.Units.veryLongDuration : 0 + readonly property int spatialExtendedDuration: enabled ? Kirigami.Units.longDuration * 2 : 0 + readonly property int spatialVerySlowDuration: enabled ? Kirigami.Units.longDuration * 4 : 0 + readonly property int effectsFastDuration: enabled ? Kirigami.Units.shortDuration : 0 + readonly property int effectsDefaultDuration: enabled ? Kirigami.Units.longDuration : 0 + readonly property int effectsSlowDuration: enabled ? Kirigami.Units.veryLongDuration : 0 + + readonly property real pressScaleIn: enabled ? 0.94 : 1 + readonly property real pressScaleOut: enabled ? 1.08 : 1 + readonly property real previewPressScale: enabled ? 0.95 : 1 + readonly property real hiddenScale: enabled ? 0.72 : 1 + readonly property real closeScaleOut: enabled ? 0.9 : 1 + + function duration(type: int): int { + switch (type) { + case Motion.Press: + return pressDuration; + case Motion.Emphasized: + case Motion.EmphasizedAccel: + case Motion.EmphasizedDecel: + return emphasizedDuration; + case Motion.SpatialFast: + return spatialFastDuration; + case Motion.SpatialDefault: + return spatialDefaultDuration; + case Motion.SpatialSlow: + return spatialSlowDuration; + case Motion.SpatialExtended: + return spatialExtendedDuration; + case Motion.SpatialVerySlow: + return spatialVerySlowDuration; + case Motion.EffectsFast: + return effectsFastDuration; + case Motion.EffectsDefault: + return effectsDefaultDuration; + case Motion.EffectsSlow: + return effectsSlowDuration; + default: + return standardDuration; + } + } + + function easing(type: int): int { + switch (type) { + case Motion.StandardAccel: + case Motion.EmphasizedAccel: + return Easing.InQuart; + case Motion.StandardDecel: + case Motion.EmphasizedDecel: + return Easing.OutQuart; + case Motion.Emphasized: + return Easing.OutQuint; + case Motion.SpatialFast: + case Motion.SpatialDefault: + case Motion.SpatialSlow: + case Motion.SpatialExtended: + case Motion.SpatialVerySlow: + return Easing.OutExpo; + case Motion.Press: + case Motion.EffectsFast: + case Motion.EffectsDefault: + case Motion.EffectsSlow: + return Easing.OutCubic; + default: + return Easing.InOutQuad; + } + } +} \ No newline at end of file diff --git a/components/mobileshell/qml/components/MotionColorAnimation.qml b/components/mobileshell/qml/components/MotionColorAnimation.qml new file mode 100644 index 00000000..32660452 --- /dev/null +++ b/components/mobileshell/qml/components/MotionColorAnimation.qml @@ -0,0 +1,8 @@ +import QtQuick + +ColorAnimation { + property int type: Motion.EffectsFast + + duration: Motion.duration(type) + easing.type: Motion.easing(type) +} \ No newline at end of file diff --git a/components/mobileshell/qml/components/MotionNumberAnimation.qml b/components/mobileshell/qml/components/MotionNumberAnimation.qml new file mode 100644 index 00000000..80a88536 --- /dev/null +++ b/components/mobileshell/qml/components/MotionNumberAnimation.qml @@ -0,0 +1,8 @@ +import QtQuick + +NumberAnimation { + property int type: Motion.Standard + + duration: Motion.duration(type) + easing.type: Motion.easing(type) +} \ No newline at end of file diff --git a/components/mobileshell/qml/components/MotionStateLayer.qml b/components/mobileshell/qml/components/MotionStateLayer.qml new file mode 100644 index 00000000..a7cf52a4 --- /dev/null +++ b/components/mobileshell/qml/components/MotionStateLayer.qml @@ -0,0 +1,47 @@ +import QtQuick + +import org.kde.kirigami as Kirigami + +Item { + id: root + + property bool hovered: false + property bool pressed: false + property bool active: false + property bool stateLayerEnabled: true + property color color: Kirigami.Theme.textColor + property color activeColor: Kirigami.Theme.highlightColor + property real hoverOpacity: 0.08 + property real pressedOpacity: 0.14 + property real activeOpacity: 0.12 + property real activeHoverOpacity: 0.18 + property real radius: Kirigami.Units.cornerRadius + + readonly property real layerOpacity: { + if (!stateLayerEnabled) { + return 0; + } + if (pressed) { + return pressedOpacity; + } + if (active) { + return hovered ? activeHoverOpacity : activeOpacity; + } + return hovered ? hoverOpacity : 0; + } + + Rectangle { + anchors.fill: parent + radius: root.radius + color: root.active ? root.activeColor : root.color + opacity: root.layerOpacity + + Behavior on color { + MotionColorAnimation { type: Motion.EffectsFast } + } + + Behavior on opacity { + MotionNumberAnimation { type: Motion.EffectsFast } + } + } +} \ No newline at end of file