From c37fbdde216cc3815c3f5ac4ef0f93d5d4c98f20 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 12 Oct 2026 15:50:52 +0200 Subject: [PATCH] ui: the signals panel's frame is a .ui file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/ui/signals-panel.ui holds the panel's shape: the box and its margins, the heading, the hint that says what a signal is for, and the list. The rows stay in code -- one per signal, carrying that signal's switch, colour, calibration and drop targets, rebuilt whenever the stream's signals change -- so the file holds the frame, which is the shape the plan gives this slice. The list's width-only size request moved with it, comment and all: a height floor there made the list occupy 120 px whatever it held, which hid the compact rows' height behind it. Read back by a new dump line, `signals panel: labels=2 list=1`, where a lost id reads as `labels=1` or `list=0` instead of a panel quietly missing its hint; `panels: … row_h=26` still reads the list's geometry and the height a row takes. check_ui is at 10 ok. --- docs/claude/invariants-gtk-and-chrome.md | 7 ++++ docs/claude/probe-readings.md | 11 ++++++ src/ui.cpp | 14 ++++++++ src/ui/signals-panel.ui | 43 ++++++++++++++++++++++++ src/ui/udpscope-ui.gresource.xml | 1 + src/ui_signals.cpp | 34 +++++++------------ 6 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 src/ui/signals-panel.ui diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index d459748..19109e6 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -309,3 +309,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear Related: `gtk_expander_set_child()` asserts that its child has no parent yet, so a block the `.ui` has already put in the panel cannot then be handed to an expander -- the file's `child` property is the right way, and it works. + +- **`--` is not allowed inside an XML comment, and a `.ui` comment is XML.** `` is a parse + error: `xml-stripblanks` fails the build, and `make test-ui` prints `FAIL ` with **no** message under + it, because blueprint-compiler's decompiler aborts on the malformed XML before it can report anything -- + the bare `FAIL` line is that mistake and nothing else. The house style for a dash in prose is the em dash, + which is why no other `.ui` in `src/ui/` contains a double hyphen. It has cost two slices a debugging + round for one character. diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 09ed8b1..4914dd4 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -1179,3 +1179,14 @@ The search walk starts at `gtk_expander_get_child()`, not at the panel: an expan widget-parent chain at all, so walking up from a search button reaches no ancestor of the panel (see `invariants-gtk-and-chrome.md`). `[defaults] trigger:` prints the same panel's seven spins in tree order, so the two lines together read what the file gave and what the code put in it. + +## `signals panel:` + +``` +[udpscope] signals panel: labels=2 list=1 +``` + +The signals panel's frame read from the window: the heading and the hint the `.ui` gives it, and the list +the rows go in. `labels=1` is a `.ui` that lost one of the two, `list=0` one that lost the list -- both of +which otherwise show as a panel quietly missing a piece. `panels: … signals=WxH row_h=26` is the other half: +the list's own geometry and the height a row really takes, which is what the compact-row CSS rule is for. diff --git a/src/ui.cpp b/src/ui.cpp index b56f6a2..938c412 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -5409,6 +5409,20 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit static_cast(model.trigger_latches().load()), static_cast(app.dbg_latches_at_disarm)); std::printf("[udpscope] window control: '%s'\n", app.dbg_window_text.c_str()); + { + // The signals panel's frame, read from the window rather than from the `.ui`: the two labels and the + // list the file gives it. A `.ui` that lost one of them reads here -- `labels=1` or `list=0` -- rather + // than showing as a panel quietly missing its hint. + GtkWidget* panel = app.signal_list != nullptr ? gtk_widget_get_parent(app.signal_list) : nullptr; + int labels = 0; + int lists = 0; + for (GtkWidget* c = panel != nullptr ? gtk_widget_get_first_child(panel) : nullptr; c != nullptr; + c = gtk_widget_get_next_sibling(c)) { + if (GTK_IS_LABEL(c)) ++labels; + if (GTK_IS_LIST_BOX(c)) ++lists; + } + std::printf("[udpscope] signals panel: labels=%d list=%d\n", labels, lists); + } std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d " "(revealed=%d) trigger=%dx%d (revealed=%d) toggles=%d cursors=%d window=%.3fs " "window_cap=%.1fs\n", diff --git a/src/ui/signals-panel.ui b/src/ui/signals-panel.ui new file mode 100644 index 0000000..fd2b38a --- /dev/null +++ b/src/ui/signals-panel.ui @@ -0,0 +1,43 @@ + + + + + + + vertical + 6 + 8 + 8 + 8 + 8 + + + Signals + start + + + + + drag a signal onto a plot + start + dim-label + + + + + none + 260 + + + + diff --git a/src/ui/udpscope-ui.gresource.xml b/src/ui/udpscope-ui.gresource.xml index d551c26..f40c8e0 100644 --- a/src/ui/udpscope-ui.gresource.xml +++ b/src/ui/udpscope-ui.gresource.xml @@ -18,5 +18,6 @@ cursors-panel.ui measures-panel.ui trigger-panel.ui + signals-panel.ui diff --git a/src/ui_signals.cpp b/src/ui_signals.cpp index 4abee39..6a66f79 100644 --- a/src/ui_signals.cpp +++ b/src/ui_signals.cpp @@ -382,28 +382,18 @@ void rebuild_signal_panels(App& app) { } GtkWidget* build_signal_panel(App& app) { - GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); - gtk_widget_set_margin_top(box, 8); - gtk_widget_set_margin_bottom(box, 8); - gtk_widget_set_margin_start(box, 8); - gtk_widget_set_margin_end(box, 8); - - GtkWidget* heading = gtk_label_new("Signals"); - gtk_widget_set_halign(heading, GTK_ALIGN_START); - gtk_box_append(GTK_BOX(box), heading); - - GtkWidget* hint = gtk_label_new("drag a signal onto a plot"); - gtk_widget_set_halign(hint, GTK_ALIGN_START); - gtk_widget_add_css_class(hint, "dim-label"); - gtk_box_append(GTK_BOX(box), hint); - - app.signal_list = gtk_list_box_new(); - gtk_list_box_set_selection_mode(GTK_LIST_BOX(app.signal_list), GTK_SELECTION_NONE); - // Width only. A height floor here made the list occupy 120 px whatever it held, - // which hid the compact rows' height behind it and wasted the space on an empty - // stream; the scroller above it already caps how tall it can grow. - gtk_widget_set_size_request(app.signal_list, 260, -1); - gtk_box_append(GTK_BOX(box), app.signal_list); + // The panel's frame -- the heading, the hint under it, and the list -- is a UI file. The rows are not: + // there is one per signal, carrying that signal's switch, colour, calibration and drop targets, and the + // whole list is rebuilt whenever the stream's signals change. `make test-ui` checks the file's types + // against the GIR; the id below is what keeps the code and the file agreeing on the name. + GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/signals-panel.ui"); + GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "signal_panel")); + app.signal_list = GTK_WIDGET(gtk_builder_get_object(ui, "signal_list")); + if (box == nullptr || app.signal_list == nullptr) { + // A missing id is a mistake in the `.ui`: the panel comes up empty rather than taking the window with + // it, and `[udpscope] signals panel:` reads the difference. + return box != nullptr ? box : gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); + } return box; }