Feature/source plugins #1
No files matched your search
@@ -489,7 +489,7 @@ Two lines of the self-check output are assertions in disguise:
|
||||
column and says there is one scroller and no stack. These are captured on the tick or at the exit probe:
|
||||
the dump runs after the window is destroyed. A source with extra panels adds *no* dock section: its panels
|
||||
are slide-over pages (`source-<id>`), so the line reads the same on the E-1608 and the probe kind.
|
||||
- `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=5 step=0 auto=1 requested=0` (in the exit
|
||||
- `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=6 step=0 auto=1 requested=0` (in the exit
|
||||
dump, beside `dock:`) — the help affordance layer as the last tick found it. **`hints=` must equal
|
||||
the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window
|
||||
itself, and `math` only while the math editor is open (its heading lives in a builder that is rebuilt
|
||||
@@ -534,14 +534,18 @@ Two lines of the self-check output are assertions in disguise:
|
||||
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). The writer chains to
|
||||
`g_log_writer_default()`, so every message still reaches stderr exactly as before and the documented
|
||||
baselines are read the same way.
|
||||
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… side_stack=… window_button=… status_label=… header_bar=… plot=…` (in the exit
|
||||
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… badge_bar=… dock=… window_button=… status_label=… header_bar=… plot=…` (in the exit
|
||||
dump, beside `help layer:`) — the **window-coordinate** rectangle of each surface the help's annotated
|
||||
figures label, `x,y,width,height`, or `-1,-1,0,0` for a surface that is **not on screen**. It is
|
||||
measured on the tick while the window is alive (nothing in the dump may call `gtk_widget_*` — see
|
||||
[verification.md](verification.md)) and with `gtk_widget_is_visible()`, not `get_visible()`: a widget
|
||||
under a hidden parent — `trigger_summary` while the pane is in *configuration* mode, the Measure page
|
||||
while the Signals tab is showing — keeps its own flag true and reads `0x0`, and a crop box drawn from
|
||||
that is a box over the wrong thing. The figures are produced by cropping a real screenshot, so these
|
||||
[verification.md](verification.md)) and with `gtk_widget_is_visible()` **and** `gtk_widget_get_mapped()`,
|
||||
not `get_visible()`: a widget under a hidden parent — `trigger_summary` while the pane is in
|
||||
*configuration* mode, and **every `trigger_*` surface while the slide-over is closed** (a revealer
|
||||
unmaps its child without touching its visible flag) — keeps its own flag true and reads `0x0`, and a crop
|
||||
box drawn from that is a box over the wrong thing. So the three `trigger_*` rectangles read real numbers
|
||||
only while the `trigger` page is open (the exit probe has it open; `UDPSCOPE_PROBE_HELPUI_MS` opens it for
|
||||
its figures legs), and `-1,-1,0,0` otherwise. `badge_bar` is the badge row under the plot and `dock` the
|
||||
right-hand column (these replaced `side_stack`, the old sidebar stack). The figures are produced by cropping a real screenshot, so these
|
||||
numbers **are** the crop boxes; their values depend on the window's size, which under the documented
|
||||
`xvfb-run` is the window's 756x894 minimum (the 640x480 screen is smaller than the content), so a run
|
||||
on a larger screen reads different numbers for the same panels. `trigger_summary` reading
|
||||
@@ -878,7 +882,7 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
||||
every row then reports `h=0`. It turns autohide off for its own measurement — a popover dismisses
|
||||
itself when it loses the grab, and `xvfb-run` has no window manager to hand it one — and restores
|
||||
it afterwards.
|
||||
- `[helpui] auto_started=1 visible=1 steps=5 step=0 (first run)` (stderr, `UDPSCOPE_PROBE_HELPUI_MS`) —
|
||||
- `[helpui] auto_started=1 visible=1 steps=6 step=0 (first run)` (stderr, `UDPSCOPE_PROBE_HELPUI_MS`) —
|
||||
the help affordance layer end to end, in ten steps 300-400 ms apart (every reading is an
|
||||
*allocation*, and a step that read its own predecessor's geometry would read a rectangle from before
|
||||
the frame that placed it). The line above is the **first-run auto-start**, read before the probe
|
||||
@@ -910,21 +914,25 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
||||
affordances are *gone*, not greyed, so anything else is a row that hid its buttons instead of
|
||||
removing them. The count comes back to the same twelve, which is the other half — a toggle that
|
||||
removed and could not restore is a one-way switch.
|
||||
- `[helpui] tour started from the probe: visible=1 steps=5 step=0` — the tour started the way
|
||||
`Ctrl+T` starts it. `steps=5` is the plan: the table's five steps whose surface is on screen or
|
||||
selectable into it (`gtk_widget_get_mapped()` -- a wrong *page* is kept, because selecting its
|
||||
page reveals it, while a *collapsed pane* is still dropped; see the surface bullets in
|
||||
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
|
||||
- `[helpui] step=1/5 visible=1 slideover=none spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
|
||||
- `[helpui] tour started from the probe: visible=1 steps=6 step=0` — the tour started the way
|
||||
`Ctrl+T` starts it. `steps=6` is the plan: the table's six stops (plot, badges, trigger, dock,
|
||||
signals, status) whose surface is on screen or can be opened into view. A stop that names a
|
||||
slide-over page is always kept (the `on_page` hook opens it whatever the dock is doing), a target
|
||||
that is mapped is kept, and a stop in a *collapsed dock* is dropped (`gtk_widget_get_mapped()`; see
|
||||
the surface bullets in [invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
|
||||
- `[helpui] step=1/6 visible=1 slideover=none spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
|
||||
— **in window coordinates**, the frame a screenshot is cropped in, with the slide-over's open page
|
||||
(`none`, or a page id) beside it; see `[toursurface]` below.
|
||||
(`none`, or a page id) beside it; see `[toursurface]` below. The `target=` rectangle is the part of the
|
||||
target that is *on screen* (intersected with the scrolled windows above it, `tour_target_bounds()`): a
|
||||
form taller than the panel's scroller on a short window is spotlit down to the scroller's edge and no
|
||||
further.
|
||||
**`contains` must be 1** on every step: the spotlight is the target's own rectangle grown three
|
||||
pixels. `clear` is the card missing the target, and it is 1 wherever the window has room for the
|
||||
card beside or above/below the hole — at the documented run's window (see `help figures:` below)
|
||||
the plot's step is *the whole content*, because at 756 px the split view overlays the side panel
|
||||
rather than putting it beside the plot, so `clear=0` there is the geometry and not a placement
|
||||
bug. The side panel's step reads `clear=1`.
|
||||
- `[helpui] after Next: step=2/5 visible=1`, `[helpui] after Back: step=1/5 visible=1`,
|
||||
bug. The badges step reads `clear=1`.
|
||||
- `[helpui] after Next: step=2/6 visible=1`, `[helpui] after Back: step=1/6 visible=1`,
|
||||
`[helpui] after Skip: visible=0 dims_hidden=1 spot_hidden=1 card_hidden=1 controllers=12 (was 12)
|
||||
click_gestures=2` — the three controls. **`controllers=` equal to `(was N)` is the "no lingering
|
||||
input grab" reading**: a tour of ordinary overlay widgets takes no grab at all, and a count that
|
||||
@@ -935,7 +943,9 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
||||
trigger pane's modes, by clicking its [edit] button. This exists because one of the help's figures
|
||||
is the *locked* pane's summary grid (`trigger_summary`), which in configuration mode is not on
|
||||
screen at all and reads `-1,-1,0,0`; the two lines give the geometry agent both states' numbers
|
||||
rather than leaving them to find the mode. `trigger_panel`'s height differs between them (508 px in
|
||||
rather than leaving them to find the mode. The probe opens the slide-over's `trigger` page for these
|
||||
legs (and for the collapsed-search leg after them) and closes it at the end: with the panel shut
|
||||
every `trigger_*` reads `-1,-1,0,0`. `trigger_panel`'s height differs between them (532 px in
|
||||
configuration mode against 72 locked — the body is replaced by the summary), which is the check
|
||||
that the mode really moved.
|
||||
- `[helpui] collapsed search: expanded=0 mapped=1 mapped_header=1 hint=12x12` (the last step, in
|
||||
@@ -945,28 +955,40 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
||||
read `mapped=0` (and `0x0`), which is exactly why the header box exists (`search_head` in
|
||||
`src/ui/trigger-panel.ui`) and why the hint joins it as its last child. `mapped_header=` is the
|
||||
header box beside it, and `hint=WxH` is the same 12x12 the layer's own line reads.
|
||||
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) -- a stop that stands on a surface it is
|
||||
not on, entered from the state it is not in. Since the sidebar's tabs became the dock the probe opens the
|
||||
`trigger` slide-over page instead of switching a tab, starts the window's tour and walks it a stop per tick
|
||||
(400 ms). **The tour's own rework for the slide-over is Task 5's**: until its page hook exists the tour
|
||||
has no page stack, so a stop whose target is on a slide-over page is dropped, and the lines below read the
|
||||
dock only. The lines:
|
||||
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) -- a stop that opens a surface it is not
|
||||
on, entered from the state it is not in. The probe opens the `trigger` slide-over page, starts the
|
||||
window's tour from there and walks the whole table a stop per tick (400 ms, past the panel's 120 ms
|
||||
slide); the `on_page` hook is what opens the page for the trigger stop and closes the panel for every
|
||||
stop that names none. The lines:
|
||||
|
||||
- `[toursurface] before: slideover=none signal_list_mapped=1 running=1` -- the state a first run starts in
|
||||
(the dock is on screen), then the auto-started walk is dismissed.
|
||||
- `[toursurface] on trigger: slideover=trigger signal_list_mapped=1 side_box_mapped=1` -- a frame after the
|
||||
page was opened: the dock is still on screen under the slide-over (they are not alternatives).
|
||||
- `[toursurface] tour: visible=1 steps=N step=0 signals_stop=1` -- **`signals_stop=1` is the filter's
|
||||
reading**: the signal list is in the dock, which is mapped, so its stop is in the plan.
|
||||
- `[toursurface] step=N/M slideover=<page> target_mapped=D[ target=signal_list]` -- one per stop;
|
||||
**the `signals` stop reads `target_mapped=1`**.
|
||||
- `[toursurface] signals stop=N/M visible=1 slideover=… spot=[…] target=[…] contains=1 card=[…] clear=1` --
|
||||
the `tour_geometry_line()` the helpui probe uses: **`contains=1` is the assertion** that the spotlight
|
||||
and the card are on the list.
|
||||
- `[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0 side_stop=0` -- the other half of
|
||||
the filter, read: with `sidebar_wanted` off (`update_side_visibility()` re-asserts it, so this is how a
|
||||
probe puts the sidebar away for good) the split view unmaps the column and the stops that need the
|
||||
dock are dropped while the page-less ones stay. This is what says a *collapsed pane* is dropped.
|
||||
- `[toursurface] before: slideover=none signal_list_mapped=1 running=1` -- the state a first run starts
|
||||
in (panel closed, dock on screen); the auto-started walk is dismissed.
|
||||
- `[toursurface] on trigger: slideover=trigger trigger_body_mapped=1 side_box_mapped=1` -- a frame after
|
||||
the page was opened: the dock is still on screen under the panel (they are not alternatives).
|
||||
- `[toursurface] tour: visible=1 steps=6 step=0 trigger_stop=1 signals_stop=1` -- the plan: **six
|
||||
stops, the trigger stop and the signals stop both in it**.
|
||||
- `[toursurface] step=N/6 title='…' slideover=<page> target_mapped=D` -- one per stop, in order: *The
|
||||
plot*, *The badges*, *The trigger*, *The dock*, *The signals*, *The status line*. **Step 1 reads
|
||||
`slideover=none` although the probe started with the panel open** (a stop with no page closes it),
|
||||
**step 3 reads `slideover=trigger`, and step 4 reads `slideover=none` again**; every `target_mapped`
|
||||
is 1.
|
||||
- `[toursurface] stop=N/6 visible=1 slideover=… spot=[…] target=[…] contains=1 card=[…] clear=C` -- the
|
||||
`tour_geometry_line()` the helpui probe uses, one per stop, read a frame after the stop was entered
|
||||
(the spotlight is withheld until the panel has finished sliding in, `page_settled`): **`contains=1` on
|
||||
every stop is the assertion** that the spotlight is on the target. `clear=0` is read as geometry, not
|
||||
as a placement bug, on the plot stop (the target is the whole content) and on the trigger stop at the
|
||||
documented 756 px window (the panel is 360 px of it, so the card has no side wide enough and goes flush
|
||||
left over the plot, overlapping the panel's edge by a few pixels).
|
||||
- `[toursurface] finished: running=0 slideover=none` -- the last Next ended the walk and **the panel is
|
||||
closed with it**.
|
||||
- `[toursurface] restarted at trigger: running=1 slideover=trigger` then
|
||||
`[toursurface] skipped: running=0 slideover=none` -- the walk parked on the trigger stop and dismissed
|
||||
(the Skip/Esc route): **`slideover=none` after the dismissal** is the tour not leaving a panel behind.
|
||||
- `[toursurface] collapsed: side_box_mapped=0 steps=4 trigger_stop=1 signals_stop=0 side_stop=0` -- the
|
||||
filter with the dock put away (`sidebar_wanted` off; `update_side_visibility()` re-asserts it, so this
|
||||
is how a probe puts the dock away for good): the dock's two stops are dropped, **and the trigger stop is
|
||||
kept** because the slide-over does not depend on the dock -- four stops remain (plot, badges, trigger,
|
||||
status).
|
||||
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: dock 0->243 (cursors_y=243 reach=2256 at_cursors=1) | g: dock 243->0 at_top=1 | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` returns, so its pair reads `243->0`) and **`refused` must be 1**.
|
||||
The `m`/`g` readings are the dock scroller's vertical adjustment before and after (the probe pads the dock to 3000 px so there is something to scroll, and undoes it at the end): **`m` must land on `cursors_y`, the cursors panel's y in the dock (`at_cursors=1`), and `g` must return to 0 (`at_top=1`)**, which is what says those two keys scroll to a section rather than toggling a pane; a `reach` of 0 would mean the dock fit its viewport and the probe's padding was lost; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `<key>: <widget-description>: <before>-><after>` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values).
|
||||
- `[color] '<name>' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `<span foreground="…">` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit.
|
||||
|
||||
@@ -639,11 +639,13 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
second and a half, and that start *writes* `tour_seen` to whichever config directory the run is
|
||||
using, which is why the second run against the same one reads `auto=0`.
|
||||
- `UDPSCOPE_PROBE_TOUR_SURFACE_MS` — the tour's *surface* switch: it opens a slide-over page (`trigger`),
|
||||
starts the window's tour, and walks it a stop per tick to the signals stop, reading the sidebar's
|
||||
page and the spotlight/card where the walk arrives. It then puts the sidebar *away* and starts the
|
||||
tour again, which is the same filter's other case (a collapsed pane drops the stops a page switch
|
||||
could have revealed). `[toursurface] signals stop=4/5 … slideover=none … contains=1` and
|
||||
`[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0` are the two assertions; its
|
||||
starts the window's tour from that state and walks all six stops a stop per tick, reading the
|
||||
slide-over's open page and the spotlight/card at each. The trigger stop must read `slideover=trigger` and
|
||||
the next `slideover=none`; the first stop closes the panel the probe left open, and the end of the walk
|
||||
(and a dismissal on the trigger stop) leave none. It then puts the dock *away* and starts the tour again:
|
||||
the dock's stops are dropped while the trigger stop, which does not depend on the dock, stays.
|
||||
`[toursurface] stop=3/6 … slideover=trigger … contains=1` and
|
||||
`[toursurface] collapsed: side_box_mapped=0 steps=4 trigger_stop=1 signals_stop=0` are the assertions; its
|
||||
lines are in [probe-readings.md](probe-readings.md). Run it alone with
|
||||
`UDPSCOPE_EXIT_AFTER_MS=9000 UDPSCOPE_EXIT_DUMP=1` against a live source.
|
||||
- `UDPSCOPE_PROBE_DEFAULTS_MS` — opens **Calibrate** and reads every spin button in it and in the
|
||||
|
||||
+19
-3
@@ -73,9 +73,11 @@ enum class EventGroupKind {
|
||||
/// do not exist yet.
|
||||
struct TourStop {
|
||||
GtkWidget* target;
|
||||
/// The named page of the view's `page_stack` the target lives on, or nullptr when the stop needs no
|
||||
/// page. The tour selects it as the stop is entered, so a target on a page the user has left is
|
||||
/// made visible instead of being walked over or spotlighted where it is not.
|
||||
/// The page the target lives on, or nullptr when the stop needs none. What a page *is* depends on
|
||||
/// the view: a named page of its `page_stack` (the chooser), or whatever its `on_page` hook opens
|
||||
/// (the main window: a slide-over page id). The tour selects it as the stop is entered, so a target
|
||||
/// on a page the user has left is made visible instead of being walked over or spotlighted where it
|
||||
/// is not. A stop with no page *closes* what an `on_page` hook opened.
|
||||
const char* page;
|
||||
const char* title;
|
||||
const char* body;
|
||||
@@ -103,6 +105,16 @@ struct TourView {
|
||||
/// sidebar; the chooser's two pages), so it is the view's and not each stop's. Set where the walk
|
||||
/// begins, when the widgets exist; null leaves every stop on the page it finds.
|
||||
GtkWidget* page_stack = nullptr;
|
||||
/// The alternative to `page_stack` for a surface that is not a stack: when set it *replaces* the
|
||||
/// stack mechanism for this view, and is called with each stop's `page` as the stop is entered --
|
||||
/// `nullptr` meaning "no page needed" -- and with `nullptr` as the walk ends. The main window's opens
|
||||
/// and closes the slide-over through it; the chooser keeps `page_stack`.
|
||||
void (*on_page)(void* user, const char* page) = nullptr;
|
||||
void* page_user = nullptr;
|
||||
/// Optional, with `on_page`: whether the surface `on_page` opened has finished appearing. A stop that
|
||||
/// names a page is not spotlit while this answers false -- a panel that is still sliding in is a
|
||||
/// target that moves, and a spotlight that follows it for 120 ms reads as a glitch. Null means "ready".
|
||||
bool (*page_settled)(void* user) = nullptr;
|
||||
/// Runs when the walk passes its last stop (Next/Finish, never Skip): the chooser's hands the tour
|
||||
/// on to the application through `Config::tour_pending`.
|
||||
void (*on_done)(void* user) = nullptr;
|
||||
@@ -2114,6 +2126,10 @@ void tour_end(TourView& view);
|
||||
void tour_update(TourView& view);
|
||||
/// The widget the current stop points at, or nullptr when the walk is not on a stop.
|
||||
GtkWidget* tour_target(TourView& view);
|
||||
/// The part of `target` that is on screen, in `overlay`'s coordinates: its bounds intersected with every
|
||||
/// scrolled window above it. False when it cannot be measured yet or is scrolled out of sight entirely.
|
||||
/// The spotlight is drawn from it, and the probe reads the spotlight against it.
|
||||
bool tour_target_bounds(GtkWidget* target, GtkWidget* overlay, graphene_rect_t* out);
|
||||
|
||||
/// Builds the *window's* tour inside `app.tour.overlay`. Called once, as the overlay is inserted.
|
||||
void build_tour(App& app);
|
||||
|
||||
+67
-45
@@ -6657,8 +6657,8 @@ GtkWidget* helpui_menu_view(App* a) {
|
||||
}
|
||||
|
||||
/// One line: the spotlight, the widget it spotlights, and the card, all in **window coordinates** --
|
||||
/// the frame a screenshot is cropped in -- with the window's own record of the sidebar page beside
|
||||
/// them, so a stop that had to change the page to find its target is readable as such. `contains` is
|
||||
/// the frame a screenshot is cropped in -- with the slide-over's open page beside them (`none` when it
|
||||
/// is closed), so a stop that had to open a page to find its target is readable as such. `contains` is
|
||||
/// the spotlight covering its target (it is the target's own rectangle, grown three pixels) and
|
||||
/// `clear` is the card missing the target. `probe` is the tag it is printed under, so a window with
|
||||
/// more than one tour probe can tell its lines apart.
|
||||
@@ -6670,7 +6670,7 @@ void tour_geometry_line(App& a, const char* probe, const char* what) {
|
||||
const bool have_spot =
|
||||
a.tour.spot != nullptr && gtk_widget_compute_bounds(a.tour.spot, a.window, &spot);
|
||||
const bool have_target = t != nullptr && gtk_widget_get_mapped(t) &&
|
||||
gtk_widget_compute_bounds(t, a.window, &target);
|
||||
tour_target_bounds(t, a.window, &target);
|
||||
const bool have_card =
|
||||
a.tour.card != nullptr && gtk_widget_compute_bounds(a.tour.card, a.window, &card);
|
||||
const auto contains = [](const graphene_rect_t& outer, const graphene_rect_t& inner) {
|
||||
@@ -6859,6 +6859,10 @@ gboolean helpui_probe_step(gpointer user) {
|
||||
// The trigger pane's summary grid is on screen only in *locked* mode, and one of the help's
|
||||
// figures is that content: the probe reads `help figures:` in both states, so the geometry agent
|
||||
// has the locked pane's numbers without having to guess how to get there.
|
||||
// The trigger form is a slide-over page: it is open for both readings (and for the collapsed-search
|
||||
// one after them), because a closed panel leaves every one of its surfaces unmapped and reads
|
||||
// `-1,-1,0,0`. The 400 ms of the step is past the 120 ms reveal.
|
||||
if (step == 6) slideover_open(*a, "trigger");
|
||||
if (a->trigger_edit != nullptr) g_signal_emit_by_name(a->trigger_edit, "clicked");
|
||||
helpui_probe_next(*a, step == 6 ? 7 : 9, 400);
|
||||
return G_SOURCE_REMOVE;
|
||||
@@ -6891,6 +6895,7 @@ gboolean helpui_probe_step(gpointer user) {
|
||||
hint != nullptr && gtk_widget_get_mapped(hint) ? 1 : 0,
|
||||
gtk_widget_get_mapped(a->trigger_search) ? 1 : 0, sw, sh);
|
||||
}
|
||||
slideover_close(*a); // opened by step 6
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
@@ -6898,7 +6903,7 @@ gboolean helpui_probe_step(gpointer user) {
|
||||
// --- the tour's surface switch: a stop that stands on another stack page -------
|
||||
|
||||
/// Whether the walk's plan holds `target`. `steps=` alone cannot say a stop was *filtered out* rather
|
||||
/// than merely reordered, so the two cases the filter tells apart are read against the plan itself.
|
||||
/// than merely reordered, so the cases the filter tells apart are read against the plan itself.
|
||||
bool tour_plan_holds(const App& a, GtkWidget* target) {
|
||||
for (const TourStop& stop : a.tour.plan) {
|
||||
if (stop.target == target) return true;
|
||||
@@ -6912,27 +6917,34 @@ void tour_surface_probe_next(App& a, guint delay_ms) {
|
||||
g_timeout_add(delay_ms, tour_surface_probe_step, &a);
|
||||
}
|
||||
|
||||
/// `UDPSCOPE_PROBE_TOUR_SURFACE_MS`: the stop that stands on a page it is not on. It puts the sidebar
|
||||
/// on **Measure**, starts the window's tour, and walks it a stop per tick -- a tick apart on purpose,
|
||||
/// because a stack's new page is allocated on the frame *after* the switch, so a reading taken in the
|
||||
/// switch's own tick would be of the page the walk has just left (and `update_tour()` places the hole
|
||||
/// from the frame that *has* been laid out). Every step prints the sidebar's page and whether the
|
||||
/// stop's target is mapped, and the stop whose target is the signal list prints the same geometry
|
||||
/// `[helpui]` does: **`side_tab=signals` with `target=signal_list` and `contains=1`** is the reading
|
||||
/// that the stop was not merely kept but *made* visible. It then puts the sidebar away --
|
||||
/// `sidebar_wanted` is the wish the tick re-asserts -- and starts the tour again: a *collapsed* pane
|
||||
/// leaves the stack unmapped, so the stops that need it are dropped and `steps=` falls. That pair is
|
||||
/// the filter's two cases, read rather than assumed.
|
||||
/// The slide-over's open page for a reading: its id, or `none`.
|
||||
const char* slideover_name(const App& a) {
|
||||
return slideover_page(a).empty() ? "none" : slideover_page(a).c_str();
|
||||
}
|
||||
|
||||
/// `UDPSCOPE_PROBE_TOUR_SURFACE_MS`: the stop that opens a surface it is not on. It opens the
|
||||
/// slide-over's `trigger` page, starts the window's tour from that state, and walks the whole table a
|
||||
/// stop per tick -- a tick apart on purpose (400 ms, longer than the panel's 120 ms slide), because a
|
||||
/// reading taken in the switch's own tick would be of the surface the walk has just left (and
|
||||
/// `update_tour()` places the hole from the frame that *has* been laid out). Every stop prints its
|
||||
/// title, the slide-over's open page and whether the target is mapped, with the spotlight geometry
|
||||
/// `[helpui]` prints: **`slideover=trigger` at the trigger stop and `slideover=none` at the one after
|
||||
/// it, with `contains=1`**, is the reading that the walk opened the panel and then closed it. The walk
|
||||
/// then runs again and is dismissed on the trigger stop (the Skip/Esc route), which must close the
|
||||
/// panel too. It finally puts the dock *away* -- `sidebar_wanted` is the wish the tick re-asserts --
|
||||
/// and starts the tour once more: the dock's stops are dropped (a collapsed pane is not walked over)
|
||||
/// while the trigger stop, which does not depend on the dock, is kept.
|
||||
gboolean tour_surface_probe_step(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
const auto mapped = [](GtkWidget* w) { return w != nullptr && gtk_widget_get_mapped(w) ? 1 : 0; };
|
||||
switch (a->dbg_tour_surface_step) {
|
||||
case 0: {
|
||||
// Where the run starts: a fresh config's sidebar is on Signals, so this is a *first* run's
|
||||
// state. The auto-start's walk (if one began) would only confuse the reading; it goes first.
|
||||
// Where the run starts: the panel closed and the dock on screen. The auto-start's walk (if one
|
||||
// began) would only confuse the reading; it goes first. The panel is then opened, so the walk's
|
||||
// first stop (which needs no page) has something to close.
|
||||
std::fprintf(stderr, "[toursurface] before: slideover=%s signal_list_mapped=%d running=%d\n",
|
||||
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(), mapped(a->signal_list), tour_active(*a) ? 1 : 0);
|
||||
slideover_name(*a), mapped(a->signal_list), tour_active(*a) ? 1 : 0);
|
||||
tour_dismiss(*a);
|
||||
slideover_open(*a, "trigger");
|
||||
a->dbg_tour_surface_step = 1;
|
||||
@@ -6940,15 +6952,16 @@ gboolean tour_surface_probe_step(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
case 1: {
|
||||
// A frame after the switch, so the Measure page has been laid out and the list really is off
|
||||
// screen. The tour is started *here*, from this state.
|
||||
// A frame after the page opened, so the panel has slid in. The tour is started *here*, from this
|
||||
// state.
|
||||
std::fprintf(stderr,
|
||||
"[toursurface] on trigger: slideover=%s signal_list_mapped=%d side_box_mapped=%d\n",
|
||||
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(),
|
||||
mapped(a->signal_list), mapped(a->side_box));
|
||||
"[toursurface] on trigger: slideover=%s trigger_body_mapped=%d side_box_mapped=%d\n",
|
||||
slideover_name(*a), mapped(a->trigger_body), mapped(a->side_box));
|
||||
start_tour(*a);
|
||||
std::fprintf(stderr, "[toursurface] tour: visible=%d steps=%d step=%d signals_stop=%d\n",
|
||||
std::fprintf(stderr,
|
||||
"[toursurface] tour: visible=%d steps=%d step=%d trigger_stop=%d signals_stop=%d\n",
|
||||
tour_active(*a) ? 1 : 0, tour_step_count(*a), tour_step_index(*a),
|
||||
tour_plan_holds(*a, a->trigger_body) ? 1 : 0,
|
||||
tour_plan_holds(*a, a->signal_list) ? 1 : 0);
|
||||
a->dbg_tour_surface_step = 2;
|
||||
tour_surface_probe_next(*a, 400);
|
||||
@@ -6956,44 +6969,53 @@ gboolean tour_surface_probe_step(gpointer user) {
|
||||
}
|
||||
case 2: {
|
||||
if (!tour_active(*a)) {
|
||||
std::fprintf(stderr, "[toursurface] walk ended before the signals stop\n");
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
GtkWidget* target = tour_target(*a);
|
||||
const bool is_list = target != nullptr && target == a->signal_list;
|
||||
std::fprintf(stderr, "[toursurface] step=%d/%d slideover=%s target_mapped=%d%s\n",
|
||||
tour_step_index(*a) + 1, tour_step_count(*a),
|
||||
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(), mapped(target),
|
||||
is_list ? " target=signal_list" : "");
|
||||
if (is_list) {
|
||||
// The frame after the switch: the page has been laid out and `update_tour()` has placed the
|
||||
// hole from it, so this is the reading that the spotlight is on the list.
|
||||
tour_geometry_line(*a, "toursurface", "signals stop");
|
||||
tour_dismiss(*a);
|
||||
// The last Next ended the walk: the panel must have gone with it.
|
||||
std::fprintf(stderr, "[toursurface] finished: running=0 slideover=%s\n", slideover_name(*a));
|
||||
start_tour(*a);
|
||||
while (tour_active(*a) && tour_target(*a) != a->trigger_body) tour_step(*a, 1);
|
||||
a->dbg_tour_surface_step = 3;
|
||||
tour_surface_probe_next(*a, 400);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
GtkWidget* target = tour_target(*a);
|
||||
const TourStop& stop = a->tour.plan[static_cast<std::size_t>(tour_step_index(*a))];
|
||||
std::fprintf(stderr, "[toursurface] step=%d/%d title='%s' slideover=%s target_mapped=%d\n",
|
||||
tour_step_index(*a) + 1, tour_step_count(*a), stop.title, slideover_name(*a),
|
||||
mapped(target));
|
||||
// The frame after the switch: the page has been laid out and `update_tour()` has placed the
|
||||
// hole from it, so this is the reading that the spotlight is on the stop's target.
|
||||
tour_geometry_line(*a, "toursurface", "stop");
|
||||
tour_step(*a, 1);
|
||||
tour_surface_probe_next(*a, 400);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
case 3: {
|
||||
// The other half of the filter. `sidebar_wanted` is the user's wish that the tick re-asserts
|
||||
// (`update_side_visibility`), so setting it here is how a probe puts the sidebar away for good;
|
||||
// the delay leaves the split view time to animate the column shut and unmap it.
|
||||
a->sidebar_wanted = false;
|
||||
// The walk again, parked on the trigger stop with the panel open: Skip (or Esc) closes it.
|
||||
std::fprintf(stderr, "[toursurface] restarted at trigger: running=%d slideover=%s\n",
|
||||
tour_active(*a) ? 1 : 0, slideover_name(*a));
|
||||
tour_dismiss(*a);
|
||||
std::fprintf(stderr, "[toursurface] skipped: running=%d slideover=%s\n",
|
||||
tour_active(*a) ? 1 : 0, slideover_name(*a));
|
||||
a->dbg_tour_surface_step = 4;
|
||||
tour_surface_probe_next(*a, 100);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
case 4: {
|
||||
// The other half of the filter. `sidebar_wanted` is the user's wish that the tick re-asserts
|
||||
// (`update_side_visibility`), so setting it here is how a probe puts the dock away for good; the
|
||||
// delay leaves the split view time to animate the column shut and unmap it.
|
||||
a->sidebar_wanted = false;
|
||||
a->dbg_tour_surface_step = 5;
|
||||
tour_surface_probe_next(*a, 700);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
default: {
|
||||
start_tour(*a);
|
||||
std::fprintf(stderr,
|
||||
"[toursurface] collapsed: side_box_mapped=%d steps=%d signals_stop=%d "
|
||||
"side_stop=%d\n",
|
||||
"[toursurface] collapsed: side_box_mapped=%d steps=%d trigger_stop=%d "
|
||||
"signals_stop=%d side_stop=%d\n",
|
||||
mapped(a->side_box), tour_step_count(*a),
|
||||
tour_plan_holds(*a, a->trigger_body) ? 1 : 0,
|
||||
tour_plan_holds(*a, a->signal_list) ? 1 : 0,
|
||||
tour_plan_holds(*a, a->side_box) ? 1 : 0);
|
||||
tour_dismiss(*a);
|
||||
|
||||
+101
-28
@@ -16,9 +16,9 @@
|
||||
// (see the allocation bullets in `docs/claude/invariants-trigger-and-pinning.md`), and a window
|
||||
// resize moves it under a spotlight that a one-shot measurement would leave behind.
|
||||
//
|
||||
// There are *two* tours and one implementation. The main window's walks the plot, the side panel,
|
||||
// the trigger and the like; the source chooser's -- which appears *before* `App` exists -- walks the
|
||||
// chooser's own widgets and then hands the tour on to the window through `Config::tour_pending`. The
|
||||
// There are *two* tours and one implementation. The main window's walks the plot, the badge bar, the
|
||||
// trigger (in the slide-over), the dock and the like; the source chooser's -- which appears *before*
|
||||
// `App` exists -- walks the chooser's own widgets and then hands the tour on to the window through `Config::tour_pending`. The
|
||||
// machinery below is written against a `TourView` and a list of `TourStop`s, so the difference
|
||||
// between the two is only which widgets they name: the chooser keeps its table in
|
||||
// `source_dialog.cpp`, where its widgets live.
|
||||
@@ -76,12 +76,18 @@ const TourStop* current_stop(const TourView& view) {
|
||||
return &view.plan[static_cast<std::size_t>(view.index)];
|
||||
}
|
||||
|
||||
/// Selects a stop's page on the view's stack, before anything is measured against the target. Both
|
||||
/// windows' stacks are `AdwViewStack`s and their page names are the ones the same tables name their
|
||||
/// `.ui` pages with, so the machinery can do this itself; the `get_child_by_name()` guard keeps a
|
||||
/// stop that names a page a later `.ui` dropped from blanking the stack. The stack's own
|
||||
/// `notify::visible-child-name` is what tells the window which page it is now on.
|
||||
/// Selects a stop's page before anything is measured against the target. Two mechanisms, one per
|
||||
/// kind of view: a view with an `on_page` hook (the main window: its pages are slide-over pages, and
|
||||
/// a stop with no page *closes* the panel) hands the page to the hook; a view with a `page_stack` (the
|
||||
/// source chooser: an `AdwViewStack` whose page names are the ones its `.ui` names its pages with)
|
||||
/// has the machinery select it. The `get_child_by_name()` guard keeps a stop that names a page a
|
||||
/// later `.ui` dropped from blanking the stack. The stack's own `notify::visible-child-name` is what
|
||||
/// tells the window which page it is now on.
|
||||
void select_view_page(TourView& view, const char* page) {
|
||||
if (view.on_page != nullptr) {
|
||||
view.on_page(view.page_user, page);
|
||||
return;
|
||||
}
|
||||
if (view.page_stack == nullptr || page == nullptr) return;
|
||||
if (adw_view_stack_get_child_by_name(ADW_VIEW_STACK(view.page_stack), page) == nullptr) return;
|
||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(view.page_stack), page);
|
||||
@@ -91,13 +97,16 @@ void select_view_page(TourView& view, const char* page) {
|
||||
/// pages, and the split view its sidebar, with `gtk_widget_set_child_visible()`, which leaves the
|
||||
/// widget's own visible flag -- the one `is_visible()` walks up the parents -- **true**. So the only
|
||||
/// honest "is this on screen" test is whether it is mapped, and it is the one that keeps the filter's
|
||||
/// two cases apart:
|
||||
/// cases apart:
|
||||
/// * a stop that names a page of a view with an `on_page` hook is always walkable: the hook opens
|
||||
/// the page whatever the rest of the window is doing (the slide-over does not depend on the dock);
|
||||
/// * a target not mapped because it is on *another page* is walked over when the stop names that
|
||||
/// page and the stack itself is on screen -- selecting the page is what puts the target there;
|
||||
/// * a target not mapped because its *pane is collapsed* leaves the stack unmapped too, so no page
|
||||
/// can reveal it and the stop is still dropped.
|
||||
bool stop_is_walkable(const TourView& view, const TourStop& stop) {
|
||||
if (stop.target == nullptr) return false;
|
||||
if (stop.page != nullptr && view.on_page != nullptr) return true;
|
||||
if (gtk_widget_get_mapped(stop.target)) return true;
|
||||
return stop.page != nullptr && view.page_stack != nullptr &&
|
||||
gtk_widget_get_mapped(view.page_stack);
|
||||
@@ -130,6 +139,25 @@ void show_step(TourView& view) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool tour_target_bounds(GtkWidget* target, GtkWidget* overlay, graphene_rect_t* out) {
|
||||
if (target == nullptr || overlay == nullptr || out == nullptr) return false;
|
||||
if (!gtk_widget_compute_bounds(target, overlay, out)) return false;
|
||||
// A target taller than the scroller it stands in (a form on a short window) is clipped by that
|
||||
// scroller: the part below its edge is not on screen, and a hole that kept going would spotlight
|
||||
// whatever is under the scroller instead -- the badge bar, the status line. So the hole is the
|
||||
// *visible* part, intersected with every scrolled window above the target.
|
||||
for (GtkWidget* parent = gtk_widget_get_parent(target); parent != nullptr;
|
||||
parent = gtk_widget_get_parent(parent)) {
|
||||
if (!GTK_IS_SCROLLED_WINDOW(parent)) continue;
|
||||
graphene_rect_t clip;
|
||||
graphene_rect_t cut;
|
||||
if (!gtk_widget_compute_bounds(parent, overlay, &clip)) continue;
|
||||
if (!graphene_rect_intersection(out, &clip, &cut)) return false;
|
||||
*out = cut;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- the machinery, shared by both windows ----------------------------------
|
||||
|
||||
void tour_build(TourView& view, GtkWidget* overlay) {
|
||||
@@ -241,6 +269,10 @@ void tour_advance(TourView& view, int delta) {
|
||||
}
|
||||
|
||||
void tour_end(TourView& view) {
|
||||
// A page the walk opened is the walk's to close: the panel must not be left standing over a window
|
||||
// whose tour has ended (Skip, Esc, or the last Next). Only when a walk was actually running --
|
||||
// dismissing an idle tour must not close a panel the user opened.
|
||||
if (view.running && view.on_page != nullptr) view.on_page(view.page_user, nullptr);
|
||||
view.running = false;
|
||||
view.index = 0;
|
||||
view.plan.clear();
|
||||
@@ -271,8 +303,15 @@ void tour_update(TourView& view) {
|
||||
// Mapped, not `is_visible()`: a widget on another page of a stack, or in a collapsed pane, keeps
|
||||
// its own visible flag -- a stack hides its pages with `set_child_visible()` -- and a widget that
|
||||
// is not on screen is not one to leave the spotlight standing on.
|
||||
const bool target_present = target != nullptr && gtk_widget_get_mapped(target);
|
||||
if (target_present && gtk_widget_compute_bounds(target, view.overlay, &bounds)) {
|
||||
bool target_present = target != nullptr && gtk_widget_get_mapped(target);
|
||||
// A stop on a page waits for the page to finish appearing: until then the target is moving, and
|
||||
// the frame is drawn as a stop with nothing to point at (everything dim, the hole empty).
|
||||
const TourStop* stop = current_stop(view);
|
||||
if (target_present && stop != nullptr && stop->page != nullptr && view.page_settled != nullptr &&
|
||||
!view.page_settled(view.page_user)) {
|
||||
target_present = false;
|
||||
}
|
||||
if (target_present && tour_target_bounds(target, view.overlay, &bounds)) {
|
||||
// The hole: the target, grown by three pixels so the outline sits *around* the widget rather than
|
||||
// on it, and clamped to the overlay -- a target partly off screen is an ordinary case (a sidebar
|
||||
// dragged wide), and a negative rectangle is not something a margin can express.
|
||||
@@ -354,6 +393,13 @@ void tour_update(TourView& view) {
|
||||
} else if (hx >= cw + 2 * kGap) {
|
||||
cx = hx - cw - kGap;
|
||||
cy = std::clamp(hy, kGap, std::max(kGap, h - ch - kGap));
|
||||
} else if (hx > w - (hx + hw) + kGap) {
|
||||
// No side wide enough for the card, but the hole is at the right edge (the slide-over's trigger
|
||||
// form on a narrow window: 276 px to its left against a 289 px card): the card goes flush left,
|
||||
// overlapping the plot and as little of the target as there is no way to avoid, instead of
|
||||
// sitting on the target's own corner.
|
||||
cx = kGap;
|
||||
cy = std::clamp(hy, kGap, std::max(kGap, h - ch - kGap));
|
||||
} else {
|
||||
// No clear side at all -- the target is nearly the whole window, which is the plot's step in a
|
||||
// window narrow enough for the split view to *overlay* the side panel rather than put it beside
|
||||
@@ -374,9 +420,9 @@ namespace {
|
||||
/// point of re-measuring every frame is that nothing here knows where the widget is.
|
||||
struct TourStep {
|
||||
const char* id;
|
||||
/// The sidebar page the target lives on, or nullptr when the stop is not on either page. The
|
||||
/// trigger and the channel list are both on the Signals page, so a stop about either has to put
|
||||
/// the sidebar there before it can spotlight one.
|
||||
/// The slide-over page the target lives on, or nullptr when the stop needs none. A stop with a page
|
||||
/// opens it; a stop without one closes the panel, so the card never points at the status line over
|
||||
/// an open panel. The page ids are the slide-over's (`trigger`, `source-<id>`, ...).
|
||||
const char* page;
|
||||
const char* title;
|
||||
const char* body;
|
||||
@@ -388,15 +434,20 @@ constexpr TourStep kTourSteps[] = {
|
||||
"Every signal you have switched on is drawn here, all rows sharing one time axis. The wheel "
|
||||
"zooms, a drag pans, and a right-click on a row opens that plot's own menu.",
|
||||
&App::area_overlay},
|
||||
{"side", nullptr, "The side panel",
|
||||
"The working surface beside the plot. Measure holds the cursors and the measurements; Signals "
|
||||
"holds the trigger and the list of signals. The buttons in the bar above show and hide it.",
|
||||
&App::side_box},
|
||||
{"trigger", "signals", "The trigger",
|
||||
{"badges", nullptr, "The badges",
|
||||
"Setup is reached by the thing it configures. The Trigger badge opens the trigger form; the "
|
||||
"Source badge opens the source's own controls. Click it again, press Esc or click the plot to "
|
||||
"close the panel.",
|
||||
&App::badge_bar},
|
||||
{"trigger", "trigger", "The trigger",
|
||||
"Describe an event -- a signal, a condition, a level and the window around it -- and arm it: "
|
||||
"the view then freezes on the first event that matches, however rare it is.",
|
||||
&App::trigger_body},
|
||||
{"signals", "signals", "The signals",
|
||||
{"side", nullptr, "The dock",
|
||||
"The dock beside the plot: the list of signals, then the cursors and the measurements. The "
|
||||
"buttons in the bar above show and hide it.",
|
||||
&App::side_box},
|
||||
{"signals", nullptr, "The signals",
|
||||
"Every signal the source declares, each with the switch that acquires it. A signal has to be "
|
||||
"switched on before it can be plotted, measured or searched.",
|
||||
&App::signal_list},
|
||||
@@ -407,6 +458,23 @@ constexpr TourStep kTourSteps[] = {
|
||||
};
|
||||
constexpr int kTourStepCount = static_cast<int>(sizeof(kTourSteps) / sizeof(kTourSteps[0]));
|
||||
|
||||
/// The tour's page hook: a page is a slide-over page, and no page closes the panel.
|
||||
void tour_open_page(void* user, const char* page) {
|
||||
App& app = *static_cast<App*>(user);
|
||||
if (page != nullptr) {
|
||||
slideover_open(app, page);
|
||||
} else {
|
||||
slideover_close(app);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the slide-over has finished sliding in. A closed panel has nothing to wait for.
|
||||
bool tour_page_settled(void* user) {
|
||||
const App& app = *static_cast<App*>(user);
|
||||
if (app.slideover.revealer == nullptr || app.slideover.page.empty()) return true;
|
||||
return gtk_revealer_get_child_revealed(GTK_REVEALER(app.slideover.revealer)) != FALSE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void build_tour(App& app) {
|
||||
@@ -422,9 +490,12 @@ void start_tour(App& app) {
|
||||
app.settings.tour_seen = true;
|
||||
(void)save_settings(app.settings);
|
||||
}
|
||||
// The main window has no page stack any more (the dock is one scroller): `page_stack` stays null, so
|
||||
// a stop's `page` selects nothing and a stop whose target is unmapped is dropped. The slide-over hook
|
||||
// that lets a stop open the trigger page is the next step's.
|
||||
// The main window has no page stack (the dock is one scroller): its pages are the slide-over's, and
|
||||
// the view's `on_page` hook opens one (or closes the panel for a stop with no page), replacing the
|
||||
// `page_stack` mechanism the chooser keeps.
|
||||
app.tour.on_page = tour_open_page;
|
||||
app.tour.page_settled = tour_page_settled;
|
||||
app.tour.page_user = &app;
|
||||
// The whole table, in walk order: `tour_begin()` drops the stops whose surface is not on screen.
|
||||
std::vector<TourStop> stops;
|
||||
stops.reserve(kTourStepCount);
|
||||
@@ -484,7 +555,8 @@ constexpr FigureSurface kFigureSurfaces[] = {
|
||||
{"trigger_summary", &App::trigger_summary},
|
||||
{"trigger_panel", &App::trigger_panel},
|
||||
{"signal_list", &App::signal_list},
|
||||
{"side_stack", &App::side_box},
|
||||
{"badge_bar", &App::badge_bar},
|
||||
{"dock", &App::side_box},
|
||||
{"window_button", &App::window_button},
|
||||
{"status_label", &App::status_label},
|
||||
{"header_bar", &App::toolbar},
|
||||
@@ -526,10 +598,11 @@ void capture_help_state(App& app) {
|
||||
GtkWidget* widget = app.*(surface.widget);
|
||||
graphene_rect_t r = GRAPHENE_RECT_INIT(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
int x = -1, y = -1, width = 0, height = 0;
|
||||
// `is_visible()`, not `get_visible()`: a widget under a hidden parent -- the trigger summary
|
||||
// while the pane is in configuration mode, the Measure page while the Signals tab is showing --
|
||||
// keeps its own flag true and reads 0x0, which a crop box would take for a real rectangle.
|
||||
if (widget != nullptr && gtk_widget_is_visible(widget) &&
|
||||
// `is_visible()` and `get_mapped()`, not `get_visible()`: a widget under a hidden parent -- the
|
||||
// trigger summary while the pane is in configuration mode -- keeps its own flag true and reads
|
||||
// 0x0, and so does everything on the slide-over while the panel is closed (a revealer unmaps its
|
||||
// child without touching its visible flag); a crop box would take either for a real rectangle.
|
||||
if (widget != nullptr && gtk_widget_is_visible(widget) && gtk_widget_get_mapped(widget) &&
|
||||
gtk_widget_compute_bounds(widget, app.window, &r)) {
|
||||
x = static_cast<int>(std::lround(r.origin.x));
|
||||
y = static_cast<int>(std::lround(r.origin.y));
|
||||
|
||||
Reference in new issue
Block a user