Feature/source plugins #1

Merged
martino merged 130 commits from feature/source-plugins into main 2026-11-04 07:35:21 +01:00
8 changed files with 55 additions and 21 deletions
Showing only changes of commit 5242babf99 - Show all commits

No files matched your search

+18 -8
View File
@@ -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/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.* 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_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:<name>`)
``` ```
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px) whose **The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
right-hand end carries two words-only badges: **Trigger** (`off`, `armed`, `capturing`, `triggered` 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, 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 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 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 (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 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 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 tabs in one scroller: the cursors and the measurements, shown from the start and insensitive until
insensitive until the stream has signals. It is temporary -- see "Review findings not (yet) the stream has signals (the signal list moved to the `signals` slide-over page). It is temporary -- see
implemented" for what the redesign still has to do with it. "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 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. 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 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 -- 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 `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 `disarmed` (trigger on, not armed), so the label never claims a state the engine is not in. The signal badges, the
come, in the spec's order: the signal badges and drag (2), the compact readouts (3), the events 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 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 manual's figures (4). Until then the events pane, the header's Events toggle and the manual still
describe the previous layout. describe the previous layout.
+9 -5
View File
@@ -99,19 +99,23 @@ touching the data path.
- **`SlideOver`** (`src/ui_slideover.cpp`) is one `GtkRevealer` holding a `GtkStack`, added as the second - **`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 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 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-<id>`; `signal:<name>`, `signals`, `measure`, own and registered under its id (`trigger`, `source-<id>`, `signals` (the signal list) and
`cursors` and `events` are reserved for later steps). One page is open at a time and `App::slideover.page` `signal:<name>` (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 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 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 [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. `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 - **`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 (`main-window.ui` `badge_bar`, two slots: signals, left, a scroller holding one badge per enabled signal
**Trigger** and **Source** badges). A badge is a flat button whose `page` is the slide-over page it 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 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. file) leaves it a read-out with `can_target` off.
- **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`): the - **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). 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 **When each is built and when it is read.** `build_slideover()` runs in `on_activate` right after the tour
+1 -1
View File
@@ -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 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. *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` **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 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 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 this trap teaches: sections competing for one 320 px column each left every one of them short of room, and
+13 -2
View File
@@ -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 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 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 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 - **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 (`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 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 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 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. 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 - **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. 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 - **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 or a new CONFIG renumbers the channel list, so an index would silently re-point a
+3 -2
View File
@@ -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 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 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 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 - `[toursurface] finished: running=0 slideover=none` -- the last Next ended the walk and **the panel is
closed with it**. closed with it**.
- `[toursurface] restarted at trigger: running=1 slideover=trigger` then - `[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=<N> signals plus_visible=0` (the "N signals" badge replaces the badges and the "+"). `dot_plotted` is the number of (`kMaxSignalBadges`); beyond that `badges=0 overflow=1 overflow_text=<N> 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 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. 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:<name>` 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 "+" first signal badge opens `signal:<name>` 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. (the overflow badge when overflowed) and the `signals` page.
+5
View File
@@ -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 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` 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. 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/<host>_<port>`) 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 - `UDPSCOPE_PROBE_SIGNAL_PAGES_MS=1500` with `UDPSCOPE_EXIT_AFTER_MS=5000` and `UDPSCOPE_ENABLE_ALL_MS=500` -- the
per-signal `signal:<name>` slide-over pages. **Against a config that has already seen the tour** (a first run's per-signal `signal:<name>` 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; auto-tour closes the slide-over at about 1.5 s, which is when this probe opens its page). One `[signalpage]` line;
+2
View File
@@ -55,6 +55,8 @@
<object class="GtkLabel" id="signals_hint"> <object class="GtkLabel" id="signals_hint">
<property name="label" translatable="yes">click a signal for its page; drag its badge onto a plot</property> <property name="label" translatable="yes">click a signal for its page; drag its badge onto a plot</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="xalign">0</property>
<property name="wrap">1</property>
<property name="hexpand">1</property> <property name="hexpand">1</property>
<property name="css-classes">dim-label</property> <property name="css-classes">dim-label</property>
</object> </object>
+4 -3
View File
@@ -445,9 +445,10 @@ constexpr TourStep kTourSteps[] = {
"zooms, a drag pans, and a right-click on a row opens that plot's own menu.", "zooms, a drag pans, and a right-click on a row opens that plot's own menu.",
&App::area_overlay}, &App::area_overlay},
{"badges", nullptr, "The badges", {"badges", nullptr, "The badges",
"Setup is reached by the thing it configures. The Trigger badge opens the trigger form; the " "Setup is reached by the thing it configures. A badge per signal opens that signal's colour, "
"Source badge opens the source's own controls. Click it again, press Esc or click the plot to " "calibration and plot; the + opens the full list; Trigger and Source open their own forms. Drag a "
"close the panel.", "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}, &App::badge_bar},
{"trigger", "trigger", "The trigger", {"trigger", "trigger", "The trigger",
"Describe an event -- a signal, a condition, a level and the window around it -- and arm it: " "Describe an event -- a signal, a condition, a level and the window around it -- and arm it: "