scope: full-screen figures, second windows, gated measurements, per-configuration events
A batch of view and instrument work, with the probes and docs for each. - **Full screen per figure row**: a toggle on each row's top-right corner, revealed by pointer proximity (`kMaximizeRevealPx` around its placed rect, per row, read from an overlay-level motion controller so a revealed button under the pointer cannot flicker); the plot menu offers the same as *Full screen this figure* / *Restore the layout*. The arrangement is saved and restored through `RowState`, so nothing plotted moves. A layout of one figure carries no toggle. - **Second window**: `open_scope_window()` copies a figure (cells, width shares, signals in their colours) or opens an empty one, with its own `RowSet` and `PlotArea`; the main window's tick binds, acquires and repaints it, mirroring the time window and pin. Entries: the plot menu's *Open this plot in a new window* and the overflow menu's *New scope window*. - **Layout editor from the plot**: the plot menu gains *Edit the layout...* / *Done editing the layout*, flipping the app's one toggle. - **Measurements gate**: `MeasureOptions::gate` restricts a measurement to the held/pause snapshot (live falls back to the live ring), persisted with the session and shown in the measure dialog. - **Crosshair axis badges**: a preference (`crosshair_axis_labels`, on by default) and the plotter's badges where the lines meet the axes, in the axes' own formats; the plot menu and the probe read them. - **Event cache per configuration**: `EventStore` is keyed `<source>/<configuration>/`, `retarget_event_store()` follows every configuration switch, and a pre-configuration cache (loose files) is adopted into the configuration's folder on first start. - **GTK4 lifecycle**: the main window's handlers move from the non-existent `"destroy"` signal to `close-request`; scope windows are closed with it and the application quits explicitly (the exit run's hang). The exit probe closes with `gtk_window_close()`. - Probes: `[maximize-probe]` (single-figure rule and the corner reveal), `[scope-probe]`, `[split-probe]` cell crosshair leg, `[prefs-probe]` store keying, `[configs]` store key, `[rowmenu]` layout-editor entry; docs, help pages, README and AGENT.md updated. Also fixes a probe-only use-after-free: `toggle_maximize_figure_row` replaces the figure object, so the area must be re-pointed (the button's path does; the probe's raw call did not).
This commit is contained in:
1 parent
bfe13630f9
commit
bdf0ff7ddc
39 files changed
+2267
-136
No files matched your search
@@ -70,6 +70,47 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
compares with a relative tolerance, because clamping to exactly `window_s_` can leave the
|
||||
group's span an ULP above it and a clamp that fires every frame is a re-frame loop, not a
|
||||
clamp.
|
||||
- **A row names its channel: number and signal, from one resolver.** The pane's row reads
|
||||
`N. signal · type`, the dock's and the strip's lines prefix the same number to the type,
|
||||
the signal's badge carries it, and the measurement dialog's signal dropdown numbers its
|
||||
entries with it -- all through `measure_channel_number()`, which is the position of the
|
||||
signal's channel in the model's channel list (the acquired channels in stream order), or 0
|
||||
when the signal is not acquired. The readouts rule cuts both ways here: every view shows the
|
||||
same number because every view asks the one function, and a view that derived a number by
|
||||
its own walk of the channel list would be the second spelling that drifts. 0 is a state, not
|
||||
a failure -- the unacquired signal's row is already grey -- so the readers *omit* the number
|
||||
rather than printing a 0. Renumbering on every enable, disable and CONFIG is inherent (the
|
||||
number answers "which active channel is this", which is what a rebuild renumbers), so a
|
||||
generation change rebuilds the pane as well as the signal list.
|
||||
- **The gate is a per-measurement option, and one band shared by everyone who uses it.**
|
||||
`MeasureOptions::gate` is set per measurement in its dialog; every gated measurement reads
|
||||
only inside the one shaded band -- a plotter `Region`, violet, its two bounds the gate's own
|
||||
pair of instruments, not the cursors, which stay what the cursors pane reads -- and the band
|
||||
is *shown exactly while some measurement is gated*, derived at `rebuild_measures_pane()`,
|
||||
because every change of the measure set (an add, an edit, a delete, a session restore)
|
||||
passes through there. Ungated measurements are keyed away from gated ones
|
||||
(`MeasureWindow`'s key is `(channel, fc, gated)`), so a gated measurement costs one more
|
||||
walk and changes no ungated number.
|
||||
The bounds live on the `RowSet` in **display x** (`gate_x0()`/`gate_x1()`), the cursors' own
|
||||
space, and that is the point: a live axis is newest-relative, so a band at `[-0.3, -0.1]`
|
||||
keeps meaning "300 ms to 100 ms ago" while the data flows under it, and a held event's
|
||||
stamps are edge-relative, so there the band names a fixed slice of the frozen frame. Either
|
||||
way it stays where it was put, which is what makes it a gate and not a second window.
|
||||
`update_measures()` clips the pane's window (viewport live, pin pinned) into the band once,
|
||||
and each gated window walks the clip: every statistic, mark and "it moved" booking below
|
||||
describes the gated slice through that one expression. A band entirely off the view clips to
|
||||
a zero span and the pane reads the same `--` any empty window gives. Because a band that
|
||||
moves can change what the *gated* measurements describe without the viewport or pin moving,
|
||||
the "it moved" test asks the band as well (`measure_gate_window`, booked every pass). While
|
||||
a region drag runs, the tick adopts the bounds out of the dragged row's plot
|
||||
(`sync_gate(drag_row)` -- the `sync_cursors()` contract: read only while a gesture holds the
|
||||
instrument, because outside one the read is our own echo) and mirrors them to every other
|
||||
row; the drag's *end* saves the session, because the bounds are session state
|
||||
(`gate_x0`/`gate_x1`, and each measure's `gate` flag in its `[[measure]]` section) and a
|
||||
drag that is not saved is a session that lies about where the gate is. `[gate-probe]` is the
|
||||
reading: the gated average differs from the ungated one on the same signal, the booked gate
|
||||
window is the band moved onto the stream's clock, the band is still where it was put a
|
||||
second later while `s0=` has moved under it, and a restored session brings both back.
|
||||
- **A measurement must not read its window raw.** Both passes go through
|
||||
`plot::data::MinMaxPyramid`, which the producer already maintains at amortised O(1) -- so the
|
||||
pane inherits the summary of the new samples rather than computing one, which is what the
|
||||
|
||||
Reference in new issue
Block a user