GTK4 and libadwaita. It receives one UDP stream through the vendored udpsclient, buffers it in a large ring, and draws it with the vendored plotter: a trigger with a pre/post event window, a pause that freezes the window, cursors, measurements, math channels in a node editor, an event cache, named configurations, preferences, and an export of the visible window to .npy/.csv. Waveform files can be opened and analysed in place. The interface is two threads -- a producer that owns the socket and the GTK main thread that owns every widget and the GL context -- sharing one model under one mutex, with samples crossing through lock-free rings. deps/plotter is a submodule pinned to the commit this tree builds against; deps/udpsclient is vendored in place. docs/claude/ documents the rules that cost a defect to learn, and SPECS.md is the requirement document.
61 lines
4.2 KiB
Markdown
61 lines
4.2 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/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/ui_prefs.cpp` (~330 lines) | the preferences: `.ui` + `AdwPreferencesDialog`, `AdwEntryRow`, `AdwSpinRow`, `AdwSwitchRow`, `AdwComboRow` | the most visible win per line: it replaces four hand-built rows with four rows that look and behave like every other GNOME application's, and the file is self-contained |
|
|
| `src/help_window.cpp` (351 lines) | the help window's chrome as a `.ui` (the pages themselves stay resources) | small, self-contained, and its probe already opens it |
|
|
| `src/source_dialog.cpp` (~900 lines) | the source chooser and the host form: `.ui` + `AdwDialog`, with `AdwEntryRow`/`AdwSpinRow` for the form | the form is the second-biggest hand-built surface, and its probes drive every branch of it |
|
|
| `src/ui.cpp` (5774 lines) | the main window's **skeleton** only -- the `AdwApplicationWindow`, the header bar, the plot area's placeholder, the panes' containers, the status bar -- with the custom widgets, popovers and dialogs left in code | one file, one window: converting the skeleton is reviewable, converting the whole file is not. Do it in slices (header bar, then the panes, then the trigger panel, then the left panes) with the probe suite after each |
|
|
|
|
Nothing in this list should be done in one pass. Each row is its own change, verified on its own.
|