diff options
Diffstat (limited to 'desktop/modules/appearance/WallpaperSpread.qml')
| -rw-r--r-- | desktop/modules/appearance/WallpaperSpread.qml | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/desktop/modules/appearance/WallpaperSpread.qml b/desktop/modules/appearance/WallpaperSpread.qml new file mode 100644 index 0000000..c133343 --- /dev/null +++ b/desktop/modules/appearance/WallpaperSpread.qml @@ -0,0 +1,116 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import Quickshell +import Quickshell.Wayland +import QtQuick + +// The transition when Apply sets a wallpaper. swaybg has none: wallp kills +// the old one and starts a new one, which can show a frame of nothing. A +// surface on the bottom layer, above swaybg and below every window, covers +// the swap: the old picture burns away over the new one, and the +// surface goes once wallp has finished, by when the new swaybg is drawing +// the same picture underneath. Sets from a terminal get no transition: by +// the time wall_h changes, swaybg has already swapped. +Scope { + // ponytail: wallp's default outputs, like Wallpapers.panels; read + // wallp.conf if they are ever changed there. + Variants { + model: [{ which: "H", output: "DP-1" }, { which: "V", output: "DP-3" }] + + PanelWindow { + id: win + required property var modelData + screen: Quickshell.screens.find(s => s.name === modelData.output) ?? null + + property bool running: false + property bool done: false + property real progress: 0 + property point origin: Qt.point(0.5, 0.5) + visible: running + + anchors { top: true; bottom: true; left: true; right: true } + color: "transparent" + exclusionMode: ExclusionMode.Ignore + mask: Region {} + WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.namespace: "keystone-wallpaper-spread" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + Connections { + target: Wallpapers + function onSwapping(which, from, to) { + if (which !== win.modelData.which || !win.screen) return; + anim.stop(); + // Behind the char band, so no pixel starts out scorched. + win.progress = -0.05; + // A different place each time the fire starts. + win.origin = Qt.point(Math.random(), Math.random()); + win.done = false; + oldImg.source = from; + newImg.source = to; + win.running = true; + } + // A failed set may have left either picture underneath, so + // the cover goes at once rather than claiming the new one. + function onApplied(ok) { + win.done = true; + if (!ok) win.running = false; + else if (!anim.running) win.running = false; + } + } + + // Both decoded before the first block drops, or the old one + // would pop in late over a half-drawn screen. + readonly property bool ready: running && oldImg.status === Image.Ready + && newImg.status === Image.Ready + onReadyChanged: if (ready) anim.restart() + + // -m fill is a centred crop, which is what PreserveAspectCrop does. + Image { + id: newImg + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + sourceSize: Qt.size(win.width, win.height) + asynchronous: true + cache: false + } + + Image { + id: oldImg + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + sourceSize: Qt.size(win.width, win.height) + asynchronous: true + cache: false + layer.enabled: win.running + layer.effect: ShaderEffect { + property real progress: win.progress + property real aspect: win.width / Math.max(1, win.height) + property point origin: win.origin + fragmentShader: Qt.resolvedUrl("burn.frag.qsb") + } + } + + NumberAnimation { + id: anim + target: win + property: "progress" + // Past 1: fbm can push a threshold a little over it. Linear, + // since an ease spends its slow ends where nothing burns. + to: 1.05 + duration: 3500 + easing.type: Easing.Linear + onFinished: if (win.done) win.running = false + } + } + } +} |
