The Measure tab is now `udpscope-tab-measure-symbolic` — the two cursors standing across a ruler — and the Signals tab is `udpscope-tab-signals-symbolic` — the trigger's rising edge over the channel list it acts on. Each is a 16px composite rather than one of the icons the toolbar carries: a glyph the toolbar already uses for one thing means two things the moment a tab shows it too, which is why the ruler alone and the trigger alone were both wrong here. The plain ruler stays exactly where it was. `udpscope-measure-symbolic` is the Preferences window's Plot page icon and is untouched; the tab's pair is a new file beside it, so nothing that already meant "the measurement ruler" changed meaning. Seven action icons bundled, nine with the two app icons. Both composites are at the limit of what 16px holds — four features each — so each was drawn and then *measured*: rasterized and read back as an ink map, since the shapes have to be checked as pixels and the house range is 25-36% ink (trigger 25, cursors 27, ruler 36). The Signals list is two rows and not three, so the edge and the list get half the cell each; its bars are all one length, because unequal ones read as a bar chart and a chart is not a list. The Measure ruler's ends are open on purpose: a closed frame puts four more pixels of ink at the left and right edges, which is what turns a ruler into a rectangle at this size. First drafts came out at 39% and 44% ink and were rebalanced to 32% and 36%. Verified: `page icons: measure=udpscope-tab-measure-symbolic(ok) signals=udpscope-tab-signals-symbolic(ok)`; `switcher: policy=narrow pills=2 labels=0 names=2`; `[side-tab-probe] … advanced=4` (switching by page name still works); the Preferences dialog opened in the same run with no "not present"; `make test-ui` 14 files; zero `Gtk-CRITICAL`. The icons' GResource blob is now 11309 bytes, still over C99's 4095-byte string-literal limit, so the clean-build warning count is still four.
192 lines
11 KiB
XML
192 lines
11 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!-- SPDX-License-Identifier: MIT -->
|
|
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
|
<!--
|
|
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 tab stack.
|
|
|
|
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 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 `measure_page` and `signals_page`; and the
|
|
status popover, attached to `status_info`.
|
|
|
|
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="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">
|
|
<!-- A switcher above a stack, and a scroller *per page*: the two pages are different
|
|
heights, so one shared scroller around the stack would leave a tab switch scrolled
|
|
to somewhere the new page never asked for. The switcher is the sibling above, not a
|
|
child of either scroller: the one control that chooses what you are looking at must
|
|
not be the first thing to scroll away.
|
|
|
|
The policy is `narrow` and the **titles are empty**: `narrow` still draws a label
|
|
beside an icon (measured: a 48px "Measure" inside a 54px pill), so an empty title is
|
|
what makes a pill an icon. The pages' *names* are what identify them to
|
|
`select_side_tab()` and the session file, and the words are re-attached to the pills in
|
|
code — tooltip and accessible name — by `name_side_tabs()` on the first tick, so an
|
|
icon-only pill is still a named thing rather than an anonymous button.
|
|
Each page's icon is a glyph of its own, and each one is a *pair*: the Measure tab is
|
|
`udpscope-tab-measure-symbolic` (the cursors and the ruler they read against), and the
|
|
Signals tab is `udpscope-tab-signals-symbolic` (the trigger's edge and the channel list
|
|
below it). They are drawn for the tabs rather than reusing the icons the toolbar and the
|
|
Preferences window already carry: a glyph the toolbar uses for one thing means two things
|
|
the moment a tab shows it too. -->
|
|
<object class="GtkBox" id="side_column">
|
|
<property name="orientation">vertical</property>
|
|
<property name="width-request">300</property>
|
|
<child>
|
|
<object class="AdwViewSwitcher" id="side_switcher">
|
|
<property name="stack">side_stack</property>
|
|
<property name="policy">narrow</property>
|
|
<property name="margin-top">4</property>
|
|
<property name="margin-bottom">4</property>
|
|
<property name="margin-start">6</property>
|
|
<property name="margin-end">6</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="AdwViewStack" id="side_stack">
|
|
<property name="vexpand">1</property>
|
|
<child>
|
|
<object class="AdwViewStackPage">
|
|
<property name="name">measure</property>
|
|
<property name="title"></property>
|
|
<!-- The cursors and the ruler: what the pane brackets, and what it
|
|
reads the bracketed window with. -->
|
|
<property name="icon-name">udpscope-tab-measure-symbolic</property>
|
|
<property name="child">
|
|
<object class="GtkScrolledWindow">
|
|
<property name="hscrollbar-policy">never</property>
|
|
<property name="vscrollbar-policy">automatic</property>
|
|
<property name="child">
|
|
<object class="GtkBox" id="measure_page">
|
|
<property name="orientation">vertical</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="AdwViewStackPage">
|
|
<property name="name">signals</property>
|
|
<property name="title"></property>
|
|
<!-- The trigger and the channel list it acts on. -->
|
|
<property name="icon-name">udpscope-tab-signals-symbolic</property>
|
|
<property name="child">
|
|
<object class="GtkScrolledWindow">
|
|
<property name="hscrollbar-policy">never</property>
|
|
<property name="vscrollbar-policy">automatic</property>
|
|
<property name="child">
|
|
<object class="GtkBox" id="signals_page">
|
|
<property name="orientation">vertical</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</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
|
|
closed and the plot pays for a pane nobody opened. -->
|
|
<property name="shrink-start-child">1</property>
|
|
<property name="resize-start-child">0</property>
|
|
<child type="end">
|
|
<object class="GtkBox" id="left_column">
|
|
<property name="orientation">vertical</property>
|
|
<property name="hexpand">1</property>
|
|
<child>
|
|
<object class="GtkBox" id="plot_box">
|
|
<property name="orientation">vertical</property>
|
|
<property name="homogeneous">1</property>
|
|
<property name="spacing">4</property>
|
|
<property name="hexpand">1</property>
|
|
<property name="vexpand">1</property>
|
|
<child>
|
|
<object class="GtkPaned" id="plot_split">
|
|
<property name="orientation">horizontal</property>
|
|
<property name="shrink-end-child">1</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkBox" id="status_row">
|
|
<property name="orientation">horizontal</property>
|
|
<property name="spacing">6</property>
|
|
<property name="margin-top">2</property>
|
|
<property name="margin-bottom">2</property>
|
|
<property name="margin-start">6</property>
|
|
<child>
|
|
<object class="GtkLabel" id="status_badge"/>
|
|
</child>
|
|
<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 divider around as the numbers change. -->
|
|
<object class="GtkLabel" id="status_label">
|
|
<property name="label">starting…</property>
|
|
<property name="ellipsize">end</property>
|
|
<property name="hexpand">1</property>
|
|
<property name="halign">fill</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkMenuButton" id="status_info">
|
|
<property name="icon-name">help-about-symbolic</property>
|
|
<property name="tooltip-text" translatable="yes">Stream and buffer detail</property>
|
|
<property name="valign">center</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</interface>
|