feat: the locked trigger pane carries Arm/Disarm, and one function states both arm buttons

The compact line -- the pane's locked, "what is it set to" mode -- showed the arm state in words and gave
no way to change it, so arming from there meant unfolding the panel first, which is the thing the lock
exists to avoid. The control is built into the pane's *heading*, the row that stays on screen when the
body goes, and it is wired to the same `on_arm_clicked` the body's button uses, so both do one thing.

Its label, sensitivity and visibility are stated by `sync_arm_buttons()`, called from the four places any of
its inputs can move: the click, the mode toggle, the curator's per-tick re-read (a single-shot latch disarms
by itself) and `apply_pane_lock()`. The label comes from the producer's `trigger_armed()` atomic, the other
two from the toolbar toggle that owns `enabled`, so no caller needs the model lock. Visibility is
`enabled && locked`: with trigger mode off the line beside it already reads "stream (trigger off)" and there
is no engine to arm, and with the pane open the body's own button is the one on screen. Never both -- two
arm buttons would be two controls for one state.

Verified with the arm probe, which locks the pane the way a user does (the [edit] click):

    compact arm with the trigger off: visible=0; with the trigger on and the pane locked:
      visible=1 sensitive=1 width=51 label=Disarm
    [udpscope] compact arm probe: locked=1 enabled=1 own=1 effective=1 width=51 panel_width=-1 label=Disarm

`panel_width=-1` is the "not both" assertion: the body's button is not on screen while the compact one is.
The width cost a defect to read. The control is created hidden and shown for the first time inside the
handler, so a same-tick `gtk_widget_get_width()` reads 0 while `gtk_widget_is_visible()` already says 1 --
and the body button's *stale* allocation (270 px, from when the body was still up) reads as a live one,
because `gtk_widget_get_visible()` is the widget's own flag and stays true under a hidden parent. The
reading is therefore taken one frame later, by a step of its own, and the control is compared with
`is_visible()`. Both defects are in the invariants, because the next control read this way will meet them.

0 Gtk-CRITICAL; clean build with the documented four warnings (the help resource re-measured); test-ui
14/14. Docs: the rule and both readings in `invariants-trigger-and-pinning.md`, the probe's entry in
`probe-readings.md`, and the user-facing sentence in `src/help/08-trigger.txt`.
This commit is contained in:
Martino Ferrari committed 2026-10-14 16:18:24 +02:00
1 parent 908286ee89
commit 620473a5be
8 files changed
+160 -17

No files matched your search

+10
View File
@@ -1207,6 +1207,16 @@ its pane.
a trigger is configured before it is switched on — so this reads `1` always, and a `0` means the
settings have been made unreachable again. The *cursors'* body is disabled with the cursors, and that
is the pane whose readout has nothing to show.
- **`compact arm with the trigger off: visible=0` / `with the trigger on and the pane locked:
visible=1 sensitive=1 width=51 label=Disarm`** is the compact pane's arm control, in the two states
that define it. The first says it is *not* on the line when there is no engine to arm; the second says
it is, and that it is live (`sensitive=1`) and laid out (`width` > 0 — a width can only be read the
frame *after* it is first shown, see the invariants). `label` must agree with `model->trigger_armed()`
rather than with a fixed word, because a single-shot latch may have disarmed it by the time the probe
looks. `[udpscope] compact arm probe: locked=1 enabled=1 own=1 effective=1 width=51 panel_width=-1
label=Disarm` prints the inputs beside the outcome, and **`panel_width=-1` is the assertion that the
body's own Arm button is not on screen at the same time** — two arm controls for one state is the
defect it exists to catch.
- **`arm sensitive with the trigger off=0`** is the one exception inside that body: `Arm` is a *runtime*
control, so with trigger mode off there is no engine to arm and it is disabled (and labelled `Arm`
again). `1` here is an Arm button that does nothing when clicked.