1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// 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;
}
// Any run's end, Apply's or the engine's. A failed set may
// have left either picture underneath, so the cover goes at
// once rather than claiming the new one.
function onFinished(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();
drawn.restart();
}
// A few frames for the cover to reach the screen, then wallp may
// swap swaybg underneath it.
Timer {
id: drawn
interval: 50
onTriggered: Wallpapers.covered()
}
// -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
}
}
}
}
|