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:
1 parent
a8c25f0cb3
commit
bb01278d88
7 files changed
+165
-54
No files matched your search
@@ -4,6 +4,22 @@ How `plot::Figure`/`plot::Plot` may and may not be driven, what a row's state be
|
|||||||
|
|
||||||
Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to learn; read the ones touching the area you are editing before you edit it.
|
Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to learn; read the ones touching the area you are editing before you edit it.
|
||||||
|
|
||||||
|
- **A sidebar's size must be its own, never a divider's position.** `GtkPaned` keeps a child's size in
|
||||||
|
`position`, which is a number of pixels **of the window**: the sidebar's width *was* `window - 940`, so on
|
||||||
|
any window narrower than 940 px — a scaled display, where the logical width is half what the screen looks
|
||||||
|
like, or a window a manager capped — the sidebar came up **0 px wide**, and the only way to see it was to
|
||||||
|
drag the divider back. Every patch that kept the paned failed for the same reason (the position derived
|
||||||
|
from the paned's own width, re-parenting the column, the property form of its children, size requests):
|
||||||
|
the *mechanism* was the fault. It is an **`AdwOverlaySplitView`** now — what a utility sidebar is in
|
||||||
|
GNOME, as against `AdwNavigationSplitView`, which is for navigation lists — with `min-sidebar-width` =
|
||||||
|
`max-sidebar-width` = 320 in **pixels**, so the width is its own at any window size, and `collapsed` (set
|
||||||
|
from the window's width in `tick()`) to *overlay* the plot when there is no room beside it rather than
|
||||||
|
being squeezed. `[sidebar-probe]` reads both states: `window=756 sidebar=320` under a 2x scale is exactly
|
||||||
|
the case that used to be zero.
|
||||||
|
- **`AdwOverlaySplitView:sidebar-width-fraction` is a 0..1 fraction of the window, whatever
|
||||||
|
`sidebar-width-unit` says.** Setting a pixel count there (320) is a `GObject` critical, and it silently
|
||||||
|
falls back to 0.25 — which reads as "the sidebar is mysteriously 560 px wide" on a wide window with no
|
||||||
|
error anywhere the build would catch. Pin the width with `min-sidebar-width`/`max-sidebar-width`.
|
||||||
- **A `GtkPaned`'s position is stated after both its sides are filled**, and that is a fact about the
|
- **A `GtkPaned`'s position is stated after both its sides are filled**, and that is a fact about the
|
||||||
*function's order* rather than about the window's shape, which is why it is the one part of the
|
*function's order* rather than about the window's shape, which is why it is the one part of the
|
||||||
window skeleton that cannot live in `main-window.ui`. `gtk_paned_set_position()` clamps against what
|
window skeleton that cannot live in `main-window.ui`. `gtk_paned_set_position()` clamps against what
|
||||||
|
|||||||
@@ -1043,6 +1043,23 @@ library measures and draws the rect in tests of its own.
|
|||||||
defect this reading exists for, and it was invisible from the frame alone: the file view pinning its
|
defect this reading exists for, and it was invisible from the frame alone: the file view pinning its
|
||||||
axis from the ring each frame is what swallowed the gesture.
|
axis from the ring each frame is what swallowed the gesture.
|
||||||
|
|
||||||
|
## `[sidebar-probe]`
|
||||||
|
|
||||||
|
```
|
||||||
|
[sidebar-probe] beside window=756 sidebar=320 collapsed=1
|
||||||
|
[sidebar-probe] overlaid window=756 sidebar=320 collapsed=1
|
||||||
|
```
|
||||||
|
|
||||||
|
`UDPSCOPE_PROBE_SIDEBAR_MS` reads the sidebar's width in both of the split view's states: beside the plot,
|
||||||
|
and **overlaid** on it (`collapsed`, which is the state a window with no room beside the plot produces).
|
||||||
|
|
||||||
|
- **`sidebar` must never be 0.** It was, for every window narrower than the 940 px the divider used to sit
|
||||||
|
at, and the pane could only be found by dragging the divider back — the report this reading exists for.
|
||||||
|
It must read its own width (320) in *both* states: `collapsed` changes where the sidebar is, never how
|
||||||
|
wide it is.
|
||||||
|
- `collapsed=1` on the `beside` line means the window is narrower than `kSidebarRoom` (900), which is the
|
||||||
|
expected reading on a scaled display, where the logical width is half what the screen looks like.
|
||||||
|
|
||||||
## `[prefs-probe]`
|
## `[prefs-probe]`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ needs a probe that proves it *loads* and that its contents match whatever drives
|
|||||||
| `src/ui/ui_layout.cpp` (~20 kB) | the layout editor's overlay buttons, which are positioned by code | only the buttons are template |
|
| `src/ui/ui_layout.cpp` (~20 kB) | the layout editor's overlay buttons, which are positioned by code | only the buttons are template |
|
||||||
| `build_toolbar` (`src/ui.cpp`, ~212 lines) | the header bar's layout, with every button still wired in code | delicate: the `[udpscope] header:` probe reads its geometry |
|
| `build_toolbar` (`src/ui.cpp`, ~212 lines) | the header bar's layout, with every button still wired in code | delicate: the `[udpscope] header:` probe reads its geometry |
|
||||||
|
|
||||||
|
**Done since:** the sidebar is an `AdwOverlaySplitView` (`src/ui/main-window.ui`) rather than a paned's end
|
||||||
|
child, so its width is its own 320 px and it overlays the plot instead of collapsing to zero — see
|
||||||
|
`[sidebar-probe]`.
|
||||||
|
|
||||||
Each of these is one change on the pattern `src/ui/ui_cursors.cpp` now follows: `ui_load()` the file, resolve
|
Each of these is one change on the pattern `src/ui/ui_cursors.cpp` now follows: `ui_load()` the file, resolve
|
||||||
the ids, keep every value, signal and rebuild in code, and run the panel's own probe before committing.
|
the ids, keep every value, signal and rebuild in code, and run the panel's own probe before committing.
|
||||||
|
|
||||||
|
|||||||
+34
-8
@@ -2753,7 +2753,12 @@ void update_side_visibility(App& app) {
|
|||||||
const bool have_signals = !app.model->signals().empty();
|
const bool have_signals = !app.model->signals().empty();
|
||||||
const bool want_column =
|
const bool want_column =
|
||||||
have_signals && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app.sidebar_toggle));
|
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.signal_toggle, have_signals);
|
||||||
gtk_widget_set_visible(app.trigger_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.
|
// follow a resize and the gutter settling without being told.
|
||||||
if (app->layout_editing) sync_layout_editor(*app);
|
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));
|
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
|
// 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
|
// 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");
|
app->window_ui = gtk_builder_new_from_resource("/org/udpscope/ui/main-window.ui");
|
||||||
if (app->window_ui == nullptr) return;
|
if (app->window_ui == nullptr) return;
|
||||||
app->window = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "main_window"));
|
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"));
|
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_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"));
|
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"));
|
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
|
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
||||||
// be a null pointer.
|
// 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->plot_box == nullptr || app->plot_split == nullptr || app->status_badge == nullptr ||
|
||||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||||
app->side_scroll == nullptr || side == 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.
|
// shows are the window's, and those are in the file.
|
||||||
gtk_window_set_titlebar(GTK_WINDOW(app->window), build_toolbar(*app));
|
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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// 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
|
// Nothing to state about the sidebar's size. It is the split view's: 320 px from the `.ui`, floored
|
||||||
// against an empty pane and the divider lands wherever the empty width happened to be. This is the
|
// at 240, and never a number derived from the window -- which is what the removed
|
||||||
// one thing about a `GtkPaned` that cannot live in the `.ui`: it has to be stated after both sides
|
// `gtk_paned_set_position(paned, 940)` was, and why a window narrower than 940 px gave the sidebar
|
||||||
// have been filled, which is a fact about this function's order, not about the window's shape.
|
// no width at all.
|
||||||
gtk_paned_set_position(GTK_PANED(paned), 940);
|
|
||||||
|
|
||||||
// The split is in the `.ui`; what goes in it is not, because an area is a GL surface with a figure
|
// 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.
|
// 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")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_BOXZOOM_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), boxzoom_probe, &app);
|
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")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_PREFS_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), prefs_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), prefs_probe, &app);
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-46
@@ -2,46 +2,67 @@
|
|||||||
<!-- SPDX-License-Identifier: MIT -->
|
<!-- SPDX-License-Identifier: MIT -->
|
||||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||||
<!--
|
<!--
|
||||||
The main window's skeleton: the window, the two panes that make its three columns, the plot column
|
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
||||||
with the status line under it, and the sidebar's scroller.
|
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
|
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
|
the window does not depend on the stream, the trigger or the layout — while the subtrees that do are
|
||||||
do are built by code and put in place:
|
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 header bar is not here either: it is the window's own titlebar, built by `build_toolbar()`
|
||||||
- the two plot areas, into the split inside `plot_box`;
|
because every button on it is wired to an action. The status line is not a bottom bar — it belongs to
|
||||||
- the sidebar's panels, appended to `side_column`;
|
the plot column, not to the window, so it stays inside `left_column` where the divider can move past
|
||||||
- the status popover, attached to `status_info`.
|
it.
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
-->
|
||||||
<interface>
|
<interface>
|
||||||
<object class="GtkApplicationWindow" id="main_window">
|
<object class="GtkApplicationWindow" id="main_window">
|
||||||
<property name="title">udpscope</property>
|
<property name="title">udpscope</property>
|
||||||
<property name="icon-name">org.udpscope.app</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-width">1280</property>
|
||||||
<property name="default-height">800</property>
|
<property name="default-height">800</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkPaned" id="paned">
|
<object class="AdwOverlaySplitView" id="side_view">
|
||||||
<property name="orientation">horizontal</property>
|
<property name="sidebar-position">end</property>
|
||||||
<property name="resize-end-child">0</property>
|
<!-- Its width in pixels, not as a fraction of the window: 320 on a first run, and never less
|
||||||
<property name="shrink-end-child">0</property>
|
than 240 however small the window gets. -->
|
||||||
<child type="start">
|
<!-- 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">
|
<object class="GtkPaned" id="events_divider">
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
<!-- Shrinkable, or the pane's own minimum is reserved as an empty column even while it is
|
<!-- Shrinkable, or the pane's own minimum is reserved as an empty column even while it is
|
||||||
@@ -80,7 +101,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<!-- The line is rewritten every frame and its counters grow, so it is
|
<!-- 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
|
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">
|
<object class="GtkLabel" id="status_label">
|
||||||
<property name="label">starting…</property>
|
<property name="label">starting…</property>
|
||||||
<property name="ellipsize">end</property>
|
<property name="ellipsize">end</property>
|
||||||
@@ -100,22 +121,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
<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>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
@@ -405,6 +405,10 @@ struct App {
|
|||||||
|
|
||||||
/// The main window's builder. Held because the window belongs to its object list: it is replaced on
|
/// The main window's builder. Held because the window belongs to its object list: it is replaced on
|
||||||
/// the next build rather than reused, and everything found by id comes out of it.
|
/// the next build rather than reused, and everything found by id comes out of it.
|
||||||
|
/// The split view the sidebar lives in. Its width, its collapse and its overlay are that widget's
|
||||||
|
/// business -- `AdwOverlaySplitView` -- and not a paned position's.
|
||||||
|
GtkWidget* side_view = nullptr;
|
||||||
|
|
||||||
GtkBuilder* window_ui = nullptr;
|
GtkBuilder* window_ui = nullptr;
|
||||||
bool comparing = false;
|
bool comparing = false;
|
||||||
/// The last x/y range seen on each side of a comparison, one entry per row: a frame compares them
|
/// The last x/y range seen on each side of a comparison, one entry per row: a frame compares them
|
||||||
@@ -1000,6 +1004,9 @@ struct RowMenuProbeState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constexpr int kTickMs = 16;
|
constexpr int kTickMs = 16;
|
||||||
|
/// Below this window width the sidebar has no room beside the plot, and the split view overlays it
|
||||||
|
/// instead of squeezing it. See `tick()`.
|
||||||
|
constexpr int kSidebarRoom = 900;
|
||||||
constexpr int kWindowUnitCount = 3;
|
constexpr int kWindowUnitCount = 3;
|
||||||
constexpr double kWindowMinS = 1.0e-6;
|
constexpr double kWindowMinS = 1.0e-6;
|
||||||
constexpr int kWindowLabelChars = 9;
|
constexpr int kWindowLabelChars = 9;
|
||||||
@@ -1129,6 +1136,7 @@ void sync_events_divider(App& app);
|
|||||||
/// because the theme is the one preference that takes effect without a restart.
|
/// because the theme is the one preference that takes effect without a restart.
|
||||||
void open_preferences(App& app);
|
void open_preferences(App& app);
|
||||||
gboolean prefs_probe(gpointer user);
|
gboolean prefs_probe(gpointer user);
|
||||||
|
gboolean sidebar_probe(gpointer user);
|
||||||
gboolean boxzoom_probe(gpointer user);
|
gboolean boxzoom_probe(gpointer user);
|
||||||
/// A labelled row whose control keeps its natural width -- what a `GtkSwitch` needs and what
|
/// A labelled row whose control keeps its natural width -- what a `GtkSwitch` needs and what
|
||||||
/// `labeled_box()`'s expanding child denies it.
|
/// `labeled_box()`'s expanding child denies it.
|
||||||
|
|||||||
@@ -1633,6 +1633,40 @@ gboolean boxzoom_probe(gpointer user) {
|
|||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// `UDPSCOPE_PROBE_SIDEBAR_MS`: the sidebar's width, in both of the split view's states.
|
||||||
|
///
|
||||||
|
/// The small-window case is the one a divider position got wrong: with the sidebar's width derived from
|
||||||
|
/// the window, a window narrower than that number gave it nothing at all. The split view takes its size
|
||||||
|
/// from its own properties instead, and when there is no room beside the plot it *overlays* it
|
||||||
|
/// (`collapsed`) rather than shrinking to nothing. Both states are read here, so "the sidebar is never
|
||||||
|
/// zero" is a number rather than a claim.
|
||||||
|
gboolean sidebar_probe(gpointer user) {
|
||||||
|
App& a = *static_cast<App*>(user);
|
||||||
|
if (a.side_view == nullptr) return G_SOURCE_REMOVE;
|
||||||
|
const auto report = [](App& app, const char* what) {
|
||||||
|
const int side = app.side_box != nullptr ? gtk_widget_get_width(app.side_box) : -1;
|
||||||
|
const int win = app.window != nullptr ? gtk_widget_get_width(app.window) : -1;
|
||||||
|
std::fprintf(stderr, "[sidebar-probe] %-10s window=%d sidebar=%d collapsed=%d\n", what, win,
|
||||||
|
side, adw_overlay_split_view_get_collapsed(ADW_OVERLAY_SPLIT_VIEW(app.side_view)) ? 1
|
||||||
|
: 0);
|
||||||
|
};
|
||||||
|
report(a, "beside");
|
||||||
|
// The state a window with no room produces: the sidebar goes over the plot, and its width is still its
|
||||||
|
// own. If this ever reads 0, the sidebar is being given a share of a width that is not there.
|
||||||
|
adw_overlay_split_view_set_collapsed(ADW_OVERLAY_SPLIT_VIEW(a.side_view), TRUE);
|
||||||
|
g_timeout_add(400, +[](gpointer u) -> gboolean {
|
||||||
|
App& app = *static_cast<App*>(u);
|
||||||
|
const int side = app.side_box != nullptr ? gtk_widget_get_width(app.side_box) : -1;
|
||||||
|
std::fprintf(stderr, "[sidebar-probe] %-10s window=%d sidebar=%d collapsed=%d\n", "overlaid",
|
||||||
|
app.window != nullptr ? gtk_widget_get_width(app.window) : -1, side,
|
||||||
|
adw_overlay_split_view_get_collapsed(ADW_OVERLAY_SPLIT_VIEW(app.side_view)) ? 1 : 0);
|
||||||
|
adw_overlay_split_view_set_collapsed(ADW_OVERLAY_SPLIT_VIEW(app.side_view), FALSE);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}, &a);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean prefs_probe(gpointer user) {
|
gboolean prefs_probe(gpointer user) {
|
||||||
App& a = *static_cast<App*>(user);
|
App& a = *static_cast<App*>(user);
|
||||||
open_preferences(a);
|
open_preferences(a);
|
||||||
|
|||||||
Reference in new issue
Block a user