src/ui/main-window.ui holds the window, its two panes, the plot column with the status line under it, and the sidebar's scroller. What stays in code is what depends on the stream: the events pane, the two plot areas, the sidebar's panels and the status popover, each put into a slot by id. The header bar is deliberately not in the file. It is the window's own titlebar, every button on it is wired to an action, and an AdwApplicationWindow refuses a titlebar outright -- a fatal Adwaita-ERROR rather than a warning that leaves the window merely ugly -- so the window is a GtkApplicationWindow and the header bar stays where it was. gtk_paned_set_position() is the other thing that had to stay in code, and the reason is order rather than shape: it clamps against its children *now*, so stating it before the sidebar's panels exist moved the plot column by ~180 px. The layout, events and box-zoom probes read what they read before this change, and the header reads window_button_w=130 bar_w=756 as it always has -- checked by building the previous commit and running the same probe against it, because the layout numbers in the notes turned out to be stale from an earlier session. Five UI files now.
72 lines
4.8 KiB
Markdown
72 lines
4.8 KiB
Markdown
# UI files and current GTK practice
|
|
|
|
Where the application's interface should be a `.ui` resource instead of widget construction, what is
|
|
already there, and the order to finish it in. Written 2026-10-11.
|
|
|
|
## Why
|
|
|
|
Three reasons, in the order they bite.
|
|
|
|
1. **Some things can only be said in a UI file.** The shortcut overview is the proof: GTK's
|
|
`GtkShortcutsShortcut` has exactly one gesture type that draws an `icon`, and libadwaita's
|
|
`AdwShortcutsItem` has no icon property at all -- so a mouse gesture can only be shown as a glyph
|
|
from a `.ui`. `src/ui/shortcuts.ui` is that, and it is the reason the file exists.
|
|
2. **Structure is data.** A window's arrangement rewritten in C++ is a hundred lines of
|
|
`gtk_box_append` that no one can read at a glance, cannot be compared with a diff, and cannot be
|
|
previewed. A UI file is the same arrangement in the notation the GNOME ecosystem reads.
|
|
3. **Current GTK and libadwaita have better widgets than this app uses.** The forms here are
|
|
`GtkEntry`/`GtkSpinButton` in hand-built label rows. The modern equivalents -- `AdwEntryRow`,
|
|
`AdwSpinRow`, `AdwSwitchRow`, `AdwComboRow`, inside `AdwPreferencesPage`/`AdwPreferencesGroup` in an
|
|
`AdwPreferencesDialog` -- are what the preferences dialog should be, and they come from the same
|
|
move.
|
|
|
|
## Conventions (established, and in `docs/claude/invariants-gtk-and-chrome.md`)
|
|
|
|
- `.ui` files live in `src/ui/` and are compiled by the third GResource (`udpscope-ui.gresource.xml`,
|
|
`prefix=/org/udpscope/ui`), loaded with `gtk_builder_new_from_resource()`.
|
|
- Every file carries `preprocess="xml-stripblanks"`, which is the only thing that catches a malformed
|
|
file at build time.
|
|
- A file that is valid XML but wrong for GTK **aborts the process** (`Gtk-ERROR`). The probe that opens
|
|
every window is what catches that; there is no build-time substitute.
|
|
- A `GIcon` property takes a nested object, not a name.
|
|
- A `.ui` over 4095 characters needs `-Wno-overlength-strings` on its generated source.
|
|
- A window built from a builder is replaced (not reused) on the next opening: it belongs to the
|
|
builder's object list, and a destroyed `GtkWindow` cannot be presented again.
|
|
- A deprecated *widget* is acceptable when no deprecated *function* is called.
|
|
|
|
## Acceptance for every slice
|
|
|
|
The same as any other change here: the build stays at **four warnings**, a run stays at **no
|
|
`Gtk-CRITICAL`**, and the slice's own surface is read back by a probe rather than asserted. A new `.ui`
|
|
needs a probe that proves it *loads* and that its contents match whatever drives it -- `[help]`'s
|
|
`ui=` against `tables=` is the model.
|
|
|
|
## Already done
|
|
|
|
- `src/ui/main-window.ui` -- the main window's **skeleton**: the window, its two panes, the plot column
|
|
with the status line under it, and the sidebar's scroller. The four subtrees that depend on the stream
|
|
(the events pane, the two plot areas, the sidebar's panels, the status popover) are still built by
|
|
code and put into the slots by id.
|
|
- `src/ui/source-form.ui` -- the form a source is added or edited with: an `AdwDialog` of
|
|
`AdwPreferencesGroup` rows, with its action in the header bar where the label says which mode it is in.
|
|
- `src/ui/help.ui` -- the help window's chrome: an `AdwWindow` with a header bar and an
|
|
`AdwNavigationSplitView` whose sidebar is the page list. The rows stay in C++ because the fourteen
|
|
pages are data, not template.
|
|
- `src/ui/preferences.ui` -- the preferences, as an `AdwPreferencesDialog` with `AdwEntryRow`,
|
|
`AdwSpinRow`, `AdwSwitchRow`, `AdwComboRow` and `AdwButtonRow` rows. The Apply button is gone: a row
|
|
applies as it changes, which is what those widgets are designed around, and `[prefs-probe]` reads the
|
|
file back after every change.
|
|
- `src/ui/shortcuts.ui` -- the shortcut overview, with the mouse rows drawn as an icon cell.
|
|
- The `src/ui/` GResource and its build block.
|
|
- `[help]`'s `ui=`/`ui_accel=`/`ui_icon=`/`ui_bare=` reading, which checks the file against the tables
|
|
and asserts that no row is left without a visual.
|
|
|
|
## Remaining, smallest first
|
|
|
|
| where | what | why it is the right next step |
|
|
|---|---|---|
|
|
| `src/source_dialog.cpp` (the chooser's own window) | the chooser: `.ui` for the window, its two pages and its action row. Its **host form is done**; what is left is the list, the notebook and the buttons around it | the last hand-built UI outside `ui.cpp`, and the file is already open |
|
|
| `src/ui.cpp` (the panels inside the skeleton) | the trigger panel, the signal panel, the cursors/measurements/math panels, the layout editor's overlay, the menus and the popovers: `.ui` for each, in that order, with the probe that reads it run after each | each is self-contained inside the skeleton that is now in place, and each has a probe of its own |
|
|
|
|
Nothing in this list should be done in one pass. Each row is its own change, verified on its own.
|