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
@@ -119,8 +119,11 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
a split one -- never a flat index, which is 0-based and says nothing about where the plot is.
|
||||
What the cells share and what each keeps:
|
||||
- **One time window.** Every cell of every row is a member of the one x link group, so a pan or zoom
|
||||
in any cell moves them all, the crosshair is one x drawn in every cell (the hovered cell shows its
|
||||
y), and the **vertical cursor pair is one shared time drawn in every cell** (`cursors_aligned`).
|
||||
in any cell moves them all, the crosshair is one x drawn in every cell, and the **vertical cursor
|
||||
pair is one shared time drawn in every cell** (`cursors_aligned`). The horizontal crosshair line is
|
||||
the hovered cell's y **drawn in every cell of its figure row** -- the cells share that y, so the
|
||||
line is one value spanning the row rather than stopping at the divider; no other row gets one
|
||||
(`Figure::set_crosshair` spreads it, `[split-probe] cell-xhair` reads it).
|
||||
A split is an arrangement of more plots, not a second time scale: nothing about the time axis changes
|
||||
(see [invariants-time-axis-and-window.md](invariants-time-axis-and-window.md)).
|
||||
- **One y per figure row.** The cells of a figure row share one y range (the plotter's
|
||||
@@ -340,3 +343,33 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
relayout, the panels' chips, the layout save) are done once by the caller rather than per slot. Re-enabling
|
||||
does **not** put the traces back: the arrangement is the user's, and guessing where they were is worse than
|
||||
leaving them off.
|
||||
|
||||
- **The full-screen toggle and a scope window are *views*: neither edits the arrangement nor takes a second
|
||||
acquisition.** The per-row toggle (`RowSet::toggle_maximize_figure_row()`) saves every row's persistent
|
||||
state (`RowState` -- the fields `carry_row()` copies, because a `Row` is not copy-assignable: its
|
||||
`SlotSeries` owns a `unique_ptr`) plus the cells, the weights and the plot count, rebuilds the figure with
|
||||
the asked-for row's plots moved to the front, and puts every field back on the second click; it saves
|
||||
nothing, so a reopened session has the arrangement, not the view. Its overlay button is on screen
|
||||
*always* (`App::maximize_buttons`, a set of its own -- `layout_buttons` is the editor's and is cleared with
|
||||
it), placed from the same `FigureLayout` rects as the editor's buttons. A scope window
|
||||
(`App::ScopeWindow`, `open_scope_window()`) owns a `RowSet`, a `PlotArea` and the area's placeholder
|
||||
`Plot`, and is driven by the **main window's tick**: the same model mutex binds and acquires its rows
|
||||
(same display mode, same epoch), and its span follows the main window's window or pin every frame. It is
|
||||
therefore a second *view* -- the settle rule is that a scope must never acquire, plan or epoch anything of
|
||||
its own. Closing the main window destroys its scopes first: a leftover toplevel keeps the GApplication's
|
||||
loop alive around a window that is gone (the exit run's hang).
|
||||
|
||||
- **A figure row's full-screen is one click from two places, and never from one figure.** The
|
||||
toggle sits on the row's top-right corner, **out of hiding only while the pointer is near that
|
||||
corner**: its own placed rect inflated by `kMaximizeRevealPx`, per row, so approaching one
|
||||
row's corner reveals that row's toggle and no other. The pointer is read from a
|
||||
`GtkEventControllerMotion` on the **overlay**, not on the GL area -- a revealed toggle sits
|
||||
under the pointer, and a leave on the GL widget would fire the moment the button appeared and
|
||||
hide it again (the flicker the ancestor avoids); a leave on the overlay means the pointer left
|
||||
the plot, which is when the toggles go away. The plot menu offers the same action as *Full
|
||||
screen this figure* / *Restore the layout*;
|
||||
both end in the same `toggle_fullscreen()` -- the `RowState` save/restore, the area's figure,
|
||||
the toggles and the signal panels -- so the two cannot drift apart. A layout of a single
|
||||
figure row carries **no** toggle (and no menu entry): there is nothing to enlarge *to*, and a
|
||||
visible no-op is worse than none. The button reappears while a row *is* maximized, because
|
||||
there it is the way back.
|
||||
Reference in new issue
Block a user