From 7d5c20125ac8480f0d0d302fdc7664709999a010 Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 5 Feb 2026 12:10:17 -0500 Subject: [PATCH] animations: fine-grained anim settings for modals and popouts --- quickshell/Common/SettingsData.qml | 4 + quickshell/Common/Theme.qml | 18 ++ quickshell/Common/settings/SettingsSpec.js | 4 + quickshell/Modals/Common/DankModal.qml | 2 +- .../DankLauncherV2/DankLauncherV2Modal.qml | 8 +- .../Modules/Settings/TypographyMotionTab.qml | 160 ++++++++++++++++++ quickshell/Widgets/DankPopout.qml | 2 +- 7 files changed, 192 insertions(+), 6 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index a82bac62..288d1872 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -154,6 +154,10 @@ Singleton { property bool nightModeEnabled: false property int animationSpeed: SettingsData.AnimationSpeed.Short property int customAnimationDuration: 500 + property int popoutAnimationSpeed: SettingsData.AnimationSpeed.Short + property int popoutCustomAnimationDuration: 150 + property int modalAnimationSpeed: SettingsData.AnimationSpeed.Short + property int modalCustomAnimationDuration: 150 property string wallpaperFillMode: "Fill" property bool blurredWallpaperLayer: false property bool blurWallpaperOnOverview: false diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index 28e4e6f6..697b5028 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -766,6 +766,24 @@ Singleton { }; } + readonly property int popoutAnimationDuration: { + if (typeof SettingsData === "undefined") + return 150; + const presetMap = [0, 150, 300, 500]; + if (SettingsData.popoutAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return SettingsData.popoutCustomAnimationDuration; + return presetMap[SettingsData.popoutAnimationSpeed] ?? 150; + } + + readonly property int modalAnimationDuration: { + if (typeof SettingsData === "undefined") + return 150; + const presetMap = [0, 150, 300, 500]; + if (SettingsData.modalAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return SettingsData.modalCustomAnimationDuration; + return presetMap[SettingsData.modalAnimationSpeed] ?? 150; + } + property real cornerRadius: { if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { return GreetdSettings.cornerRadius; diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 7f4db101..6440241c 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -40,6 +40,10 @@ var SPEC = { nightModeEnabled: { def: false }, animationSpeed: { def: 1 }, customAnimationDuration: { def: 500 }, + popoutAnimationSpeed: { def: 1 }, + popoutCustomAnimationDuration: { def: 150 }, + modalAnimationSpeed: { def: 1 }, + modalCustomAnimationDuration: { def: 150 }, wallpaperFillMode: { def: "Fill" }, blurredWallpaperLayer: { def: false }, blurWallpaperOnOverview: { def: false }, diff --git a/quickshell/Modals/Common/DankModal.qml b/quickshell/Modals/Common/DankModal.qml index 9a334b99..25156916 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -26,7 +26,7 @@ Item { property bool closeOnEscapeKey: true property bool closeOnBackgroundClick: true property string animationType: "scale" - property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial + property int animationDuration: Theme.modalAnimationDuration property real animationScaleCollapsed: 0.96 property real animationOffset: Theme.spacingL property list animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml index c7570312..319de46f 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml @@ -198,7 +198,7 @@ Item { Timer { id: closeCleanupTimer - interval: Theme.expressiveDurations.expressiveFastSpatial + 50 + interval: Theme.modalAnimationDuration + 50 repeat: false onTriggered: { isClosing = false; @@ -310,7 +310,7 @@ Item { Behavior on opacity { NumberAnimation { - duration: Theme.expressiveDurations.expressiveFastSpatial + duration: Theme.modalAnimationDuration easing.type: Easing.BezierSpline easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.emphasized } @@ -346,7 +346,7 @@ Item { Behavior on opacity { NumberAnimation { - duration: Theme.expressiveDurations.fast + duration: Theme.modalAnimationDuration easing.type: Easing.BezierSpline easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel } @@ -354,7 +354,7 @@ Item { Behavior on scale { NumberAnimation { - duration: Theme.expressiveDurations.fast + duration: Theme.modalAnimationDuration easing.type: Easing.BezierSpline easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel } diff --git a/quickshell/Modules/Settings/TypographyMotionTab.qml b/quickshell/Modules/Settings/TypographyMotionTab.qml index f7c51a16..9dd07f8d 100644 --- a/quickshell/Modules/Settings/TypographyMotionTab.qml +++ b/quickshell/Modules/Settings/TypographyMotionTab.qml @@ -270,6 +270,166 @@ Item { } } } + + SettingsCard { + tab: "typography" + tags: ["animation", "speed", "motion", "duration", "popout"] + title: I18n.tr("%1 Animation Speed").arg(I18n.tr("Popouts")) + settingKey: "popoutAnimationSpeed" + iconName: "open_in_new" + + Item { + width: parent.width + height: popoutSpeedGroup.implicitHeight + clip: true + + DankButtonGroup { + id: popoutSpeedGroup + anchors.horizontalCenter: parent.horizontalCenter + buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL + minButtonWidth: parent.width < 480 ? 44 : 64 + textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium + model: [I18n.tr("None"), I18n.tr("Short"), I18n.tr("Medium"), I18n.tr("Long"), I18n.tr("Custom")] + selectionMode: "single" + currentIndex: SettingsData.popoutAnimationSpeed + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("popoutAnimationSpeed", index); + } + + Connections { + target: SettingsData + function onPopoutAnimationSpeedChanged() { + popoutSpeedGroup.currentIndex = SettingsData.popoutAnimationSpeed; + } + } + } + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.15 + } + + SettingsSliderRow { + id: popoutDurationSlider + tab: "typography" + tags: ["animation", "duration", "custom", "speed", "popout"] + settingKey: "popoutCustomAnimationDuration" + text: I18n.tr("Custom Duration") + description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Popouts")) + minimum: 0 + maximum: 500 + value: Theme.popoutAnimationDuration + unit: "ms" + defaultValue: 150 + onSliderValueChanged: newValue => { + SettingsData.set("popoutAnimationSpeed", SettingsData.AnimationSpeed.Custom); + SettingsData.set("popoutCustomAnimationDuration", newValue); + } + + Connections { + target: SettingsData + function onPopoutAnimationSpeedChanged() { + if (SettingsData.popoutAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return; + popoutDurationSlider.value = Theme.popoutAnimationDuration; + } + } + + Connections { + target: Theme + function onPopoutAnimationDurationChanged() { + if (SettingsData.popoutAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return; + popoutDurationSlider.value = Theme.popoutAnimationDuration; + } + } + } + } + + SettingsCard { + tab: "typography" + tags: ["animation", "speed", "motion", "duration", "modal"] + title: I18n.tr("%1 Animation Speed").arg(I18n.tr("Modals")) + settingKey: "modalAnimationSpeed" + iconName: "web_asset" + + Item { + width: parent.width + height: modalSpeedGroup.implicitHeight + clip: true + + DankButtonGroup { + id: modalSpeedGroup + anchors.horizontalCenter: parent.horizontalCenter + buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL + minButtonWidth: parent.width < 480 ? 44 : 64 + textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium + model: [I18n.tr("None"), I18n.tr("Short"), I18n.tr("Medium"), I18n.tr("Long"), I18n.tr("Custom")] + selectionMode: "single" + currentIndex: SettingsData.modalAnimationSpeed + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("modalAnimationSpeed", index); + } + + Connections { + target: SettingsData + function onModalAnimationSpeedChanged() { + modalSpeedGroup.currentIndex = SettingsData.modalAnimationSpeed; + } + } + } + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.15 + } + + SettingsSliderRow { + id: modalDurationSlider + tab: "typography" + tags: ["animation", "duration", "custom", "speed", "modal"] + settingKey: "modalCustomAnimationDuration" + text: I18n.tr("Custom Duration") + description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Modals")) + minimum: 0 + maximum: 500 + value: Theme.modalAnimationDuration + unit: "ms" + defaultValue: 150 + onSliderValueChanged: newValue => { + SettingsData.set("modalAnimationSpeed", SettingsData.AnimationSpeed.Custom); + SettingsData.set("modalCustomAnimationDuration", newValue); + } + + Connections { + target: SettingsData + function onModalAnimationSpeedChanged() { + if (SettingsData.modalAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return; + modalDurationSlider.value = Theme.modalAnimationDuration; + } + } + + Connections { + target: Theme + function onModalAnimationDurationChanged() { + if (SettingsData.modalAnimationSpeed === SettingsData.AnimationSpeed.Custom) + return; + modalDurationSlider.value = Theme.modalAnimationDuration; + } + } + } + } } } } diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 9d3248bb..ebbd5b89 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -21,7 +21,7 @@ Item { property real triggerWidth: 40 property string triggerSection: "" property string positioning: "center" - property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial + property int animationDuration: Theme.popoutAnimationDuration property real animationScaleCollapsed: 0.96 property real animationOffset: Theme.spacingL property list animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial