# Wallpaper transitions Apply and the wallpaper engine play one effect today, the burn. This adds six more, a settings page to preview and choose between them, and a Random option that draws from a chosen subset. ## Transitions `burn`, `ink`, `shatter`, `ripple`, `melt`, `blinds`, `crosswarp`. | Name | Effect | | --- | --- | | burn | the existing effect, unchanged to the eye | | ink | old picture dissolves through warped noise, soft edge | | shatter | Voronoi cells crack outward from `origin`, each sliding and fading on its own delay | | ripple | expanding ring refracts the picture, new one revealed behind it | | melt | columns drip down at noise-driven speeds | | blinds | slats rotate shut in a wave across the screen | | crosswarp | both pictures stretch along their brightness and flow into each other | ## Shaders One `.frag` and one compiled `.qsb` per transition, in `desktop/modules/appearance/`, following `burn.frag`. An uber-shader with a `mode` uniform was rejected: every effect would pay for every branch, and one error would take out all seven. Each shader declares what it uses out of `progress`, `aspect`, `origin`, `source` (old picture) and `target` (new picture). The wrapper's properties are a superset; a property with no matching uniform is harmless, the reverse is not. Every shader must meet two conditions, because the cover appears over and leaves over swaybg: - at `progress` 0 it draws the old picture exactly; - at `progress` 1 it draws the new picture exactly, by full transparency over `newImg` or by sampling `target`. `progress` runs 0 to 1 for all of them. `burn.frag` remaps it internally, with `progress * 1.1 - 0.05`, to keep the range it was tuned for, and the cover's `-0.05` start and `1.05` end go away. ## WallShader.qml One `ShaderEffect` wrapper with `name`, `source`, `target`, `progress`, `aspect` and `origin`, resolving `name` to its `.qsb`. Both the cover and the thumbnails use it, so a preview runs the real shader. It is not called `Transition`: QtQuick has that type and it would shadow the file. In `WallpaperSpread.qml` it replaces the `ShaderEffect` in `oldImg`'s `layer.effect`. `newImg` gains `layer.enabled` so it can be passed as `target`. Both pictures must reach the shader through a layer: an `Image` used directly as a texture gives the whole picture, not the `PreserveAspectCrop` crop, and the cover would no longer line up with swaybg. ## Transitions.qml A singleton beside `WallEngine.qml`, holding: - `all`, the seven names with their labels; - `duration`, 3500ms, used by the cover and the thumbnails; - `pick`, a transition name or `"random"`, and `pool`, the ticked names, both saved; - `draftPick`, `draftPool`, `dirty`, `valid`, `apply()`, `reset()`; - `next()`, returning the name to play for a run. `next()` returns `pick` unless it is `"random"`, in which case it draws from `pool`, excluding the last name played when the pool holds two or more. The draw and the validation of saved values live in `TransitionPick.js` beside it, as `Shuffle.js` does, so they can be tested outside `qs`. Saved through a new `Prefs.setWallTransition()`, under `wallTransition` in `desktop.json`, default `{ pick: "burn", pool: }`, so nothing changes until the user chooses. An unknown name read from the file is dropped from `pool`, and an unknown `pick` falls back to `burn`. `next()` falls back to `burn` if the pool ends up empty. ## Settings page `TransitionsModule.qml`, `TransitionsTab.qml`, `TransitionRow.qml` and `TransitionView.qml`, in the Appearance group, listed in `shell.qml` after `WallEngineModule`. A `MockScreen` at the top, then one line per transition and one for Random, under the headings Transition, Active and In Random. - Each line shows a frame halfway through its transition. A click on the line plays it on the mock monitor and changes nothing else. - The Active column is a radio mark, editing the draft's pick. Marking a transition also plays it. Random has no frame and plays nothing. - A `Switch` per transition edits the draft's pool, and stays available whatever is Active. - `SaveBar` sits in the header through `Page.actions`, with `saveText` Apply and `discardText` Reset. - `valid` is false when Random is Active with nothing switched on, and the note says so. - `TransitionView` plays `Wallpapers.currentH` into `Wallpapers.currentV`, so no gallery scan is needed. It uses a random `origin` per play on the monitor and a fixed one in the frames. With either path empty it draws nothing. ## Runtime `Wallpapers.run()` calls `Transitions.next()` once per run and passes the name as a fourth argument of `swapping`, so both screens play the same effect. `WallpaperSpread` sets it on its `WallShader`. Apply and the engine share `run()`, so both follow the choice. Sets from a terminal still get no transition. ## Left out A duration setting, a choice per screen, and a full-screen try button. ## Checks - All seven `.frag` compile with `qsb --qt6`. - `test_transitions.js` covers the draw: the pool is respected, no immediate repeat with two or more, a pool of one repeats, an empty pool gives `burn`. - A fresh `qs -p desktop` loads with no new warnings. - The motion itself is judged by eye by the user. A `shell.qml` edit needs `./reload.sh desktop` from the user's terminal. ## Documentation `desktop/modules/appearance/README.md`: "The burn" becomes a section on transitions, and the page is described. `AGENTS.md`: the settings window's page list gains transitions.