diff --git a/README.md b/README.md index b25653d..eb61fef 100644 --- a/README.md +++ b/README.md @@ -2394,8 +2394,8 @@ now — they are listed so they are not mistaken for anything else: 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, the trigger and the channel list for Signals — with the words - kept as each pill's tooltip and + cursors and the ruler for Measure, the trigger and the channel list for Signals, 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. 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 diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index 02b8c07..7ee0120 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -65,11 +65,17 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear `udpscope-tab-measure-symbolic` is the cursors and the ruler they read against, and `udpscope-tab-signals-symbolic` is the trigger's edge above the channel list it acts on. They are not reuses because a glyph the toolbar already carries means two things the moment a tab shows it too. Both - are 16px composites with four features each, which is about the limit of this size: the Signals list is - two rows and not three, so the edge and the list get half the cell each, and the bars in it are all one - length because unequal ones read as a bar chart, and a chart is not a list. Their ink fractions (32% and - 36%) sit inside the range the icons above set, which is the check that a composite has not turned into a - blot. Beside them in + are **32x16** composites, one motif per half: the Measure pill is the two cursors beside the ruler, the + Signals pill the trigger beside the channel list. Wider than tall on purpose — a 16px square cell carries + one motif, and squeezing a pair into it is what turns both into a smear — and the pill has the width for + it, in a 300px column whose switcher has two pills in it. **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, not stretched — and the pill's image is given + `pixel_size` 32 in `name_side_tabs()`, which draws the artwork at exactly the size it is written, so its + 2px bars stay crisp. A fractional scale is what blurs them, which is why nothing here is drawn at 24 or + 48. The bars in the Signals list are all one length, because unequal ones read as a bar chart and a chart + is not a list; their ink fractions (23% and 33%) sit inside the range the icons above set, which is the + check that a composite has not turned into a blot. Beside them in `src/icons/scalable/apps/` are the two **application** icons, `org.udpscope.app.svg` (128px colour artwork) and `org.udpscope.app-symbolic.svg` (16px). The `apps` context is where an icon theme keeps an diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index cb4d269..7bfd8a0 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -357,13 +357,22 @@ 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` (in the exit dump, beside `panels:`) — the two +- `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. + 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 diff --git a/docs/claude/verification.md b/docs/claude/verification.md index 5cf2aa4..3fbbea0 100644 --- a/docs/claude/verification.md +++ b/docs/claude/verification.md @@ -16,7 +16,7 @@ xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=8000 UDPSCOPE_ENABLE_ALL_MS=1000 \ Two baselines every run is read against. A clean build emits **four** warnings and no more: two `-Wmissing-field-initializers` on `MathNode::source` and `-Woverlength-strings` on **both** generated GResource `.c` files — the help pages' (**514660** bytes) and the icons', whose blob is -**11309** against C99's 4095-byte string-literal limit with the seven action icons and the two app icons +**11661** 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 itself supports) -- re-measured when the tab work changed the set (that icon left, the two tab glyphs arrived, and this file's help page was rewritten), which did not move the count; re-measure by touching each `*.gresource.xml` diff --git a/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg b/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg index 4a3a303..01bbf85 100644 --- a/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg +++ b/src/icons/scalable/actions/udpscope-tab-measure-symbolic.svg @@ -1,23 +1,26 @@ - - - + + + diff --git a/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg b/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg index ffdcca9..abc84ac 100644 --- a/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg +++ b/src/icons/scalable/actions/udpscope-tab-signals-symbolic.svg @@ -1,20 +1,25 @@ - - - + + + diff --git a/src/ui.cpp b/src/ui.cpp index 223cb17..1e06005 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2903,6 +2903,17 @@ void name_side_tabs(App& app) { } }; 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; } } @@ -5674,9 +5685,12 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit app.dbg_trigger_sensitive, app.dbg_switcher_visible, rows.cursors_visible() ? 1 : 0, rows.time_window(), app.window_limit_s); - std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d names=%d\n", + std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d names=%d icon=%dx%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_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); std::printf("[udpscope] mode with trigger on: %s\n", app.dbg_mode_triggered.c_str()); std::printf("[udpscope] trigger marker x=%.3f, view x_max=%.3f; model trigger_time=%.3f " "valid=%d\n", diff --git a/src/ui_app.hpp b/src/ui_app.hpp index 8c777f0..666caf8 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -219,6 +219,14 @@ struct App { 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. + int dbg_switcher_w = -1; + int dbg_switcher_h = -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; diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index 40784e7..297dcac 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -78,6 +78,28 @@ 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. + 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); + } + 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.