Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
217 lines
12 KiB
XML
217 lines
12 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 (`plot_overlay` > `plot_box`, the slide-over panel being an overlay child added in code)
|
|
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 tabs are words: the policy is `wide` and every page has a title, no icon. (They
|
|
were icon-only glyphs once; the glyphs were not understandable enough.) The pages'
|
|
*names* are what identify them to `select_side_tab()` and the session file; a source
|
|
kind's own tabs are added in code with their `SourcePanel::title`. -->
|
|
<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">wide</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">Measure</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">Signals</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>
|
|
<!-- The plot, and the slide-over panel that opens over its right edge. The panel is
|
|
one opaque overlay child placed by `halign=end` and a 360 px width, never a
|
|
full-area transparent child, which would be picked wherever its allocation is
|
|
and swallow the plot's pan, drop and hover (docs/claude/invariants-plot-and-layout.md).
|
|
The overlay is the only parent change: `plot_box` keeps its id and its content. -->
|
|
<object class="GtkOverlay" id="plot_overlay">
|
|
<property name="hexpand">1</property>
|
|
<property name="vexpand">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>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<!-- Setup is reached by the object it configures: a badge per signal on the left
|
|
(step 2), the system badges (Trigger, Source) on the right. A fixed height, so
|
|
the plot above it never moves when badges come and go. -->
|
|
<object class="GtkBox" id="badge_bar">
|
|
<property name="orientation">horizontal</property>
|
|
<property name="spacing">6</property>
|
|
<property name="height-request">36</property>
|
|
<property name="margin-start">6</property>
|
|
<property name="margin-end">6</property>
|
|
<child>
|
|
<object class="GtkBox" id="badge_signals">
|
|
<property name="orientation">horizontal</property>
|
|
<property name="spacing">6</property>
|
|
<property name="hexpand">1</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkBox" id="badge_system">
|
|
<property name="orientation">horizontal</property>
|
|
<property name="spacing">6</property>
|
|
<property name="halign">end</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>
|