docs: polish phase design spec

This commit is contained in:
elwrcl committed 2026-10-03 02:26:18 +03:00
1 parent ee257bbf49
commit 9c6e1af479
1 file changed
+158
@@ -0,0 +1,158 @@
# 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.