feat: menubar + icon rail, minimap, Blender-style navigation
A pass over the whole interface, driven by using it on a 5.7-hour capture. **Layout.** The 320px sidebar of labelled widgets is now a 46px icon rail — one column of square buttons, sized so it costs the spectrogram as little width as possible — plus a menubar for one-shot actions. Menu items are defined by naming a keymap entry, so an item reuses that binding's action, gate and shortcut label and the two can't drift; items grey out under exactly the conditions that make the shortcut a no-op. Settings that need more than an on/off (FFT size, colours/levels, annotation kinds) open as popouts beside the rail rather than widening it. Every toggle has exactly one home: nothing is reachable from both the rail and a menu. Icons are drawn from raylib primitives rather than an atlas or font glyphs — the bundled font has no symbol coverage, and vector shapes stay crisp at any UI scale with no assets to ship. **Navigation.** Left-drag now pans and Ctrl+drag box-selects, with Tab swapping which is bare (Ctrl always means "the other one", so either mode does both). Previously a bare left-drag did three different things depending on invisible state, with no cursor feedback; the cursor now reports the active gesture. Wheeling a scrollbar pans that axis, or zooms it with Shift. **Minimap.** Whole-file thumbnail in the top-right with the current view drawn on it; click or drag to scrub, corner handle switches between two sizes. Rendered once per size into a cached texture and rebuilt only when its content changes — panning and zooming just move the rectangle drawn on top. The reduction is strided (each thumbnail cell samples at most 8x8), because reducing every segment x bin meant ~1 G reads per rebuild and a visible hitch on every overlay toggle. **Fixes found along the way:** - The timeline lane mapped events across the whole file while the spectrogram above it showed a zoomed window, so the two only lined up at full zoom-out and an event's tick sat nowhere near its burst. It is also properly toggleable now: the old flag only grew an always-present lane. - Clicks preferred the spectrogram over the minimap. Input handling runs ~900 lines before the draw pass that computed the minimap's rect, so a press there started a pan AND a scrub — two handlers writing app.view in one frame. Geometry queries that input depends on now live outside the draw pass. - Repainting during the background fill re-ran the full synchrosqueeze every 0.5 s. Zoomed out that is ~0.5 G bin-visits with four trig calls each, twice a second, for minutes — while showing almost nothing new, since folded segments land in columns already drawn. The interval now scales with how much work a repaint actually costs. - IsUserInteracting() sweeps 512 key codes and was called three times a frame; memoized per frame. - Frequency labels were drawn at a fixed offset wider than their gutter and overflowed into the rail. They are measured and right-aligned now, with one format chosen per axis so the column doesn't mix "3k" with "2.3k". - The horizontal scrollbar is pinned to the window bottom; it used to sit mid-layout competing with the scope's divider, and the scope covered it outright at larger window sizes. - The scope starts hidden — the spectrogram is the primary view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
This commit is contained in:
@@ -126,17 +126,24 @@ or pressing **O** for the file browser. Try the bundled sample:
|
||||
|
||||
### Controls
|
||||
|
||||
**Navigating.** A left-drag pans by default and **Ctrl+drag** draws a selection
|
||||
box; **Tab** (or the rail's pan/select icons) swaps which one is bare, and Ctrl
|
||||
always means "the other one", so either mode does both without switching back.
|
||||
Middle-drag always pans.
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| **O** | Open file browser |
|
||||
| **LMB drag** | Pan the view (**Ctrl+drag** to box-select) |
|
||||
| **Tab** | Swap pan / select mode |
|
||||
| **Middle-drag** / **Alt+drag** | Pan, regardless of mode |
|
||||
| **Mouse wheel** | Zoom both axes (preserves aspect ratio) |
|
||||
| **Shift+wheel** | Zoom the time axis only |
|
||||
| **Ctrl+wheel** | Zoom the frequency axis only |
|
||||
| **Alt+drag** / **middle-drag** | Pan the view |
|
||||
| **LMB drag** | Select a time + frequency region |
|
||||
| **Wheel on a scrollbar** | Pan that axis (**Shift** to zoom it) |
|
||||
| **Space** | Play / stop the selected region |
|
||||
| **Hover an annotation** | Tooltip with that frame's mLnL detail; lists **every** overlapping frame under the cursor |
|
||||
| **N** / **Shift+N** | Jump to the next / previous collision |
|
||||
| **O** | Open file browser |
|
||||
| **P** | Show / hide the waveform scope |
|
||||
| **M** | Marker / ruler tool |
|
||||
| **S** | Spectrum slice (PSD) |
|
||||
@@ -148,8 +155,30 @@ or pressing **O** for the file browser. Try the bundled sample:
|
||||
| **F1** | About / help |
|
||||
| **Esc** | Clear selection / close dialog |
|
||||
|
||||
Most controls are also available as buttons in the left sidebar (colormap, floor,
|
||||
dynamic range, annotation opacity, grid, …).
|
||||
### Layout
|
||||
|
||||
A menubar across the top holds one-shot actions (**File** — open, export
|
||||
PNG/WAV; **View** — reset/zoom, hide the icon rail, fullscreen; **Annotations** —
|
||||
jump to next collision; **Help**). Everything that toggles lives on the rail
|
||||
instead, so no control has two homes. Menu items are defined by naming a keyboard shortcut, so an item and
|
||||
its key can never drift apart, and items grey out under exactly the conditions
|
||||
that make the shortcut a no-op.
|
||||
|
||||
Down the left is a narrow **icon rail** — one column of square buttons, sized so
|
||||
it costs the spectrogram as little width as possible. Hover any icon for a
|
||||
tooltip. Left to right in function: play/stop and clear selection; pan/select
|
||||
mode; marker, spectrum slice, scope, grid, minimap; FFT size and colour/level
|
||||
popouts; annotations, collisions, and the timeline lane. The three settings
|
||||
popouts open beside the rail rather than widening it. `View → Hide icon rail`
|
||||
hands its width back to the spectrogram.
|
||||
|
||||
The **minimap** (top-right, toggled from the rail) is a thumbnail of the whole
|
||||
capture with the current view drawn on it — click or drag anywhere on it to
|
||||
scrub. Annotation density runs along its bottom edge and collisions along its
|
||||
top. The corner handle switches between two sizes. It is rendered once into a
|
||||
texture and only rebuilt when its *content* changes (new file, colormap,
|
||||
overlays toggled); panning and zooming just move the rectangle drawn on top, so
|
||||
navigation costs nothing.
|
||||
|
||||
### Inspecting overlapping transmissions
|
||||
|
||||
@@ -254,6 +283,14 @@ paths.
|
||||
frequency resolution `sampleRate / fftSize` Hz per bin. Amplitude in dB.
|
||||
- **Axes** — X = time (s), Y = frequency (Hz, scaled to the file's Nyquist),
|
||||
colour = amplitude.
|
||||
- **Loading** — the STFT overview is computed in one blocking pass behind the
|
||||
progress panel. It used to advance a fixed number of segments per frame, which
|
||||
made loading frame-paced rather than compute-bound: the frame limiter, not the
|
||||
FFT, set the speed, so a 478k-segment capture spent over a minute waiting
|
||||
between frames. The tell was that backgrounding the window — which skips
|
||||
presenting entirely — loaded the same file in seconds. Background work also
|
||||
continues while the window is unfocused, so a long capture can be left to
|
||||
finish behind another window.
|
||||
- **Long files** — two things keep cost tied to what's on screen rather than to
|
||||
total duration. The spectrogram image is built for the *visible* segment range
|
||||
(capped at 8192 px wide), so a multi-hour capture renders at all — an
|
||||
@@ -285,8 +322,9 @@ paths.
|
||||
src/
|
||||
spectrogram.c # entry point, main loop, CLI args, headless render
|
||||
stft.c / fft.c # STFT + FFT
|
||||
render.c # spectrogram, annotations, tooltips, scope
|
||||
ui.c # sidebar, file browser, buttons
|
||||
render.c # spectrogram, annotations, tooltips, minimap, scope
|
||||
ui.c # menubar, icon rail + popouts, file browser
|
||||
primitives.c # waveform scope + its min/max envelope summary
|
||||
audio.c # WAV load (ffmpeg fallback), bandpass, playback, WAV export
|
||||
mlnl.c / mlnl.h # mLnL annotation chunk parser
|
||||
platform_*.c # per-OS shims (linux / win32 / web)
|
||||
|
||||
Reference in New Issue
Block a user