From 125a3ace08c5c7924b0dc72ee2373735e0c93137 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Sat, 30 Aug 2025 12:19:38 -0400 Subject: [PATCH] Wallpaper: made the selection more responsive to clicks + code cleanup --- Modules/Background/Background.qml | 123 ++++++++++-------- .../Tabs/WallpaperSelectorTab.qml | 8 +- Services/WallpaperService.qml | 6 +- Shaders/frag/wp_stripes.frag | 6 +- Shaders/qsb/wp_stripes.frag.qsb | Bin 5147 -> 5180 bytes 5 files changed, 78 insertions(+), 65 deletions(-) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 4915386..fdca504 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -20,10 +20,11 @@ Variants { // Internal state management property bool firstWallpaper: true property string transitionType: "fade" - property bool transitioning: false property real transitionProgress: 0 - property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness + + readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness readonly property var allTransitions: WallpaperService.allTransitions + readonly property bool transitioning: transitionAnimation.running // Wipe direction: 0=left, 1=right, 2=up, 3=down property real wipeDirection: 0 @@ -38,53 +39,15 @@ Variants { // External state management property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) + property string futureWallpaper: "" onServicedWallpaperChanged: { - if (servicedWallpaper && servicedWallpaper !== currentWallpaper.source) { - // Set wallpaper immediately on startup - if (firstWallpaper) { - firstWallpaper = false - setWallpaperImmediate(servicedWallpaper) - return - } - - // Get the transitionType from the settings - transitionType = Settings.data.wallpaper.transitionType - - if (transitionType == "random") { - var index = Math.floor(Math.random() * allTransitions.length) - transitionType = allTransitions[index] - } - - // Ensure the transition type really exists - if (transitionType !== "none" && !allTransitions.includes(transitionType)) { - transitionType = "fade" - } - - Logger.log("Background", "New wallpaper: ", servicedWallpaper, "On:", modelData.name, "Transition:", - transitionType) - - switch (transitionType) { - case "none": - setWallpaperImmediate(servicedWallpaper) - break - case "wipe": - wipeDirection = Math.random() * 4 - setWallpaperWithTransition(servicedWallpaper) - break - case "disc": - discCenterX = Math.random() - discCenterY = Math.random() - setWallpaperWithTransition(servicedWallpaper) - break - case "stripes": - stripesCount = Math.round(Math.random() * 24 + 6) - stripesAngle = Math.random() * 360 - setWallpaperWithTransition(servicedWallpaper) - break - default: - setWallpaperWithTransition(servicedWallpaper) - break - } + // Set wallpaper immediately on startup + if (firstWallpaper) { + firstWallpaper = false + setWallpaperImmediate(servicedWallpaper) + } else { + futureWallpaper = servicedWallpaper + debounceTimer.restart() } } @@ -101,6 +64,16 @@ Variants { left: true } + Timer { + id: debounceTimer + interval: 333 + running: false + repeat: false + onTriggered: { + changeWallpaper() + } + } + Image { id: currentWallpaper anchors.fill: parent @@ -110,6 +83,8 @@ Variants { mipmap: false visible: false cache: false + // currentWallpaper should not be asynchronous to avoid flickering when swapping next to current. + asynchronous: false } Image { @@ -121,6 +96,7 @@ Variants { mipmap: false visible: false cache: false + asynchronous: true } // Fade or None transition shader @@ -193,29 +169,27 @@ Variants { to: 1.0 // The stripes shader feels faster visually, we make it a bit slower here. duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration - * 1.4 : Settings.data.wallpaper.transitionDuration + * 1.6 : Settings.data.wallpaper.transitionDuration easing.type: Easing.InOutCubic onFinished: { // Swap images after transition completes currentWallpaper.source = nextWallpaper.source nextWallpaper.source = "" transitionProgress = 0.0 - transitioning = false } } function startTransition() { if (!transitioning && nextWallpaper.source != currentWallpaper.source) { - transitioning = true transitionAnimation.start() } } function setWallpaperImmediate(source) { + transitionAnimation.stop() + transitionProgress = 0.0 currentWallpaper.source = source nextWallpaper.source = "" - transitionProgress = 0.0 - transitioning = false } function setWallpaperWithTransition(source) { @@ -224,16 +198,55 @@ Variants { if (transitioning) { // We are interrupting a transition transitionAnimation.stop() + transitionProgress = 0 currentWallpaper.source = nextWallpaper.source nextWallpaper.source = "" - transitionProgress = 0 - transitioning = false } nextWallpaper.source = source startTransition() } } + + // Main method that actually trigger the wallpaper change + function changeWallpaper() { + // Get the transitionType from the settings + transitionType = Settings.data.wallpaper.transitionType + + if (transitionType == "random") { + var index = Math.floor(Math.random() * allTransitions.length) + transitionType = allTransitions[index] + } + + // Ensure the transition type really exists + if (transitionType !== "none" && !allTransitions.includes(transitionType)) { + transitionType = "fade" + } + + //Logger.log("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType) + switch (transitionType) { + case "none": + setWallpaperImmediate(futureWallpaper) + break + case "wipe": + wipeDirection = Math.random() * 4 + setWallpaperWithTransition(futureWallpaper) + break + case "disc": + discCenterX = Math.random() + discCenterY = Math.random() + setWallpaperWithTransition(futureWallpaper) + break + case "stripes": + stripesCount = Math.round(Math.random() * 20 + 4) + stripesAngle = Math.random() * 360 + setWallpaperWithTransition(futureWallpaper) + break + default: + setWallpaperWithTransition(futureWallpaper) + break + } + } } } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index b708f9d..fcd9e50 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -8,6 +8,7 @@ import qs.Widgets ColumnLayout { id: root + width: parent.width spacing: Style.marginL * scaling @@ -94,12 +95,10 @@ ColumnLayout { GridView { id: wallpaperGridView anchors.fill: parent - clip: true model: wallpapersList - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick interactive: false + clip: true property int columns: 5 property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns) @@ -180,7 +179,8 @@ ColumnLayout { anchors.fill: parent acceptedButtons: Qt.LeftButton hoverEnabled: true - onClicked: { + // Use on pressed instead of clicked to better register clicks + onPressed: { if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { WallpaperService.changeWallpaper(undefined, wallpaperPath) } else { diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 5e7bed6..b66ce2a 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -121,17 +121,17 @@ Singleton { // ------------------------------------------------------------------- function changeWallpaper(screenName, path) { if (screenName !== undefined) { - setWallpaper(screenName, path) + _setWallpaper(screenName, path) } else { // If no screenName specified change for all screens for (var i = 0; i < Quickshell.screens.length; i++) { - setWallpaper(Quickshell.screens[i].name, path) + _setWallpaper(Quickshell.screens[i].name, path) } } } // ------------------------------------------------------------------- - function setWallpaper(screenName, path) { + function _setWallpaper(screenName, path) { if (path === "" || path === undefined) { return } diff --git a/Shaders/frag/wp_stripes.frag b/Shaders/frag/wp_stripes.frag index 61a20f7..34df964 100644 --- a/Shaders/frag/wp_stripes.frag +++ b/Shaders/frag/wp_stripes.frag @@ -22,9 +22,9 @@ void main() { vec4 color1 = texture(source1, uv); // Current (old) wallpaper vec4 color2 = texture(source2, uv); // Next (new) wallpaper - // Map smoothness from 0.0-1.0 to 0.001-0.1 range + // Map smoothness from 0.0-1.0 to 0.001-0.3 range // Using a non-linear mapping for better control at low values - float mappedSmoothness = mix(0.001, 0.1, ubuf.smoothness * ubuf.smoothness); + float mappedSmoothness = mix(0.001, 0.3, ubuf.smoothness * ubuf.smoothness); // Use values directly without forcing defaults float stripes = (ubuf.stripeCount > 0.0) ? ubuf.stripeCount : 12.0; @@ -66,7 +66,7 @@ void main() { float normalizedStripePos = clamp(stripePos / stripes, 0.0, 1.0); // Increased delay and better distribution - float maxDelay = 0.15; + float maxDelay = 0.1; float stripeDelay = normalizedStripePos * maxDelay; // Better progress mapping that uses the full 0.0-1.0 range diff --git a/Shaders/qsb/wp_stripes.frag.qsb b/Shaders/qsb/wp_stripes.frag.qsb index 0d8d94e99c00cee0789221ded7cbba6a616f09fb..4466bce27f0aa81f9befd685f4dd31d76dd86474 100644 GIT binary patch literal 5180 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 literal 5147 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