docs: the badge bar, the slide-over and the dock (step 1 of the redesign)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 21:03:03 +01:00
1 parent a1672acc70
commit 0c94caf866
7 files changed
+203 -70

No files matched your search

+76 -39
View File
@@ -73,7 +73,7 @@ switches trigger mode on, waits for an event, then leaves trigger mode the way t
"Go live" button does, closes the window, and prints on stdout the status line in
both states, the frame and per-channel sample counts, each plot's painted-frame
count, which channel each trace slot holds, and the allocated size and reveal
state of both side panels.
state of the dock and the events pane.
Every size it prints is read from a value the frame tick captured while the window was alive, not
from the widget: the dump runs from the destroy handler, where the widget pointer is already freed
@@ -96,9 +96,24 @@ src/plot_host.* the one plot::Figure, its rows' traces and cursors, the x lin
src/export.* the visible range as a table, and the .npy / .csv / .mf3 writers
src/layout_store.* the per-source session layout file (TOML), written and read back
src/event_series.hpp an immutable captured event as a plot::data::Series
src/ui.* the GTK4 window: toolbar, trigger panel, signal panel, plot box
src/ui.* the GTK4 window: toolbar, trigger form, signal list, plot box, the dock
src/ui_slideover.cpp the slide-over panel over the plot's right edge, one page at a time
src/ui_badges.cpp the badge bar under the plot: the Trigger and Source badges
```
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px) whose
right-hand end carries two words-only badges: **Trigger** (`off`, `armed`, `capturing`, `triggered`
or `disarmed`) and **Source** (the kind's name and `connected`/`disconnected`; a read-out, not a button,
for a source with no controls of its own, and for a file). A badge opens the **slide-over**: a 360 px
panel laid over the plot's right edge, opaque, with its own title, a *Close* button and one scroller per
page. The Trigger badge opens the trigger form; the Source badge opens the source kind's own panels
(the E-1608's *Analog out* and *Digital I/O*, behind a segmented switch at the top of the panel). It
closes with a second click on its badge, with `Esc`, with *Close*, or with a click on the plot; it never
resizes the plot. The right-hand **dock** (the existing sidebar, 320 px) holds what is left of the old
tabs in one scroller: the signal list, the cursors and the measurements, shown from the start and
insensitive until the stream has signals. It is temporary -- see "Review findings not (yet)
implemented" for what the redesign still has to do with it.
Threading: one producer thread owns the data source (the UDPStreamer client is the built-in
one) and calls `poll()`; the GTK main thread owns every widget and the GL contexts.
They share `ScopeModel` under one mutex, which the producer takes once per DATA
@@ -1006,7 +1021,9 @@ offset = 0.5
```
It also stores `measure_stats` (the measurements statistics toggle, a boolean, absent means
`false`). It is written (400 ms after the last change, and flushed on close) whenever the
`false`) and `dock` (whether the dock is shown, a boolean, absent means `true`; it replaced the
sidebar's `side_tab`, and a file that still carries `side_tab` loads, the key being ignored). The
open slide-over page is not stored: a panel that opens by itself at start-up would be a surprise. It is written (400 ms after the last change, and flushed on close) whenever the
window, the layout, a drop or a calibration changes, and applied once, when the
first CONFIG names the signals -- signal *names* are the identity, because a
CONFIG renumbers and an enable/disable rebuilds the channel list. Signals the
@@ -1151,18 +1168,23 @@ up, the same delay the first-run auto-start uses, the pending request starts the
chooser's rows show **no accelerators** -- the window prints `F1`/`Ctrl+T` because the window binds
them, and this dialog binds none.
**The guided tour is five steps on a `GtkOverlay`** inserted above the window's content: the plot,
the side panel, the trigger, the signals and the status line. There is no popover anywhere in it and
**The guided tour is six steps on a `GtkOverlay`** inserted above the window's content: the plot,
the badge bar, the trigger form, the dock, the signals and the status line. There is no popover anywhere in it and
therefore no pointer grab to lose: what makes the tour modal is four dim rectangles -- above, below
and either side of the target -- that swallow the clicks that land on them. A stop declares the
**page it stands on** (the trigger and the signals steps live on the sidebar's *Signals* page, named
in `TourStop::page`), and the tour selects that page as the step is entered -- on the frame *after*
the switch, since a stack allocates a new page's widgets a frame late -- so the spotlight lands on a
widget the user had left behind on the other tab instead of on nothing. `gtk_widget_get_mapped()`
decides which steps exist at all: a stop on the *wrong page* is kept, because selecting its page
reveals it, while one whose *pane is collapsed* is dropped, because the stack is unmapped with it
(a hidden page keeps its own *visible* flag true -- the widget's own flag, which is all
`is_visible()` walks -- so the old test walked over surfaces that were not on screen). The chooser's
**slide-over page it stands on** (the trigger step names `trigger`, in `TourStop::page`): the tour opens
that page as the step is entered, and a stop with no page *closes* the panel, so the card never points
at the status line over an open panel. It does not spotlight the form until the panel has finished
sliding in (about 120 ms, during which the surface is dimmed and the card waits in the corner), and the
hole is the target's bounds clipped by every scroller above it -- on the documented 756x470 window the
trigger form is taller than the panel, and the spotlight covers the part that is on screen, not the
badge bar under it. Skip, Escape and the last step close a panel the tour opened (an idle dismissal
leaves one the user opened alone). `gtk_widget_get_mapped()`
decides which steps exist at all: a stop on a slide-over page is always kept, because opening the page
reveals it, while one whose *pane is collapsed* (the dock, in a window too narrow to put it beside the
plot) is dropped, because it is unmapped with its pane (a hidden page keeps its own *visible* flag
true -- the widget's own flag, which is all `is_visible()` walks -- so the old test walked over
surfaces that were not on screen); a narrow window therefore walks four steps. The chooser's
tour works the same way over its own two pages: its tabs step shows the *Waveforms* page and
stepping off it -- in either direction -- brings the *Sources* page back.
@@ -2780,12 +2802,13 @@ reaches every widget). The full key table:
|---|---|
| `a` | arm / disarm the trigger |
| `Space` | pause / resume |
| `t` | show / hide the trigger panel |
| `c` | show / hide cursors |
| `m` | show / hide measurements |
| `g` | show / hide math channels |
| `t` | enable / disable trigger mode |
| `c` | enable / disable the cursors |
| `m` | go to Measure (scroll the dock to the cursors and the measurements) |
| `g` | go to Signals (scroll the dock back to the signal list) |
| `l` | show / hide the layout editor |
| `s` | show / hide the side panel |
| `s` | show / hide the dock |
| `Esc` | close the slide-over panel (the guided tour takes it first) |
| `f` | fit to data (the plot's own key, not an application accelerator) |
| `Ctrl+E` | export visible range |
| `F1` | open the manual (`help:org.udpscope.app`) in the desktop help viewer |
@@ -2897,32 +2920,42 @@ now — they are listed so they are not mistaken for anything else:
progress. What the spec was protecting still holds — a *held* event is bound to its
snapshot alone, and the axis is the event's own window from the edge onwards rather
than a sliding live view.
- **The sidebar is two tabs, and a math channel is a row of the signal list.** `SPECS.md`
names the panes as separate collapsible sections — trigger, cursors, measurements, math,
signal list — in one scrolling column; with four of them open in a 320 px sidebar every
section was short of room, and finding one meant scrolling past the others. The built
sidebar is a **Measure** tab (the cursors readout and the measurements) and a **Signals**
tab (the trigger and the channels), **each a text tab** (the sidebar was icon-only for a while; glyphs for "cursors and ruler" and
"trigger and channel list" were not understandable enough, so the words won). A source kind's own tabs
(the E-1608's **Analog out** and **Digital I/O**) sit beside them as text too. Both panes also have a **locked** mode, which is what a returning user gets: the
- **The sidebar is one dock, setup is a slide-over, and a math channel is a row of the signal list.**
`SPECS.md` names the panes as separate collapsible sections -- trigger, cursors, measurements, math,
signal list -- in one scrolling column; with four of them open in a 320 px sidebar every
section was short of room, and finding one meant scrolling past the others. The sidebar was then two
text tabs (a **Measure** tab and a **Signals** tab, with a source kind's own tabs beside them), and four
tabs did not fit 320 px. It is **one dock** now, with no tab switcher: the signal list, the cursors and the
measurements in one scroller, shown from the start and insensitive until the stream has signals; `m` and
`g` scroll it to the cursors and back to the signal list. The **trigger form** and a **source's own
controls** are pages of the slide-over, reached from the badge bar. Both panes also have a **locked** mode, which is what a returning user gets: the
trigger pane shows one line saying what it is set to, the signal list shows only the signals that are
on, and neither offers a control that changes the configuration — each heading has an `[edit]` button
to switch to the full pane, and back. Dragging a signal onto a plot still works while locked — filling
plots is what the list is for, not configuration — and a locked row can be dragged from anywhere on it,
the colour chip included. In a window too narrow to show both, where the sidebar is *overlaid on* the
plot, the sidebar steps aside for the length of the drag so there is something under the pointer to drop
on, and neither offers a control that changes the configuration -- each heading has an `[edit]` button
to switch to the full pane, and back. Dragging a signal onto a plot still works while locked -- filling
plots is what the list is for, not configuration -- and a locked row can be dragged from anywhere on it,
the colour chip included. In a window too narrow to show both, where the dock is *overlaid on* the
plot, the dock steps aside for the length of the drag so there is something under the pointer to drop
onto. A **first run** (no session file for that source) comes up
unlocked, because configuring the host is what a first run is for. Each pane's heading also carries its **own switch**, the same control as the
header bar's button — clicking either moves both — because a pane that is *disabled* while its
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The
sidebar is one **dock** now, with no tab switcher: the signal list, the cursors and the measurements
in one scroller, shown from the start and insensitive until the stream has signals; `m` and `g`
scroll it to the cursors and back to the signal list. With the math
list folded into the signal list under a `MATH` divider whose `+` creates a channel — a derived channel already *is* a channel in
header bar's button -- clicking either moves both -- because a pane that is *disabled* while its
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. With the math
list folded into the signal list under a `MATH` divider whose `+` creates a channel -- a derived channel already *is* a channel in
the model, so the two lists were the same list drawn twice. No
toggle hides a pane any more: cursors-off **disables** its readout and leaves it in place, and
trigger-off leaves the trigger panel fully editable — a trigger is configured *before* it is
switched on, and the locked view says `stream (trigger off)` instead of the settings — and the measurements' and math panes' toolbar toggles are gone with the panes.
trigger-off leaves the trigger form fully editable -- a trigger is configured *before* it is
switched on, and the locked view says `stream (trigger off)` instead of the settings -- and the measurements' and math panes' toolbar toggles are gone with the panes.
- **The badge bar / slide-over / dock redesign is in progress; this is its first step, and it deviates
from `docs/superpowers/specs/2026-11-02-ui-badges-dock-design.md` in three known ways.**
(1) The dock keeps the sidebar's **320 px**, not the spec's ~280: the panels it holds were built for
320, and the width changes when they become compact readouts (step 3). (2) The session file gains the
dock's *shown* state only, not a width -- the dock's width is fixed, so there is nothing to persist
-- and does not store the open slide-over page. (3) The **Trigger badge** shows `off`, `armed` and
`triggered` as the spec says, and two more words, `capturing` (a post-trigger window is filling) and
`disarmed` (trigger on, not armed), so the label never claims a state the engine is not in. Still to
come, in the spec's order: the signal badges and drag (2), the compact readouts (3), the events
section in the dock, the deletion of the left pane and the Events toggle, and `USER-GUIDE.md` and the
manual's figures (4). Until then the events pane, the header's Events toggle and the manual still
describe the previous layout.
- **A plot holds at most eight signals** (`RowSet::kSlotsPerRow`), because a `Plot`
cannot give up a trace and the slots are therefore created up front.
- **One toggle drives both cursor orientations.** The vertical markers and the
@@ -3016,6 +3049,10 @@ silence timeout and then resumes, which is the EMI case end to end.
rather than rebuilds, and why the frame tick reads the markers back: a parked
cursor's snapped position is a real number, so nothing may be inferred from it. The
trigger marker is separate: its own cursor, independent of the cursor toggle.
- **The slide-over shows a form taller than the plot by scrolling it.** The panel is as tall as the
plot and never taller, and each page has a scroller of its own, so on the documented 756x470 window
the trigger form (about 474 px) shows 253 px of itself. The tour's spotlight is clipped to that, and at
that size the card of the trigger step overlaps the panel's edge by about 18 px (`clear=0`).
- **Verified headless only.** Acquisition, partitioning, the trigger state
machine and its integration with the buffers, and rendering under Xvfb
(llvmpipe) were exercised; the interactive paths (drag and drop, the dialogs,