diff --git a/CLAUDE.md b/CLAUDE.md index d93414c..9d82a14 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,4 +61,4 @@ The detail lives in `docs/claude/`. Read the files that touch the area you are e | [invariants-measurements.md](docs/claude/invariants-measurements.md) | Why the pane is refreshed on a cadence, why neither pass may read its window raw, how a crossing is counted with hysteresis, and which mean over a split is which. | | [invariants-math-channels.md](docs/claude/invariants-math-channels.md) | A math channel *is* a channel; the engine runs on the producer thread, so a regression there costs packets and not frames; the node editor's own rules. | | [invariants-session-and-export.md](docs/claude/invariants-session-and-export.md) | What a session file persists and in what order, when a save is refused, and when an export is collected. | -| [invariants-gtk-and-chrome.md](docs/claude/invariants-gtk-and-chrome.md) | The GTK signal-signature and dialog-lifetime traps this app has already fallen into, the status bar's two tiers, the six bundled icons and their authoring rules, and how the Mallard manual is opened. | +| [invariants-gtk-and-chrome.md](docs/claude/invariants-gtk-and-chrome.md) | The GTK signal-signature and dialog-lifetime traps this app has already fallen into, the status bar's two tiers, the bundled icons and their authoring rules, and how the Mallard manual is opened. | diff --git a/README.md b/README.md index 48fa69d..a5af09c 100644 --- a/README.md +++ b/README.md @@ -2886,11 +2886,9 @@ now — they are listed so they are not mistaken for anything else: 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 pill with its own glyph and no text** — the - cursors and the ruler for Measure, a sine crossing its trigger level and the channel list - for Signals (the same picture the toolbar's trigger button carries), drawn as a - 32x16 pair so both motifs are legible at a glance — with the words kept as each pill's tooltip and - accessible name. Both panes also have a **locked** mode, which is what a returning user gets: the + 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 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 @@ -2901,9 +2899,7 @@ now — they are listed so they are not mistaken for anything else: 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 - design spec had the switcher showing *titles* and neither page an - `icon-name`; `narrow` alone does not do it (libadwaita still draws a label beside the icon, - measured), so the pages carry an empty title and the pills are named in code. With the math + switcher's policy is `wide` and each page has a title. 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. A tab is one-of-*N*, so no toggle hides a pane any more: cursors-off **disables** its readout and leaves it in place, and diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index 552231b..9d24b0d 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -51,38 +51,20 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear how the first version of that line earned them. What stays in the menu is what acts on the *data* (Fit, Export, Configurations) and the one editor that is not a pane (the layout editor): the signal list is a *tab* of the sidebar, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text. -- **Seven of this app's icons are its own, and they are a `GResource` compiled into the +- **Five of this app's icons are its own, and they are a `GResource` compiled into the binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a measurement ruler, and the stock names those buttons used to carry said something else entirely: a *record dot* for the trigger and three different **magnifiers** for cursors, - measurements and math. `src/icons/scalable/actions/udpscope-{trigger,cursors,measure,events,resize-rows,tab-measure,tab-signals}-symbolic.svg` - are the seven action icons. `udpscope-math-symbolic` left with the toolbar toggle that was its last + measurements and math. `src/icons/scalable/actions/udpscope-{trigger,cursors,measure,events,resize-rows}-symbolic.svg` + are the five action icons. `udpscope-math-symbolic` left with the toolbar toggle that was its last user; `udpscope-measure-symbolic` **stayed**, because the Preferences window's Plot page carries it -- an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is - the Preferences window's icon, not the tab's. + the Preferences window's icon. - The two **tabs** got glyphs of their own, and each is a *pair* rather than one of the icons above. - `udpscope-tab-measure-symbolic` is two cursors beside the ruler they read against — each a cap over two - bold dashes, beside a ruler of two bands with three marks between them and **no end caps** (a closed - frame reads as a rectangle, and costs a fifth of the ink on two lines that say nothing) — and - `udpscope-tab-signals-symbolic` is a sine crossing its dashed level, beside two bold channel rows. They - are not reuses, because a glyph the toolbar already carries means two things the moment a tab shows it - too; instead the *trigger's* own icon (`udpscope-trigger-symbolic`) was redrawn to the Signals pill's - picture, so the pill and the button that turns the trigger on are recognisably about the same thing. - The wave is a **full period and not a hump**: a crest and a trough are what make it read as a wave - rather than as a hill, and the level's dashes survive between the crossings. (A hump was tried and - reverted — it named the "above the level" half of a trigger and lost the crossings, which is the half - that matters.) - Both are **32x16** composites drawn **bold** — one thick band, two thick dashes, few features — because - at this size a thin wave or a 1px tick reads as *texture* rather than as a shape, which is what "not - clear" looks like. Wider than tall on purpose: a 16px square cell carries one motif, not a pair. And - **GDK paints an icon into a square box whatever the document's aspect** (measured: - `gtk_icon_theme_lookup_icon()` at 32 reports `32x32`, and librsvg fits it to `32x16`), so the pair is - letterboxed rather than stretched — and the pill's image is given `pixel_size` 32 in `name_side_tabs()`, - which draws the artwork at the size it is written, so its bars stay crisp. Every glyph here is - **inset two pixels**, because art flush with the cell edge is what a button's padding or a crop takes a - pixel off, and a fractional scale is what would blur it, which is why nothing is drawn at 24 or 48. The - two tab glyphs measure 29% and 30% ink, inside the range the icons above them set. + The sidebar's **tabs have no icons any more**: two glyphs (`udpscope-tab-measure-symbolic`, + `udpscope-tab-signals-symbolic`, each a 32x16 pair) were drawn for them and judged not understandable + enough, so the tabs are text and both SVGs were deleted. The *trigger's* own icon was redrawn to the + Signals glyph's picture at that time and kept it. - **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has none.** `src/shortcuts.cpp`'s strings are handed to `AdwShortcutsItem`, which parses them with `gtk_accelerator_parse()` — so they are `"space"`, `"e"`, `"slash"`, @@ -264,16 +246,13 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear allocation read in the sidebar is a debug capture, and each reads 0 on the page that is *not* current -- a change in what the reading means, not a defect, and no functional code may depend on those numbers. -- **`AdwViewSwitcherPolicy.narrow` still draws a label; an *empty title* is what makes a pill an icon.** - Measured on libadwaita 1.10: with `narrow` and a title, the pill holds a `GtkImage` **and** a - `GtkLabel` — 48px of "Measure" inside a 54px pill — so the policy alone does not give an icon-only tab - bar. The sidebar's pages therefore have **no title** (their `name` is what identifies them to - `select_side_tab()` and the session file) and the pills are *named in code* instead: the first tick - calls `name_side_tabs()`, which walks the switcher's children — the pills are there in page order, and - `AdwViewSwitcherButton` is not a public type, so there is no type check to make — and sets each pill's - **tooltip and accessible name** from the page's name, then hides the empty label that would otherwise - leave the icon off-centre. An icon-only pill is still a named control for a screen reader, and - `switcher: … pills=2 labels=0 names=2` is the reading for all three facts at once. +- **The sidebar's tabs are text: `AdwViewSwitcherPolicy.wide`, a title on every page, no icon.** The + built-in pages get their titles in the `.ui` and a source kind's pages from `SourcePanel::title` + (`adw_view_stack_add_titled`); the page's *name* still identifies it to `select_side_tab()` and the + session file. An icon-only version existed (policy `narrow`, empty titles, the pills named in code by + `name_side_tabs()`, because `narrow` still draws a label beside an icon); it was reversed because the + glyphs were not understandable. A page added with an empty title is a blank pill, and + `switcher: … pills=N labels=N pages=N` reads it as `labels` below `pills`. - **An *overlaid* sidebar covers the plot, so a drag out of it has nowhere to land.** `collapsed` in an `AdwOverlaySplitView` means the sidebar sits *on* the plot rather than beside it: the pointer is over the list, never over a drop target, and a signal can be dragged but not dropped. `row_drag_sidebar()` hides diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 915f018..525bbeb 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -479,27 +479,14 @@ Two lines of the self-check output are assertions in disguise: substitutes a missing-image placeholder, the button still works, and every other line of the dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this is the only check that it names something that exists. -- `switcher: policy=narrow pills=2 labels=0 names=2 icon=32x32 size=308x36` (in the exit dump, beside - `panels:`) — the two - sidebar tabs as the switcher actually draws them. **`pills=2 labels=0`** is the claim: two pills are on - screen and **no text is drawn** (`labels` counts *mapped labels with text*, because `narrow` keeps an - empty label in the pill's layout and a plain mapped count would read 2 either way). `names=2` is the - other half — both pills carry a tooltip and an accessible name set by `name_side_tabs()`, so an - icon-only pill is not an anonymous button — and `pills=0` means nothing is mapped, which makes the - rest of the line meaningless rather than a pass. `policy=narrow` is read rather than assumed. `icon=` - is the box the pills' `GtkImage` is *rendered* in, which proves the 32px override stuck: the tab - glyphs are 32x16 documents, and GDK paints an icon into a square box whatever the document's aspect, - so the artwork is drawn 32x16 at 1:1 inside that 32x32 box — letterboxed, not stretched, which is what - keeps its 2px bars crisp. It reads `0x0` on the first tick, where nothing has been allocated yet, and - is therefore read by the exit probe with the rest of the sidebar's geometry. `size=` is the bar's own - size, and it is the answer to "what did the wider glyphs cost": `36` is what it was with 16px icons, - because the pill's fixed height takes a 32px box without growing — the glyph gets twice the width for - no vertical room at all. -- `page icons: measure=udpscope-tab-measure-symbolic(ok) signals=udpscope-tab-signals-symbolic(ok)` (in the exit - dump, beside `toolbar icons:`) — each tab's own glyph and `gtk_icon_theme_has_icon()`'s verdict, read - on the first tick for the same reason the toolbar's are. **Both must read `(ok)`**: the page titles are - empty on purpose, so an icon that does not resolve leaves a *blank* pill, and a blank pill looks - exactly like a page with nothing in it. +- `switcher: policy=wide pills=4 labels=4 pages=4 size=308x34` (in the exit dump, beside `panels:`) — the + sidebar tabs as the switcher actually draws them, and they are **words**: `labels` counts *mapped labels + with text*, so `pills=labels=pages` says every page has a visible title. `pages` is read from the stack + (2 for UDPS, 3 for the probe source, 4 for the E-1608 with its Analog out and Digital I/O tabs). A + `labels` below `pills` is a page added with an empty title; `pills=0` means nothing is mapped, which + makes the rest of the line meaningless rather than a pass. `policy=wide` is read rather than assumed. + `size=` is the bar's own size: a text tab costs about 34 px of height, and a width of 308 shows the + four tabs fit the 320 px column. - `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=5 step=0 auto=1 requested=0` (in the exit dump, beside `switcher:`) — the help affordance layer as the last tick found it. **`hints=` must equal the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 153e2ce..45ff23f 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -17,8 +17,8 @@ Two baselines every run is read against. The UDPS runs must be **unchanged by th a UDPS self-check, number-normalised (`sed 's/[0-9.]\+/N/g'`), diffs empty against one taken before it. A clean build emits **three** warnings and no more: two `-Wmissing-field-initializers` on `MathNode::source` and one `-Woverlength-strings` on the icons' -generated GResource `.c`, whose blob is **12352** against C99's 4095-byte string-literal limit with -the seven action icons and the two app icons that remain (a note about a generated file, which GCC +generated GResource `.c`, whose blob is **9248** against C99's 4095-byte string-literal limit with +the five action icons and the two app icons that remain (a note about a generated file, which GCC itself supports). The *fourth* was the help pages' generated `.c` (**516044** bytes); the manual is now a Mallard set installed for Yelp rather than a GResource compiled into the binary, so that bundle — and its warning — is gone, and the baseline moved from four to **three**. Re-measure by diff --git a/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg b/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg deleted file mode 100644 index c59cc50..0000000 --- a/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg b/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg deleted file mode 100644 index ad68985..0000000 --- a/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/src/icons/udpscope.gresource.xml b/src/icons/udpscope.gresource.xml index 1370c9f..b8e6e77 100644 --- a/src/icons/udpscope.gresource.xml +++ b/src/icons/udpscope.gresource.xml @@ -20,8 +20,6 @@ scalable/actions/udpscope-trigger-symbolic.svg scalable/actions/udpscope-cursors-symbolic.svg scalable/actions/udpscope-measure-symbolic.svg - scalable/actions/udpscope-tab-measure-symbolic.svg - scalable/actions/udpscope-tab-signals-symbolic.svg scalable/actions/udpscope-events-symbolic.svg scalable/actions/udpscope-resize-rows-symbolic.svg scalable/apps/org.udpscope.app.svg diff --git a/src/source/e1608/e1608_panels.cpp b/src/source/e1608/e1608_panels.cpp index b9997d6..ecae53e 100644 --- a/src/source/e1608/e1608_panels.cpp +++ b/src/source/e1608/e1608_panels.cpp @@ -332,12 +332,10 @@ std::vector make_e1608_panels(DataSource& source) { SourcePanel ao; ao.id = "ao"; ao.title = "Analog out"; - ao.icon = "audio-volume-high-symbolic"; ao.widget = build_ao(src); SourcePanel dio; dio.id = "dio"; dio.title = "Digital I/O"; - dio.icon = "input-dialpad-symbolic"; dio.widget = build_dio(src); return {ao, dio}; } diff --git a/src/source/source_kind.hpp b/src/source/source_kind.hpp index fac37bc..fe065b4 100644 --- a/src/source/source_kind.hpp +++ b/src/source/source_kind.hpp @@ -38,7 +38,6 @@ struct ConfigForm { struct SourcePanel { std::string id; ///< stack page name std::string title; - std::string icon; ///< icon name for the tab, may be empty GtkWidget* widget = nullptr; }; diff --git a/src/ui.cpp b/src/ui.cpp index a8f00ad..347f373 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2975,64 +2975,6 @@ std::string icon_report(GtkIconTheme* theme, const char* name, const char* resou /// and Reset is inside the cursors pane. Recorded on the first tick -- the widgets exist and the /// window is alive, neither of which is true when the dump runs -- so the dump can print a fact /// instead of asking GTK about a destroyed widget. -/// The words the pills do not draw. The pages' `title` is empty on purpose (see the `.ui`), so the -/// human name of a tab lives here and is put on the pill — its tooltip and its accessible name — by -/// `name_side_tabs()`. That keeps an icon-only pill a *named* control for a screen reader. -static const char* side_tab_title(const App& app, const char* name) { - if (name != nullptr && std::strcmp(name, "measure") == 0) return "Measure"; - if (name != nullptr && std::strcmp(name, "signals") == 0) return "Signals"; - if (name != nullptr) { - for (const SourcePanelPage& p : app.source_panels) { - if (p.name == name) return p.title.c_str(); - } - } - return ""; -} - -/// Names the switcher's pills and takes their empty labels out of the layout: an empty `GtkLabel` -/// still asks for its own width, which leaves the icon off-centre in a pill meant to be one glyph. -/// Called from the first tick, because the pills are `AdwViewSwitcher`'s own and exist only once the -/// widget has been mapped. -void name_side_tabs(App& app) { - if (app.side_tabs_named || app.side_switcher == nullptr) return; - GListModel* pages = app.side_stack != nullptr - ? G_LIST_MODEL(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack))) - : nullptr; - // The switcher's children *are* its pills, in page order, and `AdwViewSwitcherButton` is not a - // public type (there is no `ADW_IS_VIEW_SWITCHER_BUTTON`), so the two lists are walked together by - // index rather than checked by type. - GtkWidget* pill = gtk_widget_get_first_child(app.side_switcher); - for (guint i = 0; pages != nullptr && pill != nullptr && i < g_list_model_get_n_items(pages); - pill = gtk_widget_get_next_sibling(pill), ++i) { - AdwViewStackPage* page = ADW_VIEW_STACK_PAGE(g_list_model_get_item(pages, i)); - const char* title = side_tab_title(app, adw_view_stack_page_get_name(page)); - g_object_unref(page); - if (title[0] == '\0') continue; - gtk_widget_set_tooltip_text(pill, title); - gtk_accessible_update_property(GTK_ACCESSIBLE(pill), GTK_ACCESSIBLE_PROPERTY_LABEL, title, -1); - const std::function hide_labels = [&](GtkWidget* w) { - if (GTK_IS_LABEL(w)) gtk_widget_set_visible(w, FALSE); - for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr; - c = gtk_widget_get_next_sibling(c)) { - hide_labels(c); - } - }; - hide_labels(pill); - // The tabs' glyphs are 32x16 documents, and the pill has the width for them: drawn at 32 they are - // 1:1, so both motifs sit side by side at the size they were authored at and the 2px bars stay - // crisp. A 16px box would scale the pair down to a smear, and a fractional scale would blur it. - const std::function size_images = [&](GtkWidget* w) { - if (GTK_IS_IMAGE(w)) gtk_image_set_pixel_size(GTK_IMAGE(w), 32); - for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr; - c = gtk_widget_get_next_sibling(c)) { - size_images(c); - } - }; - size_images(pill); - app.side_tabs_named = true; - } -} - void note_panel_controls(App& app) { if (!app.dbg_panel_controls.empty()) return; const auto in_bar = [](GtkWidget* w) { @@ -3110,39 +3052,17 @@ void note_panel_controls(App& app) { icon_report(theme, "org.udpscope.app-symbolic", "/org/udpscope/icons/scalable/apps/org.udpscope.app-symbolic.svg"); - // The two sidebar pages' own icons, named and resolved the same way, and what the switcher is - // drawing for them. The pills are `AdwViewSwitcher`'s own buttons, built when the stack is mapped, - // so this reads on the first tick: `pills` is the guard (0 pills would make a label count of 0 - // meaningless) and `labels` must be 0, because the switcher's policy is `narrow` -- the pages are - // icons, not words, in a 300px column. - // Walked through `get_pages()`, not the widget tree: an `AdwViewStackPage` is a plain `GObject` - // (only its `child` is a widget), so `gtk_widget_get_first_child()` on the stack yields the pages' - // scrolled windows and this line read empty the first time it was written. - GListModel* pages = app.side_stack != nullptr - ? G_LIST_MODEL(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack))) - : nullptr; - for (guint i = 0; pages != nullptr && i < g_list_model_get_n_items(pages); ++i) { - AdwViewStackPage* page = ADW_VIEW_STACK_PAGE(g_list_model_get_item(pages, i)); - const char* page_name = adw_view_stack_page_get_name(page); - const char* icon = adw_view_stack_page_get_icon_name(page); - app.dbg_page_icons += std::string(app.dbg_page_icons.empty() ? "" : " ") + - (page_name != nullptr ? page_name : "?") + "=" + - (icon != nullptr ? icon : "-") + - (icon != nullptr && gtk_icon_theme_has_icon(theme, icon) ? "(ok)" - : "(MISSING)"); - g_object_unref(page); - } - // `mapped`, not `visible`: visibility is per widget, so a label whose *parent* is hidden still - // reads visible, and in the narrow policy that is exactly the case. `mapped` requires every - // ancestor, which is what "on screen" means -- and it is why the pill count is read the same way: - // if nothing is mapped the pill count reads 0 and the label count means nothing, rather than - // passing vacuously. + // What the switcher is drawing: the sidebar's tabs are words (`wide` policy, a title per page, no + // icon). The pills are `AdwViewSwitcher`'s own buttons, built when the stack is mapped, so this + // reads on the first tick. `mapped`, not `visible`: visibility is per widget, so a label whose + // *parent* is hidden still reads visible; `mapped` requires every ancestor, which is what "on + // screen" means -- and it is why the pill count is read the same way: if nothing is mapped the + // pill count reads 0 and the label count means nothing, rather than passing vacuously. int pills = 0; int drawn_labels = 0; const std::function count_drawn = [&](GtkWidget* w) { if (GTK_IS_TOGGLE_BUTTON(w) && gtk_widget_get_mapped(w)) ++pills; - // A *mapped* label with text: libadwaita's narrow pill keeps an empty label in the layout, so - // "mapped" alone would count the empty ones and read 2 whether the pills were icons or words. + // A *mapped* label with text: an empty label would be counted by "mapped" alone. if (GTK_IS_LABEL(w) && gtk_widget_get_mapped(w) && gtk_label_get_text(GTK_LABEL(w))[0] != '\0') { ++drawn_labels; } @@ -3154,14 +3074,12 @@ void note_panel_controls(App& app) { if (app.side_switcher != nullptr) count_drawn(app.side_switcher); app.dbg_switcher_pills = pills; app.dbg_switcher_labels = drawn_labels; - int named_pills = 0; - for (GtkWidget* w = app.side_switcher != nullptr ? gtk_widget_get_first_child(app.side_switcher) - : nullptr; - w != nullptr; w = gtk_widget_get_next_sibling(w)) { - const char* tip = gtk_widget_get_tooltip_text(w); - if (tip != nullptr && tip[0] != '\0') ++named_pills; - } - app.dbg_switcher_names = named_pills; + // Walked through `get_pages()`, not the widget tree: an `AdwViewStackPage` is a plain `GObject`. + app.dbg_switcher_pages = + app.side_stack != nullptr + ? static_cast(g_list_model_get_n_items( + G_LIST_MODEL(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack))))) + : 0; if (app.side_switcher != nullptr) { g_object_get(G_OBJECT(app.side_switcher), "policy", &app.dbg_switcher_policy, nullptr); } @@ -3456,11 +3374,6 @@ void apply_density(App& app, DisplayMode display, bool live_display) { gboolean tick(gpointer user) { App* app = static_cast(user); if (app->window == nullptr) return G_SOURCE_REMOVE; - // Before the reading, and not after it: the pills are `AdwViewSwitcher`'s own widgets and exist - // only once the switcher is mapped, so this is the first frame that can name them -- and their - // page titles are deliberately empty, so a frame that read before this ran would read pills with - // no text and no name either. - name_side_tabs(*app); note_panel_controls(*app); sync_source_panels(*app); // The tour, re-measured every frame: the target's allocation is 0 on the frame it is first shown @@ -5289,8 +5202,8 @@ void on_activate(GtkApplication* application, gpointer user) { g_signal_connect(app->side_stack, "notify::visible-child-name", G_CALLBACK(on_side_tab_changed), app); } - // The source kind's own tabs (a generator, digital I/O). Before the first tick: that is when - // `name_side_tabs()` puts the words on the pills, and it must see these pages too. + // The source kind's own tabs (a generator, digital I/O), added before the first tick so the + // pages-versus-pills reading below counts them too. build_source_panels(*app); // The left side is the events pane *and* the plot, in one row: the pane is a column of its own @@ -6324,12 +6237,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit app.dbg_panel_switch_trigger, app.dbg_toolbar_switch_trigger, app.dbg_panel_switch_cursors, app.dbg_toolbar_switch_cursors, app.dbg_panel_drove_toolbar); - std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d names=%d icon=%dx%d " - "size=%dx%d\n", + std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d pages=%d size=%dx%d\n", app.dbg_switcher_policy == 0 ? "narrow" : (app.dbg_switcher_policy == 1 ? "wide" : "?"), - app.dbg_switcher_pills, app.dbg_switcher_labels, app.dbg_switcher_names, - app.dbg_switcher_icon_w, app.dbg_switcher_icon_h, app.dbg_switcher_w, - app.dbg_switcher_h); + app.dbg_switcher_pills, app.dbg_switcher_labels, app.dbg_switcher_pages, + app.dbg_switcher_w, app.dbg_switcher_h); // The help affordance layer: what the contextual-help toggle left on the surfaces, and where the // guided tour stood when the last capture ran. Both are captured on the tick (`capture_help_state`), // so neither asks a destroyed widget anything. @@ -6463,7 +6374,6 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit // critical (which is how the first version of this line earned four of them). std::printf("[udpscope] panel controls: %s\n", app.dbg_panel_controls.c_str()); std::printf("[udpscope] toolbar icons: %s\n", app.dbg_toolbar_icons.c_str()); - std::printf("[udpscope] page icons: %s\n", app.dbg_page_icons.c_str()); std::printf("[udpscope] app icons: %s\n", app.dbg_app_icons.c_str()); // The measurements pane, with the numbers the engine computed: a probe adds measurements and // this is where their values are read, so a wrong threshold or a window that moved under a diff --git a/src/ui/main-window.ui b/src/ui/main-window.ui index 628ba55..a6bf93f 100644 --- a/src/ui/main-window.ui +++ b/src/ui/main-window.ui @@ -54,25 +54,17 @@ child of either scroller: the one control that chooses what you are looking at must not be the first thing to scroll away. - The policy is `narrow` and the **titles are empty**: `narrow` still draws a label - beside an icon (measured: a 48px "Measure" inside a 54px pill), so an empty title is - what makes a pill an icon. The pages' *names* are what identify them to - `select_side_tab()` and the session file, and the words are re-attached to the pills in - code — tooltip and accessible name — by `name_side_tabs()` on the first tick, so an - icon-only pill is still a named thing rather than an anonymous button. - Each page's icon is a glyph of its own, and each one is a *pair*: the Measure tab is - `udpscope-tab-measure-symbolic` (the cursors and the ruler they read against), and the - Signals tab is `udpscope-tab-signals-symbolic` (the trigger's edge and the channel list - below it). They are drawn for the tabs rather than reusing the icons the toolbar and the - Preferences window already carry: a glyph the toolbar uses for one thing means two things - the moment a tab shows it too. --> + The tabs are words: the policy is `wide` and every page has a title, no icon. (They + were icon-only glyphs once; the glyphs were not understandable enough.) The pages' + *names* are what identify them to `select_side_tab()` and the session file; a source + kind's own tabs are added in code with their `SourcePanel::title`. --> vertical 300 side_stack - narrow + wide 4 4 6 @@ -85,10 +77,7 @@ measure - - - udpscope-tab-measure-symbolic + Measure never @@ -105,9 +94,7 @@ signals - - - udpscope-tab-signals-symbolic + Signals never diff --git a/src/ui_app.hpp b/src/ui_app.hpp index 76497c8..6090e9a 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -440,21 +440,13 @@ struct App { /// works, and every other line of the dump still passes. std::string dbg_toolbar_icons; std::string dbg_app_icons; - /// The two sidebar pages' own icons and whether the theme resolved them, read on the first tick - /// like the two lines above. - std::string dbg_page_icons; - /// What the switcher is: how many pills it holds, and how many labels it is *drawing*. The label - /// count is the one that says the pages are icons rather than words: `narrow` hides them, and the - /// titles stay set because they are the pages' names for a screen reader and for the tooltip. + /// What the switcher is: how many pills it holds, and how many labels it is *drawing*. The tabs + /// are words (`wide` policy, a title per page), so the label count must equal the page count. int dbg_switcher_pills = -1; int dbg_switcher_labels = -1; - int dbg_switcher_names = -1; - /// The pills' icon as it is *rendered*: a 32x16 glyph in a 32px box is drawn 1:1, which is what - /// keeps its 2px bars crisp. - int dbg_switcher_icon_w = -1; - int dbg_switcher_icon_h = -1; - /// The switcher's own size, beside it: the pills carry 32px icon boxes now, so the tab bar's height - /// is what it costs. + /// How many pages the sidebar stack holds: `pills` and `labels` must both equal it. + int dbg_switcher_pages = -1; + /// The switcher's own size, beside it: what the tab bar costs the column. int dbg_switcher_w = -1; int dbg_switcher_h = -1; /// The panes' own switches and the header bar's buttons, read together by the exit probe: the two @@ -473,8 +465,6 @@ struct App { /// thing in the body that *is* disabled with the mode. int dbg_arm_sensitive = -1; int dbg_switcher_policy = -1; - /// Whether `name_side_tabs()` has named the pills: it can only run once the switcher is mapped. - bool side_tabs_named = false; /// The header bar, so the dump can report the order the controls are in: a header bar is a /// *titlebar*, not a child of the window, so walking down from the window does not reach it. GtkWidget* toolbar = nullptr; diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index 1f04995..e9ac7a7 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -104,9 +104,8 @@ gboolean exit_probe_step3(gpointer user) { a->dbg_signal_h = gtk_widget_get_height(a->signal_panel); a->dbg_trigger_w = gtk_widget_get_width(a->trigger_panel); a->dbg_trigger_h = gtk_widget_get_height(a->trigger_panel); - // The switcher's icon as it is *rendered*: a 32x16 glyph in a 32px box draws 1:1, which is what - // keeps its 2px bars crisp rather than blurred by a fractional scale. Read here and not on the - // first tick, where the window has not been allocated yet and every widget still measures 0x0. + // The switcher's size as allocated: read here and not on the first tick, where the window has not + // been allocated yet and every widget still measures 0x0. if (a->side_switcher != nullptr) { a->dbg_switcher_w = gtk_widget_get_width(a->side_switcher); a->dbg_switcher_h = gtk_widget_get_height(a->side_switcher); @@ -180,21 +179,6 @@ gboolean exit_probe_step3(gpointer user) { ? 1 : 0; a->dbg_panel_drove_toolbar = a->dbg_toolbar_switch_cursors; } - for (GtkWidget* pill = a->side_switcher != nullptr ? gtk_widget_get_first_child(a->side_switcher) - : nullptr; - pill != nullptr; pill = gtk_widget_get_next_sibling(pill)) { - const std::function measure_icon = [&](GtkWidget* node) { - if (GTK_IS_IMAGE(node) && gtk_widget_get_mapped(node)) { - a->dbg_switcher_icon_w = std::max(a->dbg_switcher_icon_w, gtk_widget_get_width(node)); - a->dbg_switcher_icon_h = std::max(a->dbg_switcher_icon_h, gtk_widget_get_height(node)); - } - for (GtkWidget* c = gtk_widget_get_first_child(node); c != nullptr; - c = gtk_widget_get_next_sibling(c)) { - measure_icon(c); - } - }; - measure_icon(pill); - } // The height of one signal row, which is the thing "the channel toggle is too tall" // was about. The panel's own height cannot stand in for it: it also covers the // heading, the hint and the box margins. diff --git a/src/ui_source.cpp b/src/ui_source.cpp index c7c7c80..d81b723 100644 --- a/src/ui_source.cpp +++ b/src/ui_source.cpp @@ -27,11 +27,9 @@ void build_source_panels(App& app) { GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroller), p.widget); const std::string name = "source-" + p.id; - // An empty title on purpose, as for the built-in tabs: the pill is an icon, and - // `name_side_tabs()` puts `title` on it as tooltip and accessible name. - adw_view_stack_add_titled_with_icon( - ADW_VIEW_STACK(app.side_stack), scroller, name.c_str(), "", - p.icon.empty() ? "applications-engineering-symbolic" : p.icon.c_str()); + // The tab is its title: the sidebar's pills are words, not glyphs. + adw_view_stack_add_titled(ADW_VIEW_STACK(app.side_stack), scroller, name.c_str(), + p.title.c_str()); app.source_panels.push_back({name, p.title, p.widget}); } // Disconnected until the first Connected event says otherwise.