import QtQuick import QtQuick.Effects import Quickshell import Quickshell.Io import Quickshell.Wayland import "../../config.js" as Config Variants { model: Quickshell.screens PanelWindow { id: win required property var modelData property var wallpaper: Config.wallpapers.find(function(w) { return w.outputs.indexOf(modelData.name) !== -1 }) property string currentPath: wallpaper ? wallpaper.path : "" property string previousPath: "" property real reveal: 1 property bool started: false property bool initialized: false property int direction: 0 readonly property bool horizontal: direction < 2 readonly property bool fromStart: direction % 2 === 0 readonly property int fillValue: wallpaper ? Image[wallpaper.fillMode] : Image.PreserveAspectCrop readonly property bool ready: initialized && currentPath !== "" && newImage.status === Image.Ready && (previousPath === "" || oldImage.status === Image.Ready) screen: modelData visible: wallpaper !== undefined color: "#000000" anchors { top: true bottom: true left: true right: true } exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Background WlrLayershell.namespace: "wallpaper" FileView { id: stateFile path: Quickshell.env("HOME") + "/.cache/qs-wallpaper-" + win.modelData.name blockLoading: true printErrors: false } function pickDirection() { const names = ["left", "right", "top", "bottom"] const wanted = Config.wallpaperWipeDirection ?? "random" const index = names.indexOf(wanted) if (index !== -1) return index return Math.floor(Math.random() * 4) } function begin() { if (started || currentPath === "") return started = true if (reveal < 1) wipe.start() else finish() } function finish() { previousPath = currentPath stateFile.setText(currentPath) } onReadyChanged: { if (ready) begin() } Component.onCompleted: { let saved = "" try { saved = stateFile.text().trim() } catch (e) { saved = "" } previousPath = saved direction = pickDirection() reveal = (saved !== currentPath) ? 0 : 1 initialized = true if (ready) begin() } NumberAnimation { id: wipe target: win property: "reveal" from: 0 to: 1 duration: Config.wallpaperWipeDuration ?? 700 easing.type: Easing.InOutCubic onFinished: win.finish() } Item { id: stack anchors.fill: parent opacity: win.wallpaper ? win.wallpaper.opacity : 1.0 readonly property real clipWidth: win.horizontal ? (width * win.reveal) : width readonly property real clipHeight: win.horizontal ? height : (height * win.reveal) readonly property real clipX: (win.horizontal && !win.fromStart) ? (width - clipWidth) : 0 readonly property real clipY: (!win.horizontal && !win.fromStart) ? (height - clipHeight) : 0 Image { id: oldImage anchors.fill: parent clip: true source: win.initialized ? win.previousPath : "" fillMode: win.fillValue asynchronous: win.wallpaper ? win.wallpaper.asynchronous : true cache: win.wallpaper ? win.wallpaper.cache : true } Item { id: revealClip x: stack.clipX y: stack.clipY width: stack.clipWidth height: stack.clipHeight clip: true Image { id: newImage x: 0 - stack.clipX y: 0 - stack.clipY width: stack.width height: stack.height clip: true source: win.initialized ? win.currentPath : "" fillMode: win.fillValue asynchronous: win.wallpaper ? win.wallpaper.asynchronous : true cache: win.wallpaper ? win.wallpaper.cache : true } } } MultiEffect { anchors.fill: stack source: stack blurEnabled: win.wallpaper ? win.wallpaper.blur > 0 : false blur: win.wallpaper ? win.wallpaper.blur : 0 } } }