Files
valw/docs/superpowers/specs/2026-10-03-valw-theme-design.md
T
2026-10-03 20:06:59 +03:00

151 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<id>.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 <connector>`, 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.