From be815a34d3f33c4d06dcd93317fa2198b84aadfb Mon Sep 17 00:00:00 2001 From: elwrcl Date: Sat, 3 Oct 2026 20:06:59 +0300 Subject: [PATCH] docs: theme round design spec --- .../specs/2026-10-03-valw-theme-design.md | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-03-valw-theme-design.md diff --git a/docs/superpowers/specs/2026-10-03-valw-theme-design.md b/docs/superpowers/specs/2026-10-03-valw-theme-design.md new file mode 100644 index 0000000..6fd2aa0 --- /dev/null +++ b/docs/superpowers/specs/2026-10-03-valw-theme-design.md @@ -0,0 +1,150 @@ +# valw theme round: the paint shader, overview backdrop, window picker (design) + +Date: 2026-10-03 +Status: approved in brainstorming (after the throwaway `valw-lab` spike), pending written-spec review +Builds on: every phase on `main`; zoom's EGL/GLES renderer (`src/zoom/gl.rs`) + +## 1. Goal + +Give valw a theme of its own: a Balatro-like liquid-paint background, +written from scratch as a shader, coloured from the user's theme, a +wallpaper or a window. Three uses: + +- **T1** A shared theme module (shader, palettes); the region overlay's + dark dim takes the theme's colour (no shader there: the user must see + what they select). +- **T2** `valw backdrop`: the shader, calm and slow, behind niri's overview, + coloured from the wallpaper (replacing Noctalia's blurred backdrop). +- **T3** An Alt+Tab-style window picker (`valw window` from now on): cards + for every window over the shader, the shader coloured from the selected + window's icon; Enter or a click captures that window. + +### Out of scope + +- Live window thumbnails in the picker: niri 26.04 gives clients no way to + read a window's pixels except `screenshot-window`, which also sets the + clipboard and shows a notification every time. Recorded as a research + item; cards show icons until then. +- The shader on the toolbar, zoom or editor (possible later on the same + module). + +## 2. The paint shader + +The spike's shader, cleaned up (our own code, not Balatro's): + +- Two octaves of domain warping (six sine-warp iterations each, a fixed + rotation between them), banded into a 3-colour palette with soft edges, + dark rims, glossy streaks and a soft vignette. +- Uniforms: time, resolution, `motion` (0 = flow, 1 = swirl around the + centre), `speed`, `scale` (blob size), palette `base`, `body`, + `highlight`. +- Lives in `src/theme/paint.frag` and is drawn by a renderer shared with + zoom's GL code (EGL + glow; no Vulkan). + +## 3. Palettes + +`theme::Palette { base, body, highlight }` (linear 0–1 RGB), from: + +1. **Noctalia's theme**: `noctalia msg color-scheme-get` and + `theme-mode-get`; for a `community` scheme the palette file + `$XDG_STATE_HOME/noctalia/community-palettes/.json` at the current + mode: base = `mSurface`, body = `mSecondary`, highlight = `mPrimary` + (accent `mTertiary` kept for later). Other sources (builtin, + wallpaper, custom) fall back to (3) until their files are mapped. +2. **An image** (a wallpaper, an icon): dominant colours by coarse + bucketing of a downscaled copy; the three most frequent distinct + colours (ignoring transparent and near-grey pixels for icons), sorted + dark → light. +3. **A built-in default** (dark grey-blue), when Noctalia isn't there or + anything above fails (logged at debug level, never an error). + +## 4. T1: themed region dim + +The overlay outside the selection becomes the frozen screen blended 55 % +towards the theme's `base` colour (today: darkened to 60 %), so it stays +see-through. Computed once per output before the overlay appears (no cost +per frame). + +## 5. T2: `valw backdrop` + +- A long-running process: one background-layer surface per output + (namespace `valw-backdrop`, no keyboard, empty input region), drawing + the shader with `motion = flow` at a slow speed, coloured from that + output's wallpaper (`noctalia msg wallpaper-get `, palette + source 2). +- Draws only on frame callbacks, so it costs nothing while niri doesn't + show it. When frames resume after more than 2 s, it re-reads the + wallpaper (a changed wallpaper changes the colours, easing over 1 s). +- Outputs coming and going are followed; a second `valw backdrop` exits + at once (a lock, like the preview host's). +- Wiring (in copland, documented in the README): `spawn-at-startup "valw" + "backdrop"`, niri `layer-rule { match namespace="^valw-backdrop$"; + place-within-backdrop true; }`, Noctalia `[backdrop] enabled = false`. + +## 6. T3: the window picker + +- `valw window` now opens the picker; the click-a-window mode stays as + `valw window --pick` (and Space in region mode keeps using it). +- A full-screen overlay layer surface on the focused output (exclusive + keyboard): the shader full screen, and over it a centred row (wrapping + to more rows when needed) of cards for every window, most recently + focused first (niri's `focus_timestamp`), the focused window's card + selected. +- A card: a rounded translucent panel, the app icon (96 logical px), the + window title (one line, ellipsised) and the app name below. The + selected card is larger (110 %), brighter, with a 2 px light border. +- Keys: Tab / → / ↓ next, Shift+Tab / ← / ↑ previous, Enter capture, + Esc cancel (exit 3). Mouse: hover selects, a click captures, a click + outside the cards cancels. +- The shader's motion alternates per run (swirl, flow, swirl, …; the last + one is remembered in `$XDG_STATE_HOME/valw/picker`), and stays fixed + while the picker is open. Its colours come from the selected window's + icon (palette source 2) and ease to the next card's over 300 ms. +- Icons: the window's `app_id` → its `.desktop` file (`$XDG_DATA_DIRS` + applications, `StartupWMClass` or file name) → `Icon=` → the icon + theme from `~/.config/gtk-3.0/settings.ini` (`gtk-icon-theme-name`), + its `Inherits`, then `hicolor`; SVGs rendered with `resvg`. No icon: a + rounded square with the app name's initial. +- Capturing: the picker closes (surface destroyed, flushed and + round-tripped), then the Phase 3 path (`niri screenshot-window --id`), + shadow, sound, preview, clipboard as usual. + +## 7. Structure + +| File | Role | +|---|---| +| `src/theme/mod.rs` | `Palette`, sources (Noctalia, image, default) | +| `src/theme/paint.frag`, `src/theme/gl.rs` | the shader and its renderer (shared EGL setup with `zoom::gl`) | +| `src/render.rs`, `src/region.rs` | themed dim | +| `src/backdrop.rs` | `valw backdrop` | +| `src/picker/{mod,layout,icons}.rs` | the picker, card layout, icon lookup | +| `src/main.rs` | `Command::Backdrop`, `Command::Window { pick }` | +| `src/niri.rs` | `windows()` (all windows with focus timestamps) | +| `README.md` | backdrop wiring | + +New dependency: `resvg` (SVG icons; built on tiny-skia). + +## 8. Testing + +Unit tests (`cargo nextest`): +- palettes: Noctalia palette file parsing (light and dark, missing keys → + default), image palette (dark → light order, transparent and grey + pixels ignored for icons), default. +- themed dim: a pixel is blended towards `base` by the right amount. +- picker layout: card positions for 1, 5 and 20 windows on 1366×768 and + 1920×1080 (rows wrap, centred, selected card scaled), hit-testing, + keyboard navigation wrapping, MRU order from focus timestamps. +- picker motion alternation from the state file (missing/broken file → + swirl). +- icon lookup against a fake data dir (desktop file by name and by + `StartupWMClass`, theme inheritance, hicolor fallback, SVG and PNG). +- backdrop: the "resume after a pause" rule and colour easing. + +Manual checks go to the theme section of `docs/test-checklist.md` +(including how it feels on the HD 4000). + +## 9. Definition of done + +`cargo clippy -- -D warnings`, `cargo fmt --check`, `cargo nextest run` +and `nix flake check` pass; README and checklist updated; copland wiring +done with the user's approval.