diff --git a/README.md b/README.md index cba3458..60ef547 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,11 @@ place (it opens the list, and `+` is hidden): the E-1608's 8 analog inputs and c 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 only -- the link state is the status bar's; 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 +or `disarmed`) and **Source** (the kind's name only -- the link state is the status bar's). **Source is shown only when the source +has panels of its own** (the E-1608's); for UDPS, Flat UDP and a file there is nothing for it to open, so it is hidden +(the bar keeps its height; the kind's name is in the status bar). 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 +page. The Trigger badge opens the trigger form; the Source badge (when shown) 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 diff --git a/docs/claude/architecture.md b/docs/claude/architecture.md index 7446c30..9487074 100644 --- a/docs/claude/architecture.md +++ b/docs/claude/architecture.md @@ -113,7 +113,9 @@ touching the data path. **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. + file) leaves it **hidden** (the user asked for hide here, not the usual disable): `sync_badge_bar()` sets its visibility, + pick and focus from `slideover.source_pages` being non-empty every tick, so anything that indexes the badge + (the tour's `badges` step targets the whole bar, `[badges]`) must tolerate it being absent. - **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`): the 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). diff --git a/docs/claude/invariants-plot-and-layout.md b/docs/claude/invariants-plot-and-layout.md index 1b962d5..89c4ac3 100644 --- a/docs/claude/invariants-plot-and-layout.md +++ b/docs/claude/invariants-plot-and-layout.md @@ -117,7 +117,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear (`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 content" for the new row: the Trigger badge's state is a word from a short fixed set and the Source badge - is a kind's display name only (no connection state: that is the status bar's), so what changes with the stream is the row's *width* (the bar has room to + (shown only when the kind has source pages; hidden otherwise) is a kind's display name only (no connection state: that is the status bar's), so what changes with the stream is the row's *width* (the bar has room 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 inside the slide-over's scroller, never in the bar. @@ -127,8 +127,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear (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 (244 on the E-1608, 251 on UDPS: - the Source badge of each kind reads differently). A change that makes it grow with the signal count has + `bar_min_w` reads the same with one signal, nine signals and the overflow (244 on the E-1608, 124 on UDPS: + the Source badge is on screen for the E-1608 and hidden for UDPS, and its text differs by kind). 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 cursors and the measurements. A diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 717ee86..96e35cd 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -2663,14 +2663,14 @@ signals list while the title and the badge marker still name `trigger`; `-1` whe ## `[sigbadges]` ``` -[sigbadges] enabled=4 badges=4 overflow=0 overflow_text=- plus_visible=1 dot_plotted=4 bar_h=36 bar_h_fixed=1 bar_min_w=251 opens=1 plus_opens=1 +[sigbadges] enabled=4 badges=4 overflow=0 overflow_text=- plus_visible=1 dot_plotted=4 bar_h=36 bar_h_fixed=1 bar_min_w=124 opens=1 plus_opens=1 ``` `UDPSCOPE_PROBE_SIGNAL_BADGES_MS` reads the signal badges. `badges` must equal `enabled` (the enabled, listed signals) while `enabled` is at most 6 (`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 251 with the UDPS mock's 4 and with the overflow forced on, and 244 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 +`bar_min_w` is only a record: the scroller must keep it the same however many signals there are (it reads 124 with the UDPS mock's 3 (the Source badge being hidden there) and with the overflow forced on, and 244 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. @@ -2691,7 +2691,7 @@ gesture's threshold, the slide-over closing under it and the pointer-driven drop ``` [badges] table_bad=0 trigger_state=off bar_h=36 bar_h_after=36 fixed=1 open_marked=1 page_open=1 closed_unmarked=1 -[badges] source name=UDPStreamer state=connected pages=0 switch_visible=0 can_target=0 second_page=- second_ok=-1 open_marked=-1 closed_unmarked=1 panel_min_w=0 +[badges] source name=UDPStreamer pages=0 visible=0 switch_visible=0 can_target=0 second_page=- second_ok=-1 open_marked=-1 closed_unmarked=1 panel_min_w=0 ``` `UDPSCOPE_PROBE_BADGES_MS` reads the badge bar under the plot. **Line 1** is the Trigger badge: `table_bad=0` (the rows of the 16-input @@ -2700,12 +2700,13 @@ the same, and at least 36 px, before and after the `trigger` page was opened and moves), `open_marked=1` / `page_open=1` / `closed_unmarked=1` (the `udpscope-badge-open` class follows the slide-over's page, both ways; `0` on any is a badge that does not say its panel is open, or one that stays marked after it closed). -**Line 2** is the Source badge, and every field has a value it must read: +**Line 2** is the Source badge, which is **hidden when the source has no pages** (UDPS, Flat UDP, a file), and every field has a value it must read: - `name=` the source kind's name, as the badge shows it. The badge carries no link state (the status bar says it); there is no `state=` field. - `pages=` the number of slide-over pages the source kind contributes (`source-`): `0` for UDPS, `2` for the E-1608 (`source-ao`, `source-dio`). - `can_target=0` with `pages=0` is the UDPS badge being a read-out rather than a button; `can_target=1` with `pages>0` is the button being live. + `can_target=0` with `pages=0` is the UDPS badge being untargetable, which goes with it being hidden; `can_target=1` with `pages>0` is the button being live. `can_target=1` with `pages=0` is a dead button that opens nothing. +- `visible=` the badge being on screen: **`0` with `pages=0`** (hidden, by design: nothing to open), `1` with `pages>0`. `visible=1` with `pages=0` is a dead pill that was supposed to be hidden; `visible=0` with pages is a source panel nothing can open. - `switch_visible=` the segmented switch being on screen after the first source page was opened: `1` with two or more pages, `0` with one or none. A switch shown for a single page is a control that cannot change anything; hidden with two, the second panel is unreachable. - `second_page=` / `second_ok=` what `slideover_page()` read after the switch selected the second page, and whether it is that page. **`second_ok=1` with diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 25f5d9f..8a07d37 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -473,12 +473,13 @@ precedent, not an exception. **The source kind's panels are slide-over pages too** (`source-`: the E-1608's `source-ao` and `source-dio`, Flat UDP's, the probe kind's), so the same rule holds for them. The probe appends a second `[badges]` line for the Source badge: `[badges] source name= pages= - switch_visible=<0|1> can_target=<0|1> second_page= second_ok=<0|1> open_marked=<0|1|-1> + visible=<0|1> switch_visible=<0|1> can_target=<0|1> second_page= second_ok=<0|1> open_marked=<0|1|-1> closed_unmarked=1`. `pages` is the number of source pages, `switch_visible` is the segmented switch being on screen after the first source page was opened (`1` for the E-1608's two pages, `0` for one page or none), `second_page` is what `slideover_page()` read after the switch selected the second page - (`second_ok=1` when it is that page), `can_target=0` with `pages=0` is the UDPS badge being a read-out and - `open_marked=-1` is that no-page case (there is nothing to open). + (`second_ok=1` when it is that page), `visible=0` with `pages=0` is the UDPS badge being **hidden** (a kind with no source page + has nothing for it to open; `can_target=0` goes with it) and `open_marked=-1` is that no-page case (there is + nothing to open); `visible=1` with `pages>0`. - `UDPSCOPE_PROBE_SOURCE_KINDS=2000` with `UDPSCOPE_EXIT_AFTER_MS=5000`, run as `--source probe --source-opt rate=500` -- the source-plugin path end to end. The variable's presence also registers the test-only `probe` kind (a synthetic sine with one panel), so it is the only way a run sees that kind; its value is the delay in ms. diff --git a/src/ui_app.hpp b/src/ui_app.hpp index a9322ee..2f2603e 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -160,7 +160,7 @@ struct BadgeBar { GtkWidget* signals_slot = nullptr; ///< left; empty in step 1, step 2 fills it GtkWidget* system_slot = nullptr; ///< right: Trigger, Source Badge trigger; - Badge source; ///< opens the first source page; a read-out when the source has none + Badge source; ///< opens the first source page; hidden when the source has none GtkWidget* signals_row = nullptr; ///< the box inside the scroller: signal badges, then the "+" /// One per shown enabled signal; empty when overflowed. `unique_ptr`: each button's "clicked" handler holds /// a pointer to its `Badge`, which must not move when the vector does. diff --git a/src/ui_badges.cpp b/src/ui_badges.cpp index 485a84b..19ad510 100644 --- a/src/ui_badges.cpp +++ b/src/ui_badges.cpp @@ -173,13 +173,16 @@ void build_badge_bar(App& app) { bb.signals_slot = app.badge_signals; bb.system_slot = app.badge_system; make_badge(app, bb.trigger, bb.system_slot, "Trigger", "trigger"); - // The first source page is where the badge goes; a source with none (UDPS) has nothing to open, so its - // badge is a read-out: not a target, not focusable, but still on screen. + // The first source page is where the badge goes; a source with none (UDPS, Flat UDP, a file) has nothing to + // open, so its badge is hidden (`sync_badge_bar` keeps it in step with the slide-over's source pages) and, for + // as long as it is, neither a target nor focusable. Hidden, not insensitive: a dead "Source" pill that does + // nothing was only noise. The bar's height is fixed, so the row loses width and never height. const std::string first = app.source_panels.empty() ? std::string() : app.source_panels.front().name; make_badge(app, bb.source, bb.system_slot, "Source", first); if (first.empty()) { gtk_widget_set_can_target(bb.source.button, FALSE); gtk_widget_set_focusable(bb.source.button, FALSE); + gtk_widget_set_visible(bb.source.button, FALSE); } // The link state is the status bar's to say; the badge names the source and opens its pages. The empty // state label is hidden, as the "+" badge's is, rather than leaving a gap in the pill. @@ -278,6 +281,14 @@ void sync_badge_bar(App& app) { set_open(sb->button, slideover_page(app) == sb->page); } if (bb.source.button == nullptr) return; + // Shown only while there is a source page to open. The slide-over's page list is the truth, so a page that is + // registered later brings the badge back, and its focus and pick with it. + const bool has_source_pages = !app.slideover.source_pages.empty(); + if ((gtk_widget_get_visible(bb.source.button) != FALSE) != has_source_pages) { + gtk_widget_set_visible(bb.source.button, has_source_pages); + gtk_widget_set_can_target(bb.source.button, has_source_pages); + gtk_widget_set_focusable(bb.source.button, has_source_pages); + } // The source's name; the link state is the status bar's. A file has no kind worth naming. const bool file_mode = app.replaying || !app.model->config().open_waveform.empty(); std::string name = "Source"; @@ -322,7 +333,8 @@ gboolean badges_probe(gpointer user) { bad, gtk_label_get_text(GTK_LABEL(a->badges.trigger.state)), h_closed, h_after, h_closed == h_after && h_closed >= 36 ? 1 : 0, marked_open ? 1 : 0, page_open ? 1 : 0, marked_closed ? 1 : 0); - // The Source badge: its read-out, and the segmented switch of a source with several panels. + // The Source badge: shown only with a source page (`visible=`; hidden, the page-dependent fields read -1 or 0), + // and the segmented switch of a source with several panels. const std::string first = a->badges.source.page; bool switch_visible = false; int second_ok = -1; // -1: there is no second page to select (one source page, or none) @@ -348,11 +360,11 @@ gboolean badges_probe(gpointer user) { } slideover_close(*a); std::fprintf(stderr, - "[badges] source name=%s pages=%zu switch_visible=%d can_target=%d second_page=%s " + "[badges] source name=%s pages=%zu visible=%d switch_visible=%d can_target=%d second_page=%s " "second_ok=%d open_marked=%d closed_unmarked=%d panel_min_w=%d\n", gtk_label_get_text(GTK_LABEL(a->badges.source.name)), a->slideover.source_pages.size(), - switch_visible ? 1 : 0, gtk_widget_get_can_target(a->badges.source.button) ? 1 : 0, + gtk_widget_get_visible(a->badges.source.button) ? 1 : 0, switch_visible ? 1 : 0, gtk_widget_get_can_target(a->badges.source.button) ? 1 : 0, second_page.empty() ? "-" : second_page.c_str(), second_ok, first.empty() ? -1 : marked ? 1 : 0, gtk_widget_has_css_class(a->badges.source.button, "udpscope-badge-open") ? 0 : 1, widest); diff --git a/src/ui_tour.cpp b/src/ui_tour.cpp index d698849..d77a640 100644 --- a/src/ui_tour.cpp +++ b/src/ui_tour.cpp @@ -446,9 +446,9 @@ constexpr TourStep kTourSteps[] = { &App::area_overlay}, {"badges", nullptr, "The badges", "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.", + "calibration and plot; the + opens the full list; Trigger opens its form, and so does Source when " + "the source has controls of its own. 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: "