ui: the tabs are icons, and the pills keep their names
Each sidebar page gets its own glyph and no visible text. The Measure tab reuses the ruler
(`udpscope-measure-symbolic`, the icon its deleted toolbar toggle used and the Plot preferences page
still carries); the Signals tab gets a new `udpscope-signals-symbolic` — three rows of a colour chip
and a name, which is what that tab mostly is — rather than a second copy of the trigger glyph, since
one shape meaning two things in one window is worse than a new one. Its bars are all one length: a
set of unequal ones reads as a bar chart, and a chart is not a list.
`AdwViewSwitcherPolicy.narrow` alone does **not** give an icon-only bar. Measured on libadwaita 1.10
by walking the switcher's tree: with `narrow` and a title, each pill holds a `GtkImage` *and* a
`GtkLabel` — 48px of "Measure" inside a 54px pill. So the pages' `title` is **empty** (their `name` is
what identifies them to `select_side_tab()` and to the session file, and both are unchanged) and the
words are re-attached in code: `name_side_tabs()` runs on the first tick — the pills are
`AdwViewSwitcher`'s own and exist only once it is mapped — and gives each pill a **tooltip and an
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 the page icon
that does not resolve leaves a *blank* pill, which is why it is read like the toolbar's icons are.
Two readings, because the claim is two claims:
switcher: policy=narrow pills=2 labels=0 names=2
page icons: measure=udpscope-measure-symbolic(ok) signals=udpscope-signals-symbolic(ok)
`labels` counts *mapped labels with text* — a plain mapped count reads 2 either way, because the
narrow pill keeps an empty label in its layout — and `pills=2` is the guard that makes it mean
something: with nothing mapped the rest of the line is void rather than a pass.
Docs: the icon census (six action icons, eight bundled, and what each of the two new users is), the
switcher bullet in `invariants-gtk-and-chrome.md` with the measurement above, the two new dump lines
in `probe-readings.md`, the re-measured GResource figures in `verification.md` (icons 8613 -> 9842
bytes, help 514660 after this session's help rewrite; the count stays four), and the README recording
that the design spec had the switcher showing titles and neither page an icon.
Verified: `make test-ui` 14 files; `[side-tab-probe] … advanced=4` (tab switching by name still
works with empty titles); `signals panel: … divider=1`; `[math-probe] 3 math channels`; zero
`Gtk-CRITICAL` and no missing icon.
This commit is contained in:
1 parent
f9cea4885b
commit
0b146f1d53
9 files changed
+201
-16
No files matched your search
@@ -2393,8 +2393,12 @@ 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
|
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
|
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**
|
sidebar is a **Measure** tab (the cursors readout and the measurements) and a **Signals**
|
||||||
tab (the trigger and the channels), with the math list folded into the signal list under a
|
tab (the trigger and the channels), **each a pill with its own glyph and no text** — the ruler
|
||||||
`MATH` divider whose `+` creates a channel — a derived channel already *is* a channel in
|
for Measure, a channel list for Signals — 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
|
||||||
|
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
|
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: trigger-off and cursors-off **disable** their pane and leave
|
toggle hides a pane any more: trigger-off and cursors-off **disable** their pane and leave
|
||||||
it in place, and the measurements' and math panes' toolbar toggles are gone with the panes.
|
it in place, and the measurements' and math panes' toolbar toggles are gone with the panes.
|
||||||
|
|||||||
@@ -55,10 +55,14 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a
|
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
|
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,
|
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}-symbolic.svg`
|
measurements and math. `src/icons/scalable/actions/udpscope-{trigger,cursors,measure,events,resize-rows,signals}-symbolic.svg`
|
||||||
are the five action icons. `udpscope-math-symbolic` left with the toolbar toggle that was its last
|
are the six 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 --
|
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. Beside them in
|
an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is
|
||||||
|
*also* the Measure tab's icon. `udpscope-signals-symbolic` was drawn for the Signals tab: three rows of
|
||||||
|
a colour chip and a name, which is what that tab mostly is, and deliberately *not* a second copy of the
|
||||||
|
trigger glyph (one shape with two meanings in one window is worse than a new one). Its bars are all one
|
||||||
|
length because unequal ones read as a bar chart, and a chart is not a list. Beside them in
|
||||||
`src/icons/scalable/apps/` are the two
|
`src/icons/scalable/apps/` are the two
|
||||||
**application** icons, `org.udpscope.app.svg` (128px colour artwork) and
|
**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
|
`org.udpscope.app-symbolic.svg` (16px). The `apps` context is where an icon theme keeps an
|
||||||
@@ -67,7 +71,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
into `$XDG_DATA_HOME/icons/hicolor/scalable/apps` and `.../symbolic/apps` — those installed
|
into `$XDG_DATA_HOME/icons/hicolor/scalable/apps` and `.../symbolic/apps` — those installed
|
||||||
copies are what makes `Icon=org.udpscope.app` in the desktop entry resolve (a desktop shell
|
copies are what makes `Icon=org.udpscope.app` in the desktop entry resolve (a desktop shell
|
||||||
cannot read a GResource compiled into the binary).
|
cannot read a GResource compiled into the binary).
|
||||||
All seven are bundled by `src/icons/udpscope.gresource.xml` (CMake runs
|
All eight are bundled by `src/icons/udpscope.gresource.xml` (CMake runs
|
||||||
`glib-compile-resources`) and reached through **one**
|
`glib-compile-resources`) and reached through **one**
|
||||||
`gtk_icon_theme_add_resource_path()` in `run_ui()`, right after `adw_init()` — not at the top
|
`gtk_icon_theme_add_resource_path()` in `run_ui()`, right after `adw_init()` — not at the top
|
||||||
of `build_toolbar()`, where it used to be: there are two consumers now (the window's own icon,
|
of `build_toolbar()`, where it used to be: there are two consumers now (the window's own icon,
|
||||||
@@ -195,6 +199,16 @@ 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*
|
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
|
current -- a change in what the reading means, not a defect, and no functional code may depend on
|
||||||
those numbers.
|
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.
|
||||||
- **A list box outlives the children of the pane it sits in, and the teardown order is not yours to
|
- **A list box outlives the children of the pane it sits in, and the teardown order is not yours to
|
||||||
choose.** Tearing the window down finalizes the events pane's buttons, then the list box, and the
|
choose.** Tearing the window down finalizes the events pane's buttons, then the list box, and the
|
||||||
list's own `selected-rows-changed` then reaches a handler holding the freed pointers: four
|
list's own `selected-rows-changed` then reaches a handler holding the freed pointers: four
|
||||||
|
|||||||
@@ -357,6 +357,18 @@ 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
|
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
|
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.
|
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
|
||||||
|
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.
|
||||||
|
- `page icons: measure=udpscope-measure-symbolic(ok) signals=udpscope-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.
|
||||||
- `app icons: org.udpscope.app(ok 128x128) org.udpscope.app-symbolic(ok 16x16)` (in the exit dump,
|
- `app icons: org.udpscope.app(ok 128x128) org.udpscope.app-symbolic(ok 16x16)` (in the exit dump,
|
||||||
beside `toolbar icons:`) — the application icon, named and present at the size its own root
|
beside `toolbar icons:`) — the application icon, named and present at the size its own root
|
||||||
`<svg>` element declares. **Both entries must read `ok` at those sizes.** `ok` is
|
`<svg>` element declares. **Both entries must read `ok` at those sizes.** `ok` is
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ 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
|
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**
|
`-Wmissing-field-initializers` on `MathNode::source` and `-Woverlength-strings` on **both**
|
||||||
generated GResource `.c` files — the help pages' (**514532** bytes) and the icons', whose blob is
|
generated GResource `.c` files — the help pages' (**514660** bytes) and the icons', whose blob is
|
||||||
**8613** against C99's 4095-byte string-literal limit with the five action icons and the two app icons
|
**9842** against C99's 4095-byte string-literal limit with the six action icons and the two app icons
|
||||||
that remain (a note about a generated file, which GCC itself supports) -- re-measured when
|
that remain (a note about a generated file, which GCC itself supports) -- re-measured when
|
||||||
`udpscope-math-symbolic` was deleted, which did not move the count; re-measure by touching the two
|
the tab work changed the set (that icon left, `udpscope-signals-symbolic` arrived, and this file's
|
||||||
`*.gresource.xml` files and rebuilding. The fifth warning that used to appear here was an unused
|
help page was rewritten), which did not move the count; re-measure by touching each `*.gresource.xml`
|
||||||
|
under `src/` and rebuilding. The fifth warning that used to appear here was an unused
|
||||||
`output_built` in `math_probe()`; it was **deleted** as dead code — redundant with the
|
`output_built` in `math_probe()`; it was **deleted** as dead code — redundant with the
|
||||||
`output_after_add` already printed on the same line — and that deletion is what dropped the baseline
|
`output_after_add` already printed on the same line — and that deletion is what dropped the baseline
|
||||||
from five to four. Nothing started reading it. And an exit-dump run emits **no** `Gtk-CRITICAL` at
|
from five to four. Nothing started reading it. And an exit-dump run emits **no** `Gtk-CRITICAL` at
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- SPDX-License-Identifier: MIT -->
|
||||||
|
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||||
|
<!-- The Signals tab's own glyph: the channel list, drawn as a list. Three rows of a
|
||||||
|
colour chip and a name, the shape the signal panel's own rows have — which is
|
||||||
|
what the tab mostly *is*, and which is why it is not the trigger icon: the
|
||||||
|
toolbar's trigger button already carries that one, and one glyph with two
|
||||||
|
meanings in one window is worse than a new glyph. The bars are all one length
|
||||||
|
on purpose: unequal ones read as a bar chart, and a chart is not a list. Every
|
||||||
|
coordinate is a whole pixel of the 16px cell, every subpath is a filled `path`
|
||||||
|
wound the same way, and the 2px bars match the house stroke weight. -->
|
||||||
|
<svg height="16px" width="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="#2e3436" d="M1 1 H4 V4 H1 Z
|
||||||
|
M6 2 H14 V4 H6 Z
|
||||||
|
M1 6 H4 V9 H1 Z
|
||||||
|
M6 7 H14 V9 H6 Z
|
||||||
|
M1 11 H4 V14 H1 Z
|
||||||
|
M6 12 H14 V14 H6 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -20,6 +20,7 @@
|
|||||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-trigger-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-trigger-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-cursors-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-cursors-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-measure-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-measure-symbolic.svg</file>
|
||||||
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-signals-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-events-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-events-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-resize-rows-symbolic.svg</file>
|
<file preprocess="xml-stripblanks">scalable/actions/udpscope-resize-rows-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">scalable/apps/org.udpscope.app.svg</file>
|
<file preprocess="xml-stripblanks">scalable/apps/org.udpscope.app.svg</file>
|
||||||
|
|||||||
+109
-2
@@ -2865,6 +2865,48 @@ 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
|
/// 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
|
/// 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.
|
/// 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 char* name) {
|
||||||
|
if (name != nullptr && std::strcmp(name, "measure") == 0) return "Measure";
|
||||||
|
if (name != nullptr && std::strcmp(name, "signals") == 0) return "Signals";
|
||||||
|
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(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<void(GtkWidget*)> 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);
|
||||||
|
app.side_tabs_named = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void note_panel_controls(App& app) {
|
void note_panel_controls(App& app) {
|
||||||
if (!app.dbg_panel_controls.empty()) return;
|
if (!app.dbg_panel_controls.empty()) return;
|
||||||
const auto in_bar = [](GtkWidget* w) {
|
const auto in_bar = [](GtkWidget* w) {
|
||||||
@@ -2941,6 +2983,62 @@ void note_panel_controls(App& app) {
|
|||||||
" " +
|
" " +
|
||||||
icon_report(theme, "org.udpscope.app-symbolic",
|
icon_report(theme, "org.udpscope.app-symbolic",
|
||||||
"/org/udpscope/icons/scalable/apps/org.udpscope.app-symbolic.svg");
|
"/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.
|
||||||
|
int pills = 0;
|
||||||
|
int drawn_labels = 0;
|
||||||
|
const std::function<void(GtkWidget*)> 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.
|
||||||
|
if (GTK_IS_LABEL(w) && gtk_widget_get_mapped(w) && gtk_label_get_text(GTK_LABEL(w))[0] != '\0') {
|
||||||
|
++drawn_labels;
|
||||||
|
}
|
||||||
|
for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr;
|
||||||
|
c = gtk_widget_get_next_sibling(c)) {
|
||||||
|
count_drawn(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
if (app.side_switcher != nullptr) {
|
||||||
|
g_object_get(G_OBJECT(app.side_switcher), "policy", &app.dbg_switcher_policy, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
||||||
@@ -3128,6 +3226,11 @@ void update_capture_row(App& app) {
|
|||||||
gboolean tick(gpointer user) {
|
gboolean tick(gpointer user) {
|
||||||
App* app = static_cast<App*>(user);
|
App* app = static_cast<App*>(user);
|
||||||
if (app->window == nullptr) return G_SOURCE_REMOVE;
|
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);
|
note_panel_controls(*app);
|
||||||
ScopeModel& model = *app->model;
|
ScopeModel& model = *app->model;
|
||||||
const gint64 tick_start_us = g_get_monotonic_time();
|
const gint64 tick_start_us = g_get_monotonic_time();
|
||||||
@@ -5563,14 +5666,17 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
inside(app.events_list, app.events_revealer));
|
inside(app.events_list, app.events_revealer));
|
||||||
}
|
}
|
||||||
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
|
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
|
||||||
"(page_current=%d) trigger=%dx%d (sensitive=%d) switcher=%d cursors=%d window=%.3fs "
|
"(page_current=%d) trigger=%dx%d (sensitive=%d) switcher=%d "
|
||||||
"window_cap=%.1fs\n",
|
"cursors=%d window=%.3fs window_cap=%.1fs\n",
|
||||||
app.dbg_side_w, app.dbg_side_h, app.dbg_side_column_after_hide, app.dbg_signal_w,
|
app.dbg_side_w, app.dbg_side_h, app.dbg_side_column_after_hide, app.dbg_signal_w,
|
||||||
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_page_current,
|
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_page_current,
|
||||||
app.dbg_trigger_w, app.dbg_trigger_h,
|
app.dbg_trigger_w, app.dbg_trigger_h,
|
||||||
app.dbg_trigger_sensitive, app.dbg_switcher_visible,
|
app.dbg_trigger_sensitive, app.dbg_switcher_visible,
|
||||||
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
||||||
app.window_limit_s);
|
app.window_limit_s);
|
||||||
|
std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d names=%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);
|
||||||
std::printf("[udpscope] mode with trigger on: %s\n", app.dbg_mode_triggered.c_str());
|
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 "
|
std::printf("[udpscope] trigger marker x=%.3f, view x_max=%.3f; model trigger_time=%.3f "
|
||||||
"valid=%d\n",
|
"valid=%d\n",
|
||||||
@@ -5692,6 +5798,7 @@ 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).
|
// 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] 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] 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());
|
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
|
// 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
|
// this is where their values are read, so a wrong threshold or a window that moved under a
|
||||||
|
|||||||
+19
-4
@@ -52,14 +52,25 @@
|
|||||||
heights, so one shared scroller around the stack would leave a tab switch scrolled
|
heights, so one shared scroller around the stack would leave a tab switch scrolled
|
||||||
to somewhere the new page never asked for. The switcher is the sibling above, not a
|
to somewhere the new page never asked for. The switcher is the sibling above, not a
|
||||||
child of either scroller: the one control that chooses what you are looking at must
|
child of either scroller: the one control that chooses what you are looking at must
|
||||||
not be the first thing to scroll away. -->
|
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 its own glyph: `udpscope-measure-symbolic` is the ruler the
|
||||||
|
measurements used as a toggle, and `udpscope-signals-symbolic` is the channel list,
|
||||||
|
drawn for this tab rather than reusing the toolbar's trigger icon — one glyph
|
||||||
|
meaning two things in one window is worse than a new one. -->
|
||||||
<object class="GtkBox" id="side_column">
|
<object class="GtkBox" id="side_column">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="width-request">300</property>
|
<property name="width-request">300</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwViewSwitcher" id="side_switcher">
|
<object class="AdwViewSwitcher" id="side_switcher">
|
||||||
<property name="stack">side_stack</property>
|
<property name="stack">side_stack</property>
|
||||||
<property name="policy">wide</property>
|
<property name="policy">narrow</property>
|
||||||
<property name="margin-top">4</property>
|
<property name="margin-top">4</property>
|
||||||
<property name="margin-bottom">4</property>
|
<property name="margin-bottom">4</property>
|
||||||
<property name="margin-start">6</property>
|
<property name="margin-start">6</property>
|
||||||
@@ -72,7 +83,9 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="AdwViewStackPage">
|
<object class="AdwViewStackPage">
|
||||||
<property name="name">measure</property>
|
<property name="name">measure</property>
|
||||||
<property name="title" translatable="yes">Measure</property>
|
<property name="title"></property>
|
||||||
|
<!-- The ruler: what the pane measures the waveform with. -->
|
||||||
|
<property name="icon-name">udpscope-measure-symbolic</property>
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="hscrollbar-policy">never</property>
|
||||||
@@ -89,7 +102,9 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="AdwViewStackPage">
|
<object class="AdwViewStackPage">
|
||||||
<property name="name">signals</property>
|
<property name="name">signals</property>
|
||||||
<property name="title" translatable="yes">Signals</property>
|
<property name="title"></property>
|
||||||
|
<!-- The channel list this page mostly is. -->
|
||||||
|
<property name="icon-name">udpscope-signals-symbolic</property>
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="hscrollbar-policy">never</property>
|
||||||
|
|||||||
@@ -210,6 +210,18 @@ struct App {
|
|||||||
/// works, and every other line of the dump still passes.
|
/// works, and every other line of the dump still passes.
|
||||||
std::string dbg_toolbar_icons;
|
std::string dbg_toolbar_icons;
|
||||||
std::string dbg_app_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.
|
||||||
|
int dbg_switcher_pills = -1;
|
||||||
|
int dbg_switcher_labels = -1;
|
||||||
|
int dbg_switcher_names = -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
|
/// 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.
|
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
||||||
GtkWidget* toolbar = nullptr;
|
GtkWidget* toolbar = nullptr;
|
||||||
|
|||||||
Reference in new issue
Block a user