fix: the sidebar keeps its own width, so a small window can no longer hide it

The sidebar's width was a GtkPaned divider position: `window - 940` in pixels. On any window
narrower than 940 px the divider landed past the right edge and the sidebar came up 0 px wide,
reachable only by dragging the divider back.

Every attempt that kept the paned failed for that reason -- deriving the position from the paned's
own width, re-parenting the column, property-form children, code-set size requests. The mechanism was
the fault, so it is gone: the row is an AdwOverlaySplitView, which is what a utility sidebar is in
GNOME, with min-sidebar-width = max-sidebar-width = 320 in pixels. Its width is therefore its own at
any window size, and when there is no room beside the plot it *overlays* the plot (collapsed, set from
the window's width in tick()) instead of being squeezed to nothing.

Two things found on the way, both recorded in the invariants:

- sidebar-width-fraction is a 0..1 fraction of the window whatever sidebar-width-unit says. Setting a
  pixel count there is a GObject critical and it falls back to 0.25, which is why the sidebar first
  read 560. The width is pinned with min/max instead.
- the window opened at its content minimum (756 px, the plot column plus the sidebar) because the
  explicit gtk_window_set_default_size() was dropped when the window moved into the .ui. Restored.

[sidebar-probe] reads both states and is the guard: window=756 sidebar=320 under a 2x scale, which is
the case that used to read 0.
This commit is contained in:
Martino Ferrari committed 2026-10-12 12:17:56 +02:00
1 parent a8c25f0cb3
commit bb01278d88
7 files changed
+165 -54

No files matched your search

+34 -8
View File
@@ -2753,7 +2753,12 @@ void update_side_visibility(App& app) {
const bool have_signals = !app.model->signals().empty();
const bool want_column =
have_signals && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app.sidebar_toggle));
gtk_widget_set_visible(app.side_scroll, want_column);
// The split view owns the sidebar's visibility: it animates it, and when the window is too narrow it
// shows it as an overlay rather than letting the column be squeezed to nothing. Hiding the scroller
// instead left an empty column where the panels should be.
if (app.side_view != nullptr) {
adw_overlay_split_view_set_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app.side_view), want_column);
}
gtk_widget_set_visible(app.signal_toggle, have_signals);
gtk_widget_set_visible(app.trigger_toggle, have_signals);
}
@@ -3616,6 +3621,18 @@ gboolean tick(gpointer user) {
// follow a resize and the gutter settling without being told.
if (app->layout_editing) sync_layout_editor(*app);
// The one thing that has to be decided *per frame* about the sidebar: whether there is room for it
// beside the plot. Collapsed, the split view puts it *over* the plot rather than giving it a share of
// a width that is not there -- so a small window shows fewer pixels of plot, never a panel squeezed
// to nothing. `kSidebarRoom` is the sidebar's 320 plus the width below which the plot stops being
// worth looking at.
if (app->side_view != nullptr && app->window != nullptr) {
const int window_w = gtk_widget_get_width(app->window);
if (window_w > 0) {
adw_overlay_split_view_set_collapsed(ADW_OVERLAY_SPLIT_VIEW(app->side_view),
window_w < kSidebarRoom);
}
}
if (app->area_widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(app->area_widget));
// What the side column *demands*, not what it got. A GTK box's minimum width is its
// children's, a `GtkLabel`'s minimum is its text, and a minimum larger than the screen is
@@ -4375,7 +4392,8 @@ void on_activate(GtkApplication* application, gpointer user) {
app->window_ui = gtk_builder_new_from_resource("/org/udpscope/ui/main-window.ui");
if (app->window_ui == nullptr) return;
app->window = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "main_window"));
GtkWidget* paned = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "paned"));
GtkWidget* side_view = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_view"));
app->side_view = side_view;
GtkWidget* events_divider = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "events_divider"));
app->plot_box = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "plot_box"));
app->plot_split = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "plot_split"));
@@ -4386,7 +4404,7 @@ void on_activate(GtkApplication* application, gpointer user) {
GtkWidget* side = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_column"));
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
// be a null pointer.
if (app->window == nullptr || paned == nullptr || events_divider == nullptr ||
if (app->window == nullptr || side_view == nullptr || events_divider == nullptr ||
app->plot_box == nullptr || app->plot_split == nullptr || app->status_badge == nullptr ||
app->status_label == nullptr || app->status_info_button == nullptr ||
app->side_scroll == nullptr || side == nullptr) {
@@ -4413,6 +4431,12 @@ void on_activate(GtkApplication* application, gpointer user) {
// shows are the window's, and those are in the file.
gtk_window_set_titlebar(GTK_WINDOW(app->window), build_toolbar(*app));
// The size the window opens at, stated in code as well as in the `.ui`: `default-width` there is the
// size it *asks* for, and a window whose content has a larger minimum opens at that minimum instead --
// 756 px here, the plot column's own minimum plus the sidebar's 320. The old code set it explicitly;
// this is where it went when the window moved into the file, and it is why the window came up small.
gtk_window_set_default_size(GTK_WINDOW(app->window), 1280, 800);
// The badge and the text share one row so the badge sits at the line's head. It is a
// sibling rather than part of the label's own markup because the label's text is
// rewritten every frame and its markup is ellipsized -- a blinking span inside it would
@@ -4574,11 +4598,10 @@ void on_activate(GtkApplication* application, gpointer user) {
// Two plots, in a paned so a comparison's divider can move: the primary takes everything while the
// second is hidden, and the width is the user's once it is not.
// After the children exist -- the sidebar's panels are appended above -- or the position is clamped
// against an empty pane and the divider lands wherever the empty width happened to be. This is the
// one thing about a `GtkPaned` that cannot live in the `.ui`: it has to be stated after both sides
// have been filled, which is a fact about this function's order, not about the window's shape.
gtk_paned_set_position(GTK_PANED(paned), 940);
// Nothing to state about the sidebar's size. It is the split view's: 320 px from the `.ui`, floored
// at 240, and never a number derived from the window -- which is what the removed
// `gtk_paned_set_position(paned, 940)` was, and why a window narrower than 940 px gave the sidebar
// no width at all.
// The split is in the `.ui`; what goes in it is not, because an area is a GL surface with a figure
// handed to it.
@@ -5236,6 +5259,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
if (const char* env = g_getenv("UDPSCOPE_PROBE_BOXZOOM_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), boxzoom_probe, &app);
}
if (const char* env = g_getenv("UDPSCOPE_PROBE_SIDEBAR_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), sidebar_probe, &app);
}
if (const char* env = g_getenv("UDPSCOPE_PROBE_PREFS_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), prefs_probe, &app);
}