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
@@ -123,6 +123,23 @@ src/ui_badges.cpp the badge bar under the plot: the signal badges, the + badge,
|
||||
src/ui_signal_page.cpp the per-signal slide-over pages (`signal:<name>`)
|
||||
```
|
||||
|
||||
**A row full screen, and a second window.** Each figure row carries a full-screen toggle in its
|
||||
top-right corner: it comes out of hiding while the pointer is near that corner (the button's own
|
||||
rect plus a `kMaximizeRevealPx` of slack, per row) and is invisible otherwise, so the plot is not
|
||||
decorated by controls nobody is reaching for; one click makes that row fill
|
||||
the plot area and the same button restores the whole arrangement; the plot's right-click menu
|
||||
offers the same as **Full screen this figure** / **Restore the layout**, and a layout of a single
|
||||
figure carries no button (there is nothing to enlarge *to*). It is a *view* — the row set
|
||||
saves every row's state on the way in and re-applies it on the way out (`RowState` in
|
||||
`plot_host.*`), so nothing plotted moves and nothing is saved. A plot's right-click menu has
|
||||
**Open this plot in a new window**, which copies that figure — its cells, their width shares and
|
||||
every slot's signal in its colour — into a second top-level window with its own `RowSet`, figure
|
||||
and `PlotArea`; the overflow menu's **New scope window** opens one with an empty figure. A scope
|
||||
window is another *view* of the same acquisition: the main window's tick binds and acquires its
|
||||
rowset under the same model mutex, mirrors the main window's time window (or its pin) onto it
|
||||
every frame, and pumps its render — so the two windows never disagree about the data, the display
|
||||
mode or the span.
|
||||
|
||||
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
|
||||
part is the **signals**: one badge per *enabled* signal (a dot in the signal's colour, filled while the
|
||||
signal is plotted, and its name), then a `+` badge that opens the **Add a signal** page (`g` does the same). That page proposes only what can be
|
||||
@@ -163,8 +180,10 @@ page's label and to the dock's, so the two cannot disagree. **When the dock is n
|
||||
with the dock button, or the window narrow enough (< 900 px, "compact mode") for the dock to be a collapsed
|
||||
overlay on the plot, open or not -- the same readouts appear on **one line under the plot**, above the badge bar (the
|
||||
*readouts strip*): a **Cursors** button (opens the `cursors` page) and one chip per cursor line (its name and
|
||||
its *delta* value), then a **Measures** button (opens the `measure` page) and one chip per measurement (the
|
||||
signal's colour chip, the type, the value; a click opens its dialog). Each of the two groups is there only
|
||||
its *delta* value), then one chip per measurement (the
|
||||
signal's colour chip, the type, the value; a click opens its dialog) and a **`[+]`** after the last chip that
|
||||
adds a new measurement -- the strip is where the readings are watched, so it is where adding one belongs.
|
||||
Each of the two groups is there only
|
||||
with something to read -- the Cursors group with the cursors switched on, the Measures group with at least one
|
||||
measurement, the separator with both -- and **with neither, the strip is not shown at all**.
|
||||
It is a **fixed 26 px** high and its chips are as wide as the dock's cells (name 6 characters, type 10, value
|
||||
@@ -396,7 +415,9 @@ edge the ticks were generated for; the caption always names the anchor the label
|
||||
with, so the addition stays exact.
|
||||
|
||||
**Measurements** (overflow menu) reveals a pane with a `[+]` and one row per measurement:
|
||||
`[badge] <type> <value> [edit]`. `[+]` opens a dialog with the signal, the type, the options
|
||||
`[chips] <N>. <signal> · <type> <value> [edit]` — the number is the signal's among the
|
||||
active channels, the same one the signal's badge in the bar and the dialog's signal dropdown
|
||||
carry. `[+]` opens a dialog with the signal, the type, the options
|
||||
that type actually uses, and "show on plot"; `edit` opens the same dialog on that
|
||||
measurement, with a `Delete` beside `Save`.
|
||||
|
||||
@@ -507,6 +528,21 @@ Both cost: a filtered window disables the two bucket fast paths, because a bucke
|
||||
samples and cannot be mixed into a filtered walk. A filtered window is still walked once and shared by every
|
||||
measurement that asks for it, so a pane of five measurements at two corners costs two walks, not five.
|
||||
|
||||
**The gate measures a slice of the window, and it is a per-measurement choice.** A
|
||||
measurement's dialog carries `Gate enabled`; every gated measurement then describes
|
||||
only the samples inside one shared violet band, carried by its own pair of draggable edges on
|
||||
the plots — *not* the cursors, which keep meaning what the cursors pane reads. The band is
|
||||
shown exactly while some measurement is gated: gate one and it appears, gate or delete the
|
||||
last one and it goes. Ungated measurements on the same signal are untouched — they share the
|
||||
window walks they always shared, and a gated one costs one more walk, not one per pane. The
|
||||
band lives in the same relative time the axis labels do, so it stays exactly where it was put
|
||||
in both modes: on a live view "300 ms ago to 100 ms ago" stays put on screen while the data
|
||||
flows under it — the gated read of the newest data a bench scope gives — and on a held event
|
||||
it names a fixed slice of the frozen frame. Drag an edge to resize, grab the middle to move; a
|
||||
band with no samples in it — dragged outside the view, or over a gap — reads `--` like any
|
||||
window with nothing in it. The band's position and each measurement's gate flag are session
|
||||
state, so a reopened scope measures what the closed one measured.
|
||||
|
||||
### Why a crossing is counted with hysteresis
|
||||
|
||||
A crossing is not read off a single level. It is read off a **band**: two thresholds a tenth of
|
||||
@@ -592,7 +628,15 @@ results must still narrow with the axis (`follows=1`).
|
||||
|
||||
**Show on plot** marks the result on the row that plots the signal: a ruler at the level
|
||||
(`top`/`bottom`), both rulers at the extremes (`min`/`max`), the pulse's or the cycle's two
|
||||
edge times as vertical markers. The marks are **readouts, not instruments**: a fixed pool of
|
||||
edge times as vertical markers. Each mark carries its **badge**: the ruler's own value written
|
||||
in a small opaque panel straddling the ruler — in the y axis' own tick format, so it reads
|
||||
exactly like the tick beside it — and the time mark's time in a box centred on the marker (in
|
||||
the pane's own formatter, so a live view's badge is an age). The boxes exist for the trace
|
||||
underneath: a number drawn bare over a noisy signal is a number nobody can read, and an
|
||||
opaque one is a number nothing prints through. And when marks land close
|
||||
together — two levels a few pixels apart, a pulse's two edges — their badges **merge into one
|
||||
panel with one line per mark**, because two texts over one column is a smear and one boxed
|
||||
readout is a reading. The marks are **readouts, not instruments**: a fixed pool of
|
||||
cursors per row, created **unselectable** (`Plot::set_cursor_selectable()`, added for this), so
|
||||
the controller's hit test never offers the pointer one — it can neither hover nor drag a
|
||||
measurement's ruler. They are also re-placed every frame, because a mark is only correct for
|
||||
@@ -850,15 +894,21 @@ Three things it deliberately is not, and the reasons are in
|
||||
## Captured events
|
||||
|
||||
Trigger mode keeps the last **32 triggered acquisitions** in a cache, so an event you did not have
|
||||
time to look at is still there:
|
||||
time to look at is still there. The folder is the source **and its active configuration** -- an
|
||||
event's trigger, channel set and units are the configuration's, so two configurations of one source
|
||||
do not list each other's events:
|
||||
|
||||
```
|
||||
~/.cache/udpscope/events/192.168.1.40-8000/
|
||||
~/.cache/udpscope/events/192.168.1.40-8000/default/
|
||||
20261009-123250-0001.npy # the event, in the export format
|
||||
20261009-123250-0001.txt # when, how long, how many signals, the rate, the edge
|
||||
20261009-123250-0001.bmp # a picture of it, drawn from the samples
|
||||
```
|
||||
|
||||
Switching configuration re-points the cache with it, and a cache written before configurations
|
||||
existed (loose files directly in `<source>/`) is adopted into the configuration's folder on the
|
||||
first start.
|
||||
|
||||
Events are read in two places. The **dock's Events block** lists them compactly -- one row per event, newest
|
||||
first, at most **24** with a dim "... N more, see Manage" row after them: a marker (a filled circle on the
|
||||
event on screen, a hollow one on each member of the comparison or accumulation on screen), the time and
|
||||
@@ -1067,9 +1117,13 @@ named `default`: copied into the directory, remembered as active, and the old fi
|
||||
|
||||
`~/.config/udpscope/settings.toml`, written by **Preferences…** -- in the overflow menu or the source
|
||||
chooser -- and read at start-up. It holds the event cache's directory, how many events to keep (`0`
|
||||
keeps every one), whether relative time is on, and the interface's theme. Whether anything is cached at
|
||||
keeps every one), whether relative time is on, the interface's theme, and the crosshair's **axis
|
||||
labels** — the two small boxes where the crosshair's lines meet the axes: the y value among the y
|
||||
ticks, the time among the x ticks, each in its axis' own format. On by default; the switch applies at
|
||||
once. Whether anything is cached at
|
||||
all is the trigger pane's own switch, and there is deliberately no second one here; the directory is
|
||||
the one place every source gets a folder inside, `<cache>/udpscope/events` by default. The cache
|
||||
the one place every source gets a folder inside (and every configuration a folder within that),
|
||||
`<cache>/udpscope/events` by default. The cache
|
||||
directory and relative time are *start-up* settings -- the buffers are planned and the cache directory
|
||||
is opened before any source is known -- and the dialog says so; the theme applies as soon as it is
|
||||
chosen. A change
|
||||
@@ -2292,10 +2346,17 @@ the line marks the edge, so the pre/post split can be read straight off the plot
|
||||
not one of the user's cursors: it has its own colour, ignores the cursor show/hide
|
||||
toggle, and is parked (invisible) when there is no event.
|
||||
|
||||
**The status line.** It starts with an activity badge and then the mode, the
|
||||
**The status line.** It starts with the **trigger circle**, then an activity badge and then
|
||||
the mode, the
|
||||
connection, the measured rate, the packet/frame/gap counters, what plot 1 holds and
|
||||
the buffer's size and span. Three of those are worth calling out:
|
||||
the buffer's size and span. Four of those are worth calling out:
|
||||
|
||||
- **The trigger circle is the trigger's own light**, in the same glyph-and-colour alphabet as
|
||||
the badge: solid green while the trigger has fired (an event is held, or a capture is
|
||||
filling), **flashing** green while armed and waiting for an event, red while the trigger
|
||||
mode is on but disarmed — and *hidden* while the trigger mode is off, because a light for a
|
||||
mode the user is not in is noise, and the mode text beside it is not showing trigger words
|
||||
then either. Its tooltip names the state in words.
|
||||
- **The badge blinks green at 1 Hz while the pipeline is actually running** — the
|
||||
stream is connected, a CONFIG has arrived, acquisition is not paused, and in trigger
|
||||
mode the trigger is armed. Anything else leaves it steady, with the reason on its
|
||||
@@ -2800,13 +2861,14 @@ edge.
|
||||
**Rows are edited in place, not chosen from a count.** The header bar used to carry a
|
||||
`plots` dropdown of 1 to 4, and a count is the wrong handle: it says how many rows you
|
||||
want and leaves which row your traces land on to the renumbering. **Layout editor**
|
||||
(overflow menu) instead puts the controls on the rows — a `×` at the top-left of each row
|
||||
(overflow menu, a plot's right-click menu, or `l`) instead puts the controls on the rows — a `×` at the top-left of each row
|
||||
removes it, a `+` on each boundary inserts one there — so the row an action names is the
|
||||
row you are pointing at. `--plots` is now only the count to start with, and `RowSet::kMaxRows`
|
||||
(8) is a ceiling rather than a menu: past about that a row is chrome and no plot.
|
||||
|
||||
The editor also carries its own way out: a **`Done`** button in the plot area's top-right
|
||||
corner while it is up, because the toggle that opened it lives in the overflow menu — a
|
||||
corner while it is up, because the toggle that opened it lives in the overflow menu (and in a
|
||||
plot's right-click menu, as *Edit the layout...* / *Done editing the layout*) — a
|
||||
long way from the plot being edited. It turns that toggle off, so there is still exactly
|
||||
one piece of state, and the buttons (its own included) go with it.
|
||||
|
||||
|
||||
Reference in new issue
Block a user