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

+52 -46
View File
@@ -2,46 +2,67 @@
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The main window's skeleton: the window, the two panes that make its three columns, the plot column
with the status line under it, and the sidebar's scroller.
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
events pane's divider, the plot column with the status line under it, and the sidebar's scroller.
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
the reasons it is here:
- its width is *its own*: `sidebar-width-fraction` in **pixels** (the unit says so), floored by
`min-sidebar-width`. A `GtkPaned` kept a child's size in a divider position, which is a number of
pixels *of the window* — so on a window narrower than that number the sidebar came up with no
width at all, which is the bug this widget replaces;
- it resizes by dragging its own edge, within `min-sidebar-width`..`max-sidebar-width`;
- when the window is too narrow for both columns it *overlays* the plot instead of squeezing it,
which is what `collapsed` is for; `tick()` sets it from the window's width.
What is *not* here is as deliberate as what is. Everything this file nests is fixed — the shape of
the window does not depend on the stream, the trigger or the layout — while the four subtrees that
do are built by code and put in place:
the window does not depend on the stream, the trigger or the layout — while the subtrees that do are
built by code and put in place: the events pane, into `events_divider`'s start side; the two plot
areas, into the split inside `plot_box`; the sidebar's panels, appended to `side_column`; and the
status popover, attached to `status_info`.
- the events pane, into the inner paned's start side;
- the two plot areas, into the split inside `plot_box`;
- the sidebar's panels, appended to `side_column`;
- the status popover, attached to `status_info`.
A `GtkApplicationWindow` and not an `AdwApplicationWindow`, deliberately: the header bar is the
window's own titlebar and Adw's window refuses one outright (`gtk_window_set_titlebar() is not
supported for AdwApplicationWindow`, which is a fatal `Adwaita-ERROR`, not a warning). An Adw window
would want its header as an `AdwToolbarView`'s top bar instead, and this one is a `GtkHeaderBar`
packed by hand — a change for its own sake, and one that would take the window controls away from it
(a GtkHeaderBar draws those only as a titlebar).
The header bar is *not* in this file: it is the window's own titlebar,
built by `build_toolbar()` because every button on it is wired to an action. The status line is not a
bottom bar either — it belongs to the plot column, not to the window, so it stays inside `left_column`
where the paned divider can move past it.
The `GtkPaned`s are here rather than in code because their *rules* are the skeleton: the events pane
is shrinkable and does not resize (so a closed pane costs the plot nothing), and the sidebar is
neither. `gtk_paned_set_position()` is not, because it has to run after the children exist.
The header bar is not here either: it is the window's own titlebar, built by `build_toolbar()`
because every button on it is wired to an action. The status line is not a bottom bar — it belongs to
the plot column, not to the window, so it stays inside `left_column` where the divider can move past
it.
-->
<interface>
<object class="GtkApplicationWindow" id="main_window">
<property name="title">udpscope</property>
<property name="icon-name">org.udpscope.app</property>
<!-- The size the window asks for, stated in code as well as here: a window whose content has a
larger minimum opens at that minimum instead (see `ui.cpp`). -->
<property name="default-width">1280</property>
<property name="default-height">800</property>
<child>
<object class="GtkPaned" id="paned">
<property name="orientation">horizontal</property>
<property name="resize-end-child">0</property>
<property name="shrink-end-child">0</property>
<child type="start">
<object class="AdwOverlaySplitView" id="side_view">
<property name="sidebar-position">end</property>
<!-- Its width in pixels, not as a fraction of the window: 320 on a first run, and never less
than 240 however small the window gets. -->
<!-- Both ends of the range, in pixels: `sidebar-width-fraction` is a 0..1 *fraction* of the
window (setting a pixel count there is a GObject critical and falls back to 0.25), so the
width is pinned by min and max instead. -->
<property name="sidebar-width-unit">px</property>
<property name="min-sidebar-width">320</property>
<property name="max-sidebar-width">320</property>
<property name="sidebar">
<!-- The column scrolls as a whole, so a long signal list or a revealed trigger panel can
never push either panel out of the window. -->
<object class="GtkScrolledWindow" id="side_scroll">
<property name="hscrollbar-policy">never</property>
<property name="vscrollbar-policy">automatic</property>
<property name="min-content-width">300</property>
<property name="child">
<object class="GtkBox" id="side_column">
<property name="orientation">vertical</property>
<property name="width-request">300</property>
</object>
</property>
</object>
</property>
<property name="content">
<object class="GtkPaned" id="events_divider">
<property name="orientation">horizontal</property>
<!-- Shrinkable, or the pane's own minimum is reserved as an empty column even while it is
@@ -80,7 +101,7 @@
<child>
<!-- The line is rewritten every frame and its counters grow, so it is
ellipsized: a label's minimum width is its text, and an unellipsized one
would push the paned divider around as the numbers change. -->
would push the divider around as the numbers change. -->
<object class="GtkLabel" id="status_label">
<property name="label">starting…</property>
<property name="ellipsize">end</property>
@@ -100,22 +121,7 @@
</object>
</child>
</object>
</child>
<child type="end">
<!-- The column scrolls as a whole, so a long signal list or a revealed trigger panel can
never push either panel out of the window. -->
<object class="GtkScrolledWindow" id="side_scroll">
<property name="hscrollbar-policy">never</property>
<property name="vscrollbar-policy">automatic</property>
<property name="width-request">320</property>
<child>
<object class="GtkBox" id="side_column">
<property name="orientation">vertical</property>
<property name="width-request">300</property>
</object>
</child>
</object>
</child>
</property>
</object>
</child>
</object>