From 5242babf993d99f1f8f3b98af355e6d1de6e317b Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Tue, 3 Nov 2026 01:48:43 +0100 Subject: [PATCH] docs: the signal badges, the signal pages and the six-badge overflow (step 2 of the redesign) Also wrap the signals page's hint line, which at 375 px minimum widened the whole slide-over panel to 376. Co-Authored-By: Claude Opus 4.6 --- README.md | 26 ++++++++++++++++------- docs/claude/architecture.md | 14 +++++++----- docs/claude/invariants-gtk-and-chrome.md | 2 +- docs/claude/invariants-plot-and-layout.md | 15 +++++++++++-- docs/claude/probe-readings.md | 5 +++-- docs/claude/verification.md | 5 +++++ src/ui/signals-panel.ui | 2 ++ src/ui_tour.cpp | 7 +++--- 8 files changed, 55 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 32ffef5..8f3f433 100644 --- a/README.md +++ b/README.md @@ -98,11 +98,20 @@ src/layout_store.* the per-source session layout file (TOML), written and read b src/event_series.hpp an immutable captured event as a plot::data::Series 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 +src/ui_badges.cpp the badge bar under the plot: the signal badges, the + badge, Trigger and Source +src/ui_signal_page.cpp the per-signal slide-over pages (`signal:`) ``` -**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` +**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 full signal list (`g` does the same). +A signal badge opens that signal's own page in the slide-over -- colour, calibration and the *Plot on N* +buttons -- and is also the drag handle: drag it onto a plot to draw the signal there, or onto another +badge to reorder them. Past **six** signal badges the bar shows a single **"N signals"** badge in their +place (it opens the list, and `+` is hidden): the E-1608's 8 analog inputs and counter would not fit +otherwise. The signal badges sit in a scroller, so the bar's minimum width, and with it the window's, +never depends on how many signals there are. The badge order is kept **for the session only**; it is not +written to the session file. The 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 @@ -110,9 +119,9 @@ page. The Trigger badge opens the trigger form; the Source badge opens the sourc (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. +tabs in one scroller: the cursors and the measurements, shown from the start and insensitive until +the stream has signals (the signal list moved to the `signals` slide-over page). 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. @@ -2950,8 +2959,9 @@ now — they are listed so they are not mistaken for anything else: 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 + `disarmed` (trigger on, not armed), so the label never claims a state the engine is not in. The signal badges, the + signal pages and the drag (step 2) are done. Still to + come, in the spec's order: 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. diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md index 67454b7..10319af 100644 --- a/docs/claude/architecture.md +++ b/docs/claude/architecture.md @@ -99,19 +99,23 @@ touching the data path. - **`SlideOver`** (`src/ui_slideover.cpp`) is one `GtkRevealer` holding a `GtkStack`, added as the second child placed *over* the plot (after the layout editor's buttons) in `GtkOverlay#plot_overlay`. A page is a `slideover_add_page(app, id, title, widget)`: the widget is wrapped in a `GtkScrolledWindow` of its - own and registered under its id (`trigger`, `source-`; `signal:`, `signals`, `measure`, - `cursors` and `events` are reserved for later steps). One page is open at a time and `App::slideover.page` + own and registered under its id (`trigger`, `source-`, `signals` (the signal list) and + `signal:` (one signal's page, `src/ui_signal_page.cpp`, created on demand by `signal_page_ensure()` + and pruned by `signal_pages_sync()`, which `rebuild_signal_panels()` ends with); `measure`, `cursors` + and `events` are reserved for later steps). One page is open at a time and `App::slideover.page` is its id, empty when closed. The open page is **not** a session value. Esc, *Close*, a second click on the badge and a click on the plot close it; the click-away is a capture-phase observer (see [invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). With two or more `source-*` pages, an `AdwToggleGroup` under the header is the segmented switch between them. - **`BadgeBar`** (`src/ui_badges.cpp`) is the fixed-height row between the plot and the status line - (`main-window.ui` `badge_bar`, two slots: signals, left, empty until step 2; system, right, holding the - **Trigger** and **Source** badges). A badge is a flat button whose `page` is the slide-over page it + (`main-window.ui` `badge_bar`, two slots: signals, left, a scroller holding one badge per enabled signal + and the `+`, or a single "N signals" badge past six; system, right, holding the **Trigger** and + **Source** badges). `sync_signal_badges()` rebuilds the signal badges (from `rebuild_signal_panels()`); + their order is `App::badge_order`, in memory only. A badge is a flat button whose `page` is the slide-over page it toggles; the Source badge's page is the first `source-*` page, and a kind with none (UDPS, Flat UDP, a file) leaves it a read-out with `can_target` off. - **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`): the - signal list, the cursors and the measurements in one scroller. It holds no tab switcher and no + cursors and the measurements only, in one scroller (the signal list is the `signals` page). It holds no tab switcher and no revealer, and it does not hold a source kind's panels (those are slide-over pages). **When each is built and when it is read.** `build_slideover()` runs in `on_activate` right after the tour diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index 2d630e3..deb5551 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -225,7 +225,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear take room has to be hidden and not merely unrevealed; the revealer keeps the slide while the pane *is* open. The allocated width is how this was found -- the revealer's own measure said 0. **The sidebar is one dock now, and has no `GtkRevealer` left in it**: a single `GtkScrolledWindow` - (`dock_scroller`) holding the signal list, the cursors and the measurements one under another. It + (`dock_scroller`) holding the cursors and the measurements one under another. It replaced an `AdwViewSwitcher` over an `AdwViewStack`, whose four text tabs did not fit 320 px (and a source can add more); a source kind's own panels are slide-over pages, not dock sections. The reason is the one this trap teaches: sections competing for one 320 px column each left every one of them short of room, and diff --git a/docs/claude/invariants-plot-and-layout.md b/docs/claude/invariants-plot-and-layout.md index b96155d..52cbe9b 100644 --- a/docs/claude/invariants-plot-and-layout.md +++ b/docs/claude/invariants-plot-and-layout.md @@ -110,7 +110,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear does not propagate its child's height), and content that is taller than the plot -- the trigger form on a 470 px window -- scrolls inside the panel instead of growing it. The widgets that go in a page are reparented into it; their widths must fit 360 minus the border (the trigger form measures 267 - minimum, the E-1608's panels 197). + minimum, the E-1608's panels 197). **One page wider than that widens the panel + for all of them** (the stack measures every page): the signals page's hint line once did, unwrapped, and + `[slideover] panel=` read 376 instead of 360; the label now wraps. - **The badge bar has a fixed height and sits outside the overlay.** `badge_bar` is 36 px (`height-request`) between `plot_overlay` and the status line, so the plot above it never moves when a badge's text changes (`bar_h` = `bar_h_after` in `[badges]`). This is "layout must not depend on @@ -119,8 +121,17 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear spare on the right) and never its height. The badge labels are not ellipsized: a name long enough to need it would be a defect of the kind's `display_name()`. Rows that appear by trigger condition appear inside the slide-over's scroller, never in the bar. +- **The signal badges live in a scroller, so the bar's minimum width never depends on the signal count.** + The bar's signals slot holds a `GtkScrolledWindow` (horizontal `EXTERNAL`, vertical `NEVER`) around one + row of badges and the `+`, because a button row's minimum width is the sum of its children: nine badges + (the E-1608: eight inputs and the counter) would push the window's minimum width past a laptop screen. + Past `kMaxSignalBadges` (6) the row is replaced by one "N signals" badge, which also stands for the `+`. + The bar's height stays 36 px in every one of these states (`bar_h_fixed` in `[sigbadges]`), and + `bar_min_w` reads the same with one signal, nine signals and the overflow (329 on the E-1608, 336 on UDPS: + the Source badge of each kind reads differently). A change that makes it grow with the signal count has + taken the scroller out of the measurement. - **The dock's width is fixed too** (`side_column` `width-request` 300, the split view's 320 px floor - and cap), and it holds one scroller with the signal list, the cursors and the measurements. A + and cap), and it holds one scroller with the cursors and the measurements. A section's visibility is never what makes the dock taller; the scroller is what absorbs the extra. - **Trace slots are identified by signal name, not channel index.** Enabling a signal or a new CONFIG renumbers the channel list, so an index would silently re-point a diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 72d034d..143253c 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -987,7 +987,8 @@ two also catch a row whose accelerator went missing. `settings=` must be a real in the `[helpui]` entry above, where `raw`, `target` and `inside` are explained). `clear=0` is read as geometry, not as a placement bug, on the plot stop (the target is the whole content) and on the trigger stop at the documented 756 px window (the panel is 360 px of it, so the card has no side wide enough and goes flush - left over the plot, overlapping the panel's edge by a few pixels). + left over the plot, overlapping the panel's edge by a few pixels), and on the `signals` stop for the same + reason (the dock beside it is 320 px and the card has no side wide enough). - `[toursurface] finished: running=0 slideover=none` -- the last Next ended the walk and **the panel is closed with it**. - `[toursurface] restarted at trigger: running=1 slideover=trigger` then @@ -2664,7 +2665,7 @@ signals list while the title and the badge marker still name `trigger`; `-1` whe (`kMaxSignalBadges`); beyond that `badges=0 overflow=1 overflow_text= signals plus_visible=0` (the "N signals" badge replaces the badges and the "+"). `dot_plotted` is the number of badges whose signal is on a plot (its dot is then the trace colour, not the unplotted grey). `bar_h` is the bar's allocated height and `bar_h_fixed=1` that neither it nor its natural height moved while one signal was switched off and on again (`rebuild_signal_panels()` each time), and that the badge count followed; `0` is a bar whose height depends on its content. -`bar_min_w` is only a record: the scroller must keep it the same however many signals there are (it reads 336 with the mock's 4 and with the overflow forced on). `opens=1`: clicking the +`bar_min_w` is only a record: the scroller must keep it the same however many signals there are (it reads 336 with the UDPS mock's 4 and with the overflow forced on, and 329 against the E-1608 mock with one signal and with nine -- the overflow, `enabled=9 badges=0 overflow=1 overflow_text=9 signals plus_visible=0 bar_h=36 bar_h_fixed=1`; a value that grows with the signal count is a scroller that has stopped constraining the bar). `opens=1`: clicking the first signal badge opens `signal:` with the badge marked open and a second click closes it (`-1` when overflowed, there being no signal badges); `plus_opens=1` the same for the "+" (the overflow badge when overflowed) and the `signals` page. diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 03001da..7c749b1 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -447,6 +447,11 @@ precedent, not an exception. read `click_inside_closes=0 still_open=1 click_outside_closes=1 esc_closes=1 closed=1`. For a screenshot add `UDPSCOPE_PROBE_SLIDEOVER_HOLD=1`, which leaves the page open after line 1 (run with `GSK_RENDERER=cairo` and `import -window root`): the panel must be drawn over the traces, opaque. +- **Slide-over and page probes (`SLIDEOVER`, `BADGES`, `SIGNAL_PAGES`, `SIGNAL_BADGES`) need a config that has already seen + the first-run tour**: a fresh `XDG_CONFIG_HOME` auto-starts the tour at about 1.5 s, and a tour stop with no page closes the + slide-over under the probe. Seed it with `mkdir -p $D/udpscope && printf 'tour_seen = true\n' > $D/udpscope/settings.toml` + (or one short throw-away run, which writes the key when the tour starts) and run with `XDG_CONFIG_HOME=$D`. Delete the + per-source subdirectory (`$D/udpscope/_`) between runs that must start from a first-run layout. - `UDPSCOPE_PROBE_SIGNAL_PAGES_MS=1500` with `UDPSCOPE_EXIT_AFTER_MS=5000` and `UDPSCOPE_ENABLE_ALL_MS=500` -- the per-signal `signal:` slide-over pages. **Against a config that has already seen the tour** (a first run's auto-tour closes the slide-over at about 1.5 s, which is when this probe opens its page). One `[signalpage]` line; diff --git a/src/ui/signals-panel.ui b/src/ui/signals-panel.ui index 66fc09a..33c775a 100644 --- a/src/ui/signals-panel.ui +++ b/src/ui/signals-panel.ui @@ -55,6 +55,8 @@ click a signal for its page; drag its badge onto a plot start + 0 + 1 1 dim-label diff --git a/src/ui_tour.cpp b/src/ui_tour.cpp index a17f297..9085d63 100644 --- a/src/ui_tour.cpp +++ b/src/ui_tour.cpp @@ -445,9 +445,10 @@ constexpr TourStep kTourSteps[] = { "zooms, a drag pans, and a right-click on a row opens that plot's own menu.", &App::area_overlay}, {"badges", nullptr, "The badges", - "Setup is reached by the thing it configures. The Trigger badge opens the trigger form; the " - "Source badge opens the source's own controls. Click it again, press Esc or click the plot to " - "close the panel.", + "Setup is reached by the thing it configures. A badge per signal opens that signal's colour, " + "calibration and plot; the + opens the full list; Trigger and Source open their own forms. Drag a " + "signal badge onto a plot to draw it there, or onto another badge to reorder. Click again, press " + "Esc or click the plot to close the panel.", &App::badge_bar}, {"trigger", "trigger", "The trigger", "Describe an event -- a signal, a condition, a level and the window around it -- and arm it: "