Files
valw/docs/superpowers/specs/2026-10-03-valw-polish-design.md
T
2026-10-03 02:26:18 +03:00

159 lines
6.6 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 polish phase (design)
Date: 2026-10-03
Status: approved in brainstorming, pending written-spec review
Builds on: Phases 0–6 (on `main`)
## 1. Goal
Three finishing touches, in this order:
- **P1** Region selection: a size label next to the pointer and macOS's
Shift / Alt / Space modifiers while dragging.
- **P2** An optional soft shadow around window shots.
- **P3** The combo shutter sound: escalating with quick successive shots,
ending in an endlessly rising loop.
### Out of scope
The deferred minors in `docs/test-checklist.md` (they wait for the end
test pass). Nothing binds to Caps Lock (the user maps it to Ctrl).
## 2. P1: Region selection
### 2.1 Size label
While a selection is being dragged, a label shows its size in physical
pixels, `W × H` (the same numbers the saved PNG will have), in white
13 px text on a rounded `#1c1c1e` pill at 90 % opacity, 6 px padding.
It sits 16 logical px right of and below the pointer; if it would leave
the output it moves to the other side of the pointer (left and/or above).
It is drawn only on the output where the drag happens, and disappears
with the drag.
### 2.2 Modifiers while dragging
The selection is computed from the press point `a`, the pointer `p` and
the modifiers, as a pure function in `selection.rs`:
| Held | Effect |
|---|---|
| none | rectangle from `a` to `p` |
| Shift | only one dimension follows the pointer: the one that changes more in the first motion after Shift goes down; the other stays as it was when Shift was pressed. Releasing Shift returns to the plain rectangle |
| Alt (Option) | `a` is the centre: the rectangle spans `a ± (p − a)` |
| Space (held) | the whole rectangle moves with the pointer, keeping its size; on release the far corner follows the pointer again from where it is |
Shift and Alt combine (one dimension, from the centre). Space before a
drag still switches to window mode (Phase 3); Space during a drag now
moves the selection. Releasing the mouse button finishes the selection
with whatever modifiers are held.
The overlay learns the modifier state from the keyboard (SCTK
`update_modifiers`) and Space press/release events.
## 3. P2: Window shadow
`[capture] window_shadow = false` (default off). When on, a window shot
(Phase 3, from `valw window`, Space in region mode, or the toolbar) gets
a shadow before it is saved:
- The image is padded by 40 px on every side (transparent).
- The shadow is the window's own alpha (so it follows rounded corners),
black at 50 % opacity, blurred with a radius of 20 px (three box blur
passes approximating a Gaussian), offset 12 px down.
- The window is drawn over it unchanged.
The preview, clipboard and file all get the shadowed image (it has
alpha; Phase 3's preview already handles that).
## 4. P3: The combo sound
### 4.1 The sounds
All sounds are synthesised in code (no samples): 16-bit mono PCM at
44 100 Hz, written as WAV.
- **The shutter** (~150 ms): a short filtered noise click (5 ms attack,
fast decay) followed after 60 ms by a second, softer and longer
"shk" (noise through a gentle band-pass, 80 ms decay).
- **Shots 1–5**: the shutter played faster and higher: speed
1.00, 1.12, 1.25, 1.40, 1.60 (resampled; shorter and brighter).
- **Shot 6**: the shutter at speed 1.80 followed by a Shepard-tone
glide rising half an octave over 400 ms.
- **Shot 7**: the same shutter, then the glide continuing over the next
half octave, so that its end equals the start of shot 6's glide.
- From then on shots alternate 6, 7, 6, 7…, which sounds like an endless
rise. The Shepard tone is a sum of octave-spaced partials under a
fixed bell-shaped loudness curve over log-frequency, so a whole-octave
shift sounds identical.
### 4.2 The combo
- A shot's number is 1 + the previous one's, if the previous shot was
less than `combo_reset_secs` ago (default 5); otherwise 1. After 7 it
goes back to 6.
- The state (last number, last time) is a small file in
`$XDG_RUNTIME_DIR` (`valw-combo`), so separate `valw` runs share it.
A missing or unreadable file means number 1.
### 4.3 Playing
- The sound plays right after the screen was captured (before saving),
for every mode that produces a shot (screen, `--all` once, region,
window, zoom's `c`). Cancelled captures play nothing.
- The seven WAVs are written once to `$XDG_RUNTIME_DIR/valw-sound/`
(regenerated if missing) and played by spawning `pw-play` (PipeWire)
detached, with the volume from the config; valw doesn't wait for it.
`pw-play` comes with the Nix package's `PATH`. If it can't be started,
a warning is logged once per run and the capture goes on.
### 4.4 Settings
```toml
[sound]
enabled = true
volume = 0.6 # 0.0–1.0
combo_reset_secs = 5 # 1–60
```
The toolbar's Options menu gains **Play sound** (a check, remembered in
`toolbar.toml`, default from `sound.enabled`), which wins over the config
for captures started from the bar, like Show cursor.
## 5. Structure
| File | Change |
|---|---|
| `src/selection.rs` | modifier-aware selection: `Mods`, the Shift axis lock, centre and move |
| `src/region.rs` | modifiers, Space during a drag, the size label |
| `src/render.rs` | blending a small premultiplied label into the overlay buffer |
| `src/wayland.rs` | `update_modifiers` and Space release to the overlay |
| `src/shadow.rs` (new) | `add_shadow(&RgbaImage) -> RgbaImage` |
| `src/window.rs` / `src/main.rs` | apply the shadow when configured; play the sound after capture |
| `src/sound/` (new) | `synth.rs` (shutter, speed-up, Shepard glide, WAV), `combo.rs` (numbering and state file), `mod.rs` (cache + `pw-play`) |
| `src/config.rs` | `capture.window_shadow`, `[sound]` |
| `src/toolbar/*` | the Play sound option |
| `nix/package.ulu.nix` | `pipewire` on the wrapper's `PATH` |
## 6. Testing
Unit tests (`cargo nextest`):
- `selection`: plain, Shift locking each axis, Alt from the centre, Shift
+ Alt, Space moving and then resizing again, clipping to the output.
- the size label's placement near each edge and its text.
- `shadow`: output size, the shadow below the window (alpha > 0 under,
~0 far away), the window's own pixels unchanged, rounded corners
followed.
- `synth`: lengths per speed, no clipping, the WAV header; the Shepard
glide's end matching its start an octave apart (6's start = 7's end).
- `combo`: numbering 1…7 then 6, 7, 6; reset after the gap; a broken
state file.
- config defaults and bounds; the toolbar's sound option.
Manual checks go to the polish section of `docs/test-checklist.md`.
## 7. Definition of done
`cargo clippy -- -D warnings`, `cargo fmt --check`, `cargo nextest run`
and `nix flake check` pass; the checklist section is written.