help: a tinier (i), hints beside their controls, and the tour starting in the chooser
The contextual help becomes the affordance the ask names: a plain (i) (`dialog-information-symbolic`) at **16x16**, and one that sits beside the control it explains rather than only in a pane's heading. - The mark: `GtkMenuButton` ignores `min-width` on the widget and a size request, so the size is made in `install_css()` on all three nodes (outer `menubutton`, its `button` subnode, the glyph). Measured 16x16 against the default 34x34; the dump prints it as `hint=WxH`, measured with `gtk_widget_compute_bounds()` on the first *visible* hint. - More sites: `events_record` (the "record events" switch in the trigger panel), `trigger_advanced` (the pre-trigger share row), `signals_config` (the signals pane's hint line, wrapped in a box so a hint can join it). Hints join as siblings, so none can cover a switch, pencil or dropdown. - The source chooser gets its own layer -- `chooser_sources` (a new heading above the saved-source list) and `chooser_waveform` (the waveforms action row) -- built with the one `context_help_button()` both windows use and re-synced by its Help menu's toggle. The form's `AdwEntryRow`s cannot host a sibling hint, so none is invented for the host/port fields. - The tour is one implementation over two windows: `ui_impl::TourView` plus a list of `TourStop`s; each window keeps its own table. A walk that passes its last stop calls `on_done`, and the chooser's sets `Config::tour_pending`, so a first run is one tour that crosses from the chooser into the app. The chooser's tour auto-starts from `settings.tour_seen` exactly as the window's does, and its "Guided tour" row now starts *it* instead of only requesting the window's. - Probes: `UDPSCOPE_PROBE_CHOOSER_TOUR=1` / `UDPSCOPE_PROBE_CHOOSER_TOUR_MS`, the hint size in the `help layer:`/`[helpui] contextual on:` lines, and the chooser's hints in its `chooser ui:` line. Docs updated in invariants-gtk-and-chrome.md, probe-readings.md and verification.md.
This commit is contained in:
1 parent
395c9caec3
commit
829176248a
14 files changed
+911
-303
No files matched your search
+89
-25
@@ -57,6 +57,48 @@ enum class EventGroupKind {
|
||||
Compare, ///< two events in two plots side by side, at one scale, with synchronised axes
|
||||
Accumulate, ///< up to four events overlaid in the same traces, translucent, so agreement is brightness
|
||||
};
|
||||
|
||||
// --- the guided tour, shared by both windows (ui_tour.cpp) -------------------
|
||||
//
|
||||
// The tour is *two* tours now -- the main window's and the source chooser's -- because the chooser is
|
||||
// the window a first run meets first and the surfaces it must explain are its own. The machinery is
|
||||
// one implementation: a `TourView` is the overlay children (the four dim rectangles, the spotlight
|
||||
// outline and the card) plus where the walk stands in it, and either window holds one. What differs
|
||||
// is only the *stops*: a stop's target is a widget the owner resolves, so each window keeps its own
|
||||
// table (the chooser runs before `App` exists and could not name one of `App`'s fields).
|
||||
|
||||
/// One stop of a guided tour: the widget to spotlight, and the two paragraphs of text beside it.
|
||||
/// The target is resolved when the tour starts, so the table may name widgets that do not exist yet.
|
||||
struct TourStop {
|
||||
GtkWidget* target;
|
||||
const char* title;
|
||||
const char* body;
|
||||
};
|
||||
|
||||
/// A tour's widgets and its place in the walk. `hole` is the spotlight's rectangle in the overlay's
|
||||
/// own coordinates, kept from the last frame that could measure the target: a frame can come between
|
||||
/// two layout passes, where `gtk_widget_compute_bounds()` answers nothing, and a hole that emptied
|
||||
/// there would make the spotlight and the card jump.
|
||||
struct TourView {
|
||||
GtkWidget* overlay = nullptr;
|
||||
GtkWidget* dim[4] = {};
|
||||
GtkWidget* spot = nullptr;
|
||||
GtkWidget* card = nullptr;
|
||||
GtkWidget* title = nullptr;
|
||||
GtkWidget* body = nullptr;
|
||||
GtkWidget* counter = nullptr;
|
||||
GtkWidget* back = nullptr;
|
||||
GtkWidget* next = nullptr;
|
||||
int hole[4] = {0, 0, 0, 0};
|
||||
bool running = false;
|
||||
int index = 0;
|
||||
std::vector<TourStop> plan;
|
||||
/// 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;
|
||||
void* done_user = nullptr;
|
||||
};
|
||||
|
||||
struct App {
|
||||
ScopeModel* model = nullptr;
|
||||
Acquisition* acq = nullptr;
|
||||
@@ -232,26 +274,17 @@ struct App {
|
||||
/// are not a panel with a heading.
|
||||
GtkWidget* status_row = nullptr;
|
||||
GtkWidget* window_control_slot = nullptr;
|
||||
/// The guided tour. `tour_overlay` is a `GtkOverlay` inserted above the window's content, and the
|
||||
/// four dim rectangles, the spotlight and the card are its overlay children. `tour_plan` is the
|
||||
/// The hint hosts that are *not* a pane's heading row: a specific control's own row, so the "?" sits
|
||||
/// beside the thing it explains -- the "record events" switch, the search block's action row, and the
|
||||
/// signals pane's hint line (whose box exists so a hint can join it beside the text).
|
||||
GtkWidget* trigger_record_row = nullptr;
|
||||
GtkWidget* trigger_pre_row = nullptr;
|
||||
GtkWidget* signals_note = nullptr;
|
||||
/// The guided tour. `tour.overlay` is a `GtkOverlay` inserted above the window's content, and the
|
||||
/// four dim rectangles, the spotlight and the card are its overlay children; `tour.plan` is the
|
||||
/// steps whose target was on screen when the tour started, so a surface that is not there is not
|
||||
/// walked over. `tour_hole` is the spotlight's rectangle in the overlay's own coordinates, kept
|
||||
/// from the last frame that could measure the target: a frame can come between two layout passes,
|
||||
/// where `gtk_widget_compute_bounds()` answers nothing, and a hole that emptied there would make
|
||||
/// the spotlight and the card jump for that frame.
|
||||
GtkWidget* tour_overlay = nullptr;
|
||||
GtkWidget* tour_dim[4] = {};
|
||||
GtkWidget* tour_spot = nullptr;
|
||||
GtkWidget* tour_card = nullptr;
|
||||
GtkWidget* tour_title = nullptr;
|
||||
GtkWidget* tour_body = nullptr;
|
||||
GtkWidget* tour_counter = nullptr;
|
||||
GtkWidget* tour_back = nullptr;
|
||||
GtkWidget* tour_next = nullptr;
|
||||
int tour_hole[4] = {0, 0, 0, 0};
|
||||
bool tour_running = false;
|
||||
int tour_index = 0;
|
||||
std::vector<int> tour_plan;
|
||||
/// walked over. The machinery is shared with the source chooser's tour -- see `TourView`.
|
||||
TourView tour;
|
||||
/// The first-run auto-start: set by the timer that fires it, and read by the probe and the dump.
|
||||
/// The decision itself is `settings.tour_seen`, which is the whole of "this has happened before".
|
||||
bool tour_auto_started = false;
|
||||
@@ -1732,19 +1765,50 @@ void on_contextual_help_toggled(GtkToggleButton* button, gpointer user);
|
||||
/// The "?" for one surface by id, for a host that only exists while a dialog is open (the math
|
||||
/// editor is rebuilt on every open, so it cannot be a row the window's own build visits).
|
||||
void context_help_add(App& app, const char* id, GtkWidget* host);
|
||||
/// The same affordance for a surface that is *not* in `App`'s table -- the source chooser's, which
|
||||
/// runs before `App` exists and so cannot name one of its fields. `host` is a `GtkBox` and the button
|
||||
/// joins it as its last child, which is the rule the table's own rows follow; `on` false takes any
|
||||
/// hint off `host` instead. `shortcut` may be "".
|
||||
void context_help_button(GtkWidget* host, const char* id, const char* title, const char* body,
|
||||
const char* shortcut, bool on);
|
||||
/// How many hints a widget subtree carries, and their ids, for a window that has no `App` (the
|
||||
/// chooser's probe). `context_help_count()` is this with `App::window` as the root.
|
||||
int context_help_count_in(GtkWidget* root, std::string* ids);
|
||||
/// The size of the first *visible* hint button in a subtree, read the way the probe's geometry is
|
||||
/// (`gtk_widget_compute_bounds()`), and the number a shrinking affordance is checked against. Both
|
||||
/// are 0 when the layer is off or nothing on screen carries one.
|
||||
void context_help_size_in(GtkWidget* root, int* width, int* height);
|
||||
/// `context_help_size_in()` with `App::window` as the root.
|
||||
void context_help_size(App& app, int* width, int* height);
|
||||
|
||||
/// Builds the tour's overlay children (the four dim rectangles, the spotlight outline and the card)
|
||||
/// inside `app.tour_overlay`. Called once, as the overlay is inserted.
|
||||
/// Builds a tour's overlay children (the four dim rectangles, the spotlight outline and the card)
|
||||
/// inside `overlay`. Called once, as the overlay is inserted, by either window that draws a tour.
|
||||
void tour_build(TourView& view, GtkWidget* overlay);
|
||||
/// Starts at the first stop. `stops` is the walk, already filtered to what is on screen; `on_done`
|
||||
/// (may be null) runs when the walk passes its last stop, and is how the chooser hands off.
|
||||
void tour_begin(TourView& view, std::vector<TourStop> stops, void (*on_done)(void* user),
|
||||
void* done_user);
|
||||
/// Moves `delta` stops, finishing the walk (and calling `on_done`) on the way past the last one.
|
||||
void tour_advance(TourView& view, int delta);
|
||||
/// Ends the walk without finishing it: every child of the overlay is hidden and nothing is left on
|
||||
/// screen. Skip and Escape.
|
||||
void tour_end(TourView& view);
|
||||
/// Called from the tick while the walk runs: re-measures the target each frame, so a window resize
|
||||
/// keeps the spotlight on the widget, and places the card where it does not cover it.
|
||||
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);
|
||||
|
||||
/// Builds the *window's* tour inside `app.tour.overlay`. Called once, as the overlay is inserted.
|
||||
void build_tour(App& app);
|
||||
/// Starts the tour at its first step and writes `tour_seen`. Safe to call while it already runs --
|
||||
/// it restarts at step 0.
|
||||
/// Starts the window's tour at its first step and writes `tour_seen`. Safe to call while it already
|
||||
/// runs -- it restarts at step 0.
|
||||
void start_tour(App& app);
|
||||
/// Moves `delta` steps, ending the tour on the way past the last one.
|
||||
void tour_step(App& app, int delta);
|
||||
/// Ends the tour: every child of the overlay is hidden and nothing of the tour is left on screen.
|
||||
void tour_dismiss(App& app);
|
||||
/// Called from the tick while the tour runs: re-measures the target each frame, so a window resize
|
||||
/// keeps the spotlight on the widget, and places the card where it does not cover it.
|
||||
/// The window's tour, re-measured each frame.
|
||||
void update_tour(App& app);
|
||||
bool tour_active(const App& app);
|
||||
/// The widget the current tour step points at, or nullptr when the tour is not on a step. The probe
|
||||
|
||||
Reference in new issue
Block a user