diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-29 17:20:28 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-29 17:20:28 +0100 |
| commit | afa0e6e7402ba19688348ab1c41b30bdf14d2d49 (patch) | |
| tree | 545a3bae8a870a3ed5b90c5e509a914f0f9ab138 | |
| parent | 6b875524f57d7bbbe4e7c6b6554a4aa929eef567 (diff) | |
| download | quickshell-afa0e6e7402ba19688348ab1c41b30bdf14d2d49.tar.gz quickshell-afa0e6e7402ba19688348ab1c41b30bdf14d2d49.zip | |
docs: the wallpaper engine and the burn
The appearance README gains the burn (the cover, the covered()
handshake and why, the shader cache trap) and the engine (sources,
shuffle, light run, homes, the draft page, where its state lives and
what starts it). The spec records what changed while building it: Prefs
instead of list files, a page of its own, folder sources, the header
buttons, the cover handshake.
AGENTS.md gains the Field.edited trap, which applies to every page with
a field and a button: a click does not end editing, so the value a
button acts on must be read on every change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | AGENTS.md | 6 | ||||
| -rw-r--r-- | desktop/modules/appearance/README.md | 54 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md | 16 |
3 files changed, 73 insertions, 3 deletions
@@ -253,6 +253,12 @@ changing that component. The ones that generalise: to false on it, unlike a Controls `TextField`, so every hand-rolled field carries `activeFocusOnTab: true` or Tab skips it. A new one needs it too. Multi-line `TextEdit`s are left out on purpose. +- **`Field.edited` fires on `editingFinished`, and a click does not end + editing.** The hand-rolled `Button`, `Tab` and `Toggle` are `MouseArea`s + that take no focus, so typing a value and clicking one of them leaves the + value uncommitted: the wallpaper engine was started at 30 minutes with 1 + typed in the field. A value a button acts on is read on every change + (`onValueChanged`), as `WallEngineTab` does. - **A QML method cannot be named `escape`.** It shadows the global JS function, and the file fails with `Illegal method name` at load, which a hot reload reports as the whole island being unavailable. The island's diff --git a/desktop/modules/appearance/README.md b/desktop/modules/appearance/README.md index 65132c3..f933ac0 100644 --- a/desktop/modules/appearance/README.md +++ b/desktop/modules/appearance/README.md @@ -1,12 +1,12 @@ # appearance -Six pages of the settings window: Wallpaper, Theme, Theme status, Icons & -cursor, HyprSunset and HyprIdle. Until October 2026 they were the tabs of a separate `appearance/` +Seven pages of the settings window: Wallpaper, Wallpaper engine, Theme, +Theme status, Icons & cursor, HyprSunset and HyprIdle. Until October 2026 they were the tabs of a separate `appearance/` shell with an overlay of its own; they moved into the desktop process when the settings window gave them the room that shell existed to provide. Each page is a `*Module.qml` here wrapping a `*Tab.qml`, and the work is done -by singletons (`Wallpapers`, `Udt`, `Icons`, `Hyprsunset`, `Hypridle`, +by singletons (`Wallpapers`, `WallEngine`, `Udt`, `Icons`, `Hyprsunset`, `Hypridle`, `Steps`). The window's open calls each module's `refresh()`, which rescans: schemes, wallpapers and themes can all change during a session, and a scan done only at startup would go stale. @@ -126,6 +126,54 @@ which screen is targeted. Their proportions come from the pane **width**, not its height. Scaling the stand off height gave the wide monitor a 20px stand that was invisible and would have given the vertical one an absurd long neck. +### The burn + +Apply, and every engine change, burns the old picture away over the new +one (`WallpaperSpread.qml`, `burn.frag`). swaybg has no transitions, and +wallp kills one and starts another, so a surface on the bottom layer of each +screen, above swaybg and below every window, holds the old picture over the +swap. `Wallpapers.run()` emits `swapping`, then waits for each cover to call +`covered()` (both its pictures decoded, then 50ms for a few frames) before +it starts wallp, with a 1.5s fallback for a cover that never comes. Started +any earlier, the new picture showed through for a moment before the burn. +The images stay hidden until both are decoded for the same reason: the new +one usually lands first. A set from a terminal gets no burn. After editing +`burn.frag`, recompile with `qsb --qt6 -o burn.frag.qsb burn.frag` under a +new name or restart the shell: a running shell keeps a shader by URL. + +## Wallpaper engine + +Both wallpapers on a timer (`WallEngine.qml`). Each screen draws from its +hearted list, toggled with the heart on a thumbnail for the screen the +picker targets, or from one gallery folder picked on the engine's page. The +order is a shuffle bag (`Shuffle.js`, `node desktop/modules/appearance/test_shuffle.js`): +every image once per round, never the same twice running. + +Timer changes are the light run: `Wallpapers.run(h, v, "tick")` runs wallp +with `UDT_ROTATION=1`, which udt-accent reads to skip GRUB, the homepage and +SDDM and not start `udt-grub` and `udt-homepage`. The rest of the theme, +lights and keyboard included, follows each change. A manual Apply is always +the full run. + +Each screen has a home: what it showed when the engine started, then each +manual Apply. Stop puts the homes back with a full run, so the boot and login +surfaces match the desktop again. A Stop during a run waits for that run. + +The page commits like the other settings pages: the interval and the sources +are a draft, and the header carries the state with Reset, Stop and Start +(Apply while running). Start changes the wallpapers at once; the first timer +change would otherwise be a whole interval away. The minutes field updates +the draft on every keystroke, because `Field.edited` waits for Enter or a +focus change and clicking Start takes neither. Hearts are not drafted. + +State is `wallEngine` in `$XDG_STATE_HOME/keystone/desktop.json`: `on`, +`minutes`, `h` and `v` (the hearts), `folderH` and `folderV` (a +`Wallpapers.groups` key, "" for the hearts), `homeH` and `homeV`. In Prefs +rather than in list files because a list can be empty, and an empty +`FileView` write writes nothing. The engine runs from the start of the +session because `WallEngineModule` references it: a singleton is built on +first use. + ## Themes Each scheme shows its real colours, parsed from the pair of files diff --git a/docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md b/docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md index aac3b1c..425a99d 100644 --- a/docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md +++ b/docs/superpowers/specs/2026-10-29-wallpaper-engine-design.md @@ -97,3 +97,19 @@ confirmed by eye on the next accent change. Sequential order, per-screen intervals, persisting the shuffle, rotation from a terminal `wallp` without the desktop shell. + +## Changes during implementation + +- The lists live in `Prefs` (`wallEngine` in `desktop.json`), not in + `~/.config/keystone/wallpapers/*.list`: a list can be empty, and an empty + `FileView` write writes nothing. +- The engine has a page of its own in the Appearance group rather than a + section on the Wallpaper page, so the picker keeps its height. +- Each screen can draw from one gallery folder instead of its hearts + (`folderH`, `folderV`), asked for once the gallery turned out to hold + only wide images. +- The page commits like the other settings pages: interval and sources are + a draft, and the header has Reset, Stop and Start (Apply while running) + in place of an on/off switch. Start changes the wallpapers at once. +- `Wallpapers.run()` holds wallp until the burn's covers are drawing, which + removed a flash of the new picture before the burn. |
