ui: draw the sidebar tabs without the placeholder icon

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 12:11:16 +01:00
1 parent 5bb23e6a0a
commit 0c6e69957d
6 files changed
+75 -8

No files matched your search

+10 -4
View File
@@ -3060,8 +3060,11 @@ void note_panel_controls(App& app) {
// pill count reads 0 and the label count means nothing, rather than passing vacuously.
int pills = 0;
int drawn_labels = 0;
int drawn_images = 0;
const std::function<void(GtkWidget*)> count_drawn = [&](GtkWidget* w) {
if (GTK_IS_TOGGLE_BUTTON(w) && gtk_widget_get_mapped(w)) ++pills;
// A *mapped* image is an icon (or its missing-icon placeholder) on a tab: tabs are words only.
if (GTK_IS_IMAGE(w) && gtk_widget_get_mapped(w)) ++drawn_images;
// 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;
@@ -3074,6 +3077,7 @@ 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;
app.dbg_switcher_images = drawn_images;
// Walked through `get_pages()`, not the widget tree: an `AdwViewStackPage` is a plain `GObject`.
app.dbg_switcher_pages =
app.side_stack != nullptr
@@ -5203,7 +5207,9 @@ void on_activate(GtkApplication* application, gpointer user) {
G_CALLBACK(on_side_tab_changed), app);
}
// 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.
// pages-versus-pills reading below counts them too. The tabs are words, so the switcher's icons
// are hidden first and kept hidden for the pages this call adds.
keep_tabs_icon_free(*app);
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
@@ -6240,10 +6246,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 pages=%d size=%dx%d\n",
std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d images=%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_pages,
app.dbg_switcher_w, app.dbg_switcher_h);
app.dbg_switcher_pills, app.dbg_switcher_labels, app.dbg_switcher_images,
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.
+8
View File
@@ -444,6 +444,9 @@ struct App {
/// 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;
/// How many `GtkImage`s the switcher has *mapped*: a tab is its title and nothing else, so this
/// must be 0 -- an icon, or the missing-icon placeholder GTK draws for a page with no `icon-name`.
int dbg_switcher_images = -1;
/// 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.
@@ -1560,6 +1563,11 @@ void config_dialog_refresh(ConfigDialog& d);
void open_configurations(App& app);
void save_layout_now(App& app);
/// The sidebar's tabs are words: hide the icon every `AdwViewSwitcherButton` draws (the
/// missing-icon placeholder, for a page with a title and no `icon-name`), for the buttons that exist
/// and for any page added later. Call once, before `build_source_panels()`.
void keep_tabs_icon_free(App& app);
/// Capability panels (`src/ui_source.cpp`). `build_source_panels` adds the source kind's panels as
/// pages of `side_stack`, once, before the first tick. `apply_source_panels` sets every panel's
/// sensitivity from `connected` -- disabled, never hidden: the tab switcher owns visibility.
+42
View File
@@ -11,6 +11,48 @@
namespace udpscope {
namespace ui_impl {
namespace {
/// Hide every `GtkImage` inside `w`, and keep it hidden. `AdwViewSwitcherButton` always builds an
/// icon -- in both of its layouts -- and a page with no `icon-name` gets `image-missing`, which is
/// the placeholder the sidebar's text tabs used to draw. There is no property or policy that turns
/// the icon off, so the image is hidden (a hidden child takes no space: the label gets the pill) and
/// a `notify::visible` handler puts it back to hidden if the button ever shows it again.
void hide_tab_icons(GtkWidget* w) {
if (GTK_IS_IMAGE(w)) {
if (g_object_get_data(G_OBJECT(w), "udpscope-icon-hidden") == nullptr) {
g_object_set_data(G_OBJECT(w), "udpscope-icon-hidden", GINT_TO_POINTER(1));
g_signal_connect(w, "notify::visible", G_CALLBACK(+[](GObject* o, GParamSpec*, gpointer) {
GtkWidget* img = GTK_WIDGET(o);
if (gtk_widget_get_visible(img)) gtk_widget_set_visible(img, FALSE);
}),
nullptr);
}
gtk_widget_set_visible(w, FALSE);
return;
}
for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr; c = gtk_widget_get_next_sibling(c)) {
hide_tab_icons(c);
}
}
void on_tab_pages_changed(GListModel*, guint, guint, guint, gpointer sw) {
hide_tab_icons(GTK_WIDGET(sw));
}
} // namespace
void keep_tabs_icon_free(App& app) {
if (app.side_switcher == nullptr || app.side_stack == nullptr) return;
hide_tab_icons(app.side_switcher);
// A page added later gets a button when the model reports it; this handler is connected *after*
// the switcher's own, so the button already exists when it runs.
g_signal_connect_after(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack)), "items-changed",
G_CALLBACK(on_tab_pages_changed), app.side_switcher);
// And once more when the switcher is (re)mapped, which also covers a rebuild of the buttons.
g_signal_connect_swapped(app.side_switcher, "map", G_CALLBACK(hide_tab_icons), app.side_switcher);
}
void build_source_panels(App& app) {
if (app.side_stack == nullptr || app.acq == nullptr || app.acq->source() == nullptr) return;
SourceKind* kind = find_kind(app.source_kind);