Feature/source plugins #1
No files matched your search
@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
udpscope is a **GTK4 oscilloscope-style viewer for a UDPStreamer source**. It receives one
|
||||
data source -- a UDPStreamer stream through `deps/udpsclient` by default, or another registered
|
||||
source kind (see `docs/claude/architecture.md`) -- buffers it in a large ring (default 10 M samples per signal, under an 8 GiB memory limit), and draws it
|
||||
with `deps/plotter` — a figure of up to eight stacked rows sharing one x window. On top of
|
||||
with `deps/plotter` — a figure of up to eight plots in stacked rows (a row can be split into up to four side by side) sharing one x window. On top of
|
||||
the live view it provides the things a bench scope provides: a trigger with a pre/post event
|
||||
window, a pause that freezes the visible window, cursors, statistical measurements,
|
||||
user-defined math channels built in a node editor, and an export of the visible window to
|
||||
|
||||
@@ -26,8 +26,8 @@ Options: `--server`, `--port`, `--multicast`, `--interface`, `--buffer-samples`
|
||||
(samples kept per acquired signal, default 10M; see "The sample buffer is budgeted in samples" below),
|
||||
`--memory-limit-mb` (hard ceiling on the ring storage, default 8192), `--max-window` (largest time
|
||||
window the toolbar offers, default 600), `--holdoff` (default trigger hold-off),
|
||||
`--recv-buffer`, `--plots` (the number of stacked plots to start with, 1..8; rows are
|
||||
added and removed from the **Layout editor** afterwards), `--relative-time` (see below),
|
||||
`--recv-buffer`, `--plots` (the number of plots to start with, one per row, 1..8; rows are
|
||||
added and removed and a row can be split into side-by-side plots from the **Layout editor** afterwards), `--relative-time` (see below),
|
||||
`--source NAME` and the repeatable `--source-opt k=v` (the source kind and its settings; see "Data sources" below),
|
||||
`--capture-packets N` (see "Packet capture" below), `--renderer NAME` (GTK's renderer:
|
||||
`cairo`, `gl` or `vulkan`; `cairo` is the software preset that exports both
|
||||
@@ -1105,6 +1105,9 @@ plots = 1
|
||||
measure_stats = false
|
||||
|
||||
[[plot]]
|
||||
row = 0
|
||||
width = 1
|
||||
weight = 1
|
||||
signals = ["Ch1", "", "", ""]
|
||||
|
||||
[[calibration]]
|
||||
@@ -1113,6 +1116,14 @@ gain = 2
|
||||
offset = 0.5
|
||||
```
|
||||
|
||||
Each `[[plot]]` table is one **plot** (one cell of the layout), in left-to-right, top-to-bottom order.
|
||||
`row` is the figure row the plot sits in (0-based) and `width` its share of that row's width; `weight` is
|
||||
the *row's* share of the figure's height, written on every plot of the row and read from the row's first
|
||||
one. A session with no `row` keys -- every file written before a row could be split -- loads as one plot per
|
||||
row, and so does one whose `row` numbers cannot be a layout (a number that skips or goes back, more than 4
|
||||
plots in a row or 8 in all): the file is never refused for it, and the signals stay where they were. See
|
||||
"Splitting a row".
|
||||
|
||||
It also stores `measure_stats` (the measurements statistics toggle, a boolean, absent means
|
||||
`false`) and `dock` (whether the dock is shown, a boolean, absent means `true`; it replaced the
|
||||
sidebar's `side_tab`, and a file that still carries `side_tab` loads, the key being ignored). The
|
||||
@@ -2568,7 +2579,8 @@ out of the plot **while a gesture is on that row**, because a drag writes the *p
|
||||
the only place it is visible. Outside a gesture the plot is not read at all: `cursor.x` is the
|
||||
snapped value then, which is what the app wrote itself a frame earlier, and adopting that back
|
||||
is what made a dragged cursor spring to where it had been. A ruler is per row: each row has its own y axis, so two rows' rulers are
|
||||
two different values, and the pane names the row.
|
||||
two different values, and the pane names the row. In a split row (see "Splitting a row") the vertical pair is drawn in
|
||||
every plot and a ruler belongs to one plot, which the pane names `plot 2a`.
|
||||
|
||||
`mix` needs no rulers: it reports the value the trace actually has at each marker, read
|
||||
through the library's `Plot::crosshair_value()` — **the nearer of the two bracketing
|
||||
@@ -2761,8 +2773,9 @@ full range — the snapshot's own span, which is legitimately *narrower* than th
|
||||
when the ring had not filled it. A fit of a frozen view keeps it frozen rather than throwing it
|
||||
back onto the stream.
|
||||
|
||||
**Shared x axis.** The rows are the rows of **one `plot::Figure`**, drawn by one
|
||||
`plot::gtk4::PlotArea` in figure mode, and they all join a single x link group.
|
||||
**Shared x axis.** The plots are the plots of **one `plot::Figure`** (one per row, or several side by
|
||||
side in a split row, see "Splitting a row"), drawn by one `plot::gtk4::PlotArea` in figure mode, and they
|
||||
all join a single x link group.
|
||||
A gesture changes the row it landed on; `Figure::layout()` adopts that change
|
||||
into the group and pushes it to the others, so panning or zooming any row moves
|
||||
them all with nothing to propagate by hand. The live follow is the *group's*
|
||||
@@ -2776,8 +2789,8 @@ The self-check's layout line carries `x-desync N`, the number of frames in which
|
||||
any row's x range differed from row 0's by more than a pixel of data. **It must
|
||||
read 0.**
|
||||
|
||||
`Figure`'s row count is fixed at construction, so there is one structural path:
|
||||
a changed row count, or a changed legend name (the one thing a `Plot` cannot
|
||||
`Figure`'s shape (its cells per row) is fixed at construction, so there is one structural path:
|
||||
a changed row or cell count, or a changed legend name (the one thing a `Plot` cannot
|
||||
express), builds a new figure and the area is handed it. That costs no GL state —
|
||||
the widget keeps its context. A displayed event survives the rebuild because the
|
||||
pinned `[-pre, +post]` window is restated rather than re-derived from the live
|
||||
@@ -2808,6 +2821,37 @@ above row 2" are the same insertion, and two buttons there would sit one on top
|
||||
other. The `rows + 1` boundary buttons — above the first row and below the last included —
|
||||
reach every position the two-per-row reading offers.
|
||||
|
||||
### Splitting a row
|
||||
|
||||
A row can be split into **two to four plots side by side**, up to **eight plots in all**. A split is
|
||||
only an arrangement of more plots on the screen: **every plot shares the one time window** -- one x
|
||||
link group, so a pan or zoom in any plot moves them all; one crosshair, drawn at the same time in every
|
||||
plot; and one pair of vertical cursors, at the same two times in every plot. What each plot keeps is its
|
||||
own **y axis** (auto or fixed), its own horizontal rulers and its own fit; a wheel or an axis drag acts on
|
||||
the plot under the pointer. Only the **bottom** row's plots carry the time labels, and the left margin
|
||||
holding the y labels is one width for the whole figure, so the plots of a row line up with the rows
|
||||
above and below it; a plot whose labels are narrower simply has more margin.
|
||||
|
||||
In the **Layout editor** (`l`):
|
||||
|
||||
- A **`+` at a row's left and right edge, and one between each two plots**, splits the row there: a new,
|
||||
empty plot is added at that position and the plots after it move right *with their traces*. A one-cell
|
||||
row can be split from either side. The `+` is dim at 4 plots in the row or 8 plots in all (the row `+`,
|
||||
which adds a whole row, dims at 8 plots).
|
||||
- A **drag arrow between two plots** resizes the two neighbours against each other; neither can be
|
||||
dragged below about 96 px. The plotter keeps 64 px as an absolute floor, and when the window is too
|
||||
narrow for that it shrinks every plot proportionally and drops the gap, so nothing has a negative size.
|
||||
The widths are saved when the drag ends.
|
||||
- The **`×`** at a plot's top left removes that plot (and its traces) from a split row, and removes the
|
||||
*row* from a one-plot row, as it always did. It is dim when that would leave no plot.
|
||||
|
||||
Plots are named by their row and, in a split row, a letter: **`1`**, **`2a`**..**`2d`**. The names are
|
||||
what the signals page's *Plot on* buttons (tooltip "row 2, cell 1 of 2"), the status notices ("plotting
|
||||
'sine' on plot 2b") and the cursors pane for a split row (`plot 2a`; a plot of an unsplit row stays
|
||||
`row N`, counted from 0 as before) show. A signal dragged from its badge lands on the plot under the
|
||||
pointer, with the same free-slot rule as a row. The session file keeps the arrangement (`row` and `width`
|
||||
on each `[[plot]]`, see "The session layout file").
|
||||
|
||||
The buttons are `GtkOverlay` children placed by their margins over the figure's own row
|
||||
rects (`FigureLayout::rows`, divided by the area's device scale), deliberately **not** one
|
||||
transparent widget covering the plot: an overlay child is picked wherever its allocation
|
||||
|
||||
+25
-2
@@ -71,6 +71,29 @@ it (type, size, range, sample rate, timing). Below come the colour and the **Plo
|
||||
rows. Gain and offset are applied at ingest, so the measurements and the export carry the same numbers the
|
||||
plot shows; a renamed unit is used there too and is saved with the session.
|
||||
|
||||
### Splitting a row
|
||||
|
||||
Press `l` (or pick **Layout editor** from the overflow menu) to arrange the plots: a **`×`** at the top
|
||||
left of each plot removes it, a **`+`** on each boundary between rows adds a row, and a **Done** button
|
||||
in the plot's top-right corner leaves the editor. The plots can also be put **side by side**: a row holds
|
||||
up to **4** plots, and the figure up to **8** in all.
|
||||
|
||||
- **To split a row**, click the `+` at its left or right edge, or the `+` between two of its plots. The
|
||||
new plot is empty and the plots after it keep their traces. The `+` is dim at 4 plots in the row or 8
|
||||
in the figure.
|
||||
- **To resize**, drag the arrow between two plots left or right. The two neighbours trade width (each
|
||||
stays at least about 96 px wide); the sizes are saved with the session.
|
||||
- **To remove a plot**, click its `×`. In a split row it removes just that plot and its traces; in a row
|
||||
of one plot it removes the row.
|
||||
|
||||
Every plot shares **one time window**: panning or zooming in any of them moves them all, the crosshair
|
||||
sits at the same time in each, and the vertical cursors are the same two times in each. Each plot has
|
||||
its **own y axis**, its own horizontal rulers and its own fit, and only the bottom row shows the time
|
||||
labels. A plot of a split row is named by its row and a letter, `2a` to `2d`: that is what the signals
|
||||
page's **Plot on** buttons, the status line and the cursors readout call it, and a signal dragged
|
||||
from its badge goes onto the plot under the pointer. The arrangement is saved per source, and a session
|
||||
saved before rows could be split opens with one plot per row, as it was.
|
||||
|
||||
## 4. The window and navigation
|
||||
|
||||
The header's window control (`2 s`) sets the time span on screen; the popover takes a number and a
|
||||
@@ -129,8 +152,8 @@ does not walk it off its snapshot.
|
||||
|
||||
The cursors toggle in the header bar (`c`) switches on two instruments per row:
|
||||
|
||||
- **vertical** — two times, shared by every row;
|
||||
- **horizontal** — two rulers per row;
|
||||
- **vertical** — two times, shared by every row (and every plot of a split row);
|
||||
- **horizontal** — two rulers per plot;
|
||||
- **both** — the two together;
|
||||
- **mix** — the two times, read off each *trace* where they cross it.
|
||||
|
||||
|
||||
@@ -259,10 +259,12 @@ Layers to keep straight:
|
||||
`ChannelBuffer` is a `plot::data::Series` over a (value, time) ring pair.
|
||||
- `SlotSeries` (`src/channel.hpp`) — a trace's stable series object, rebound each frame
|
||||
to either a `ChannelBuffer` or an `EventSeries`.
|
||||
- `RowSet` (`src/plot_host.hpp`) — **one `plot::Figure`** of up to `kMaxRows` rows (added
|
||||
and removed one at a time by the layout editor), each a
|
||||
borrowed `plot::Plot*` with `kSlotsPerRow` fixed trace slots. The figure owns
|
||||
everything the rows have to agree on: the x window (one x link group every row joins),
|
||||
- `RowSet` (`src/plot_host.hpp`) — **one `plot::Figure`** of up to `kMaxRows` **plots** (rows are added
|
||||
and removed one at a time by the layout editor, and a row can be split into up to
|
||||
`kMaxCellsPerRow` cells side by side), each plot a
|
||||
borrowed `plot::Plot*` with `kSlotsPerRow` fixed trace slots; `row(i)` indexes the plots row-major,
|
||||
`figure_row_count()` counts the rows. The figure owns
|
||||
everything the plots have to agree on: the x window (one x link group every plot joins),
|
||||
the live follow (the *group's* — a member's own axis must be `XFollow::Off`, since
|
||||
`Figure::link_x()` refuses a `Live` member), the left gutter and the crosshair.
|
||||
|
||||
|
||||
@@ -269,6 +269,22 @@ adapter `PlotArea`'s **figure mode** —
|
||||
`set_figure()`, one `input::Controller` per row, nearest-row event routing, a
|
||||
figure-wide crosshair — plus `PlotArea::row_at()` for naming the row a pointer is over.
|
||||
|
||||
A figure row can hold **several plots side by side**, which the host's horizontal split needs and
|
||||
the library had no concept of: `Figure(std::span<const u32> cells_per_row)` builds one, with
|
||||
`plot_count()`, `cells_in_row()`, `first_plot_of_row()` and `row_of_plot()` to move between rows and the
|
||||
flattened plot index, `set_cell_weights()` and `set_col_gap()` to size the cells, and `FigureLayout::cells`
|
||||
(one rect per plot, flattened) and `FigureLayout::col_gap` to read the result back. `Figure(u32 rows)`
|
||||
still means one cell per row, so every row-only caller and test is unchanged, and `row_count()` stays the
|
||||
number of *figure rows* (the host's `RowSet::row_count()` is the plot count, `figure_row_count()` the
|
||||
rows). `plot(i)`, `link_x()` and the crosshair's hovered index all take the **flattened** plot index.
|
||||
Cell widths use the height scheme: a floor of **64 px logical** per cell, the weights share the rest,
|
||||
the last cell closes the row exactly, and a figure too narrow for the floors shrinks the cells
|
||||
proportionally and drops the column gap. Only the bottom row's cells carry x labels and the x title
|
||||
(every other plot gets `XStrip::None`), and the left y gutter stays one maximum over the whole figure.
|
||||
In the GTK4 and GLFW adapters the pointer is routed by **cell rect** (`row_in(rows, x, y)`: the
|
||||
containing cell, else the nearest by squared distance), one `input::Controller` per plot. The tests are
|
||||
the `figure_cell*` cases in `tests/test_figure.cpp` and `glfw_host_routes_by_cell`.
|
||||
|
||||
`plot::data::RingBuffer` carries two writes a plain SPSC ring has no use for and this app
|
||||
does: `amend(absolute, v)` rewrites a retained slot when a late packet's data finally
|
||||
arrives, and `clear_to(absolute)` empties a ring and **resumes its count at a given
|
||||
|
||||
@@ -52,7 +52,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
up front and rebinding via `SlotSeries`; a *name* change (legend text) is the one
|
||||
thing that forces a rebuild. Cursors are parked outside the axis range instead of
|
||||
removed.
|
||||
- **A `Figure`'s row count is fixed at construction**, so `RowSet::rebuild_all()` builds
|
||||
- **A `Figure`'s shape (its cells per row) is fixed at construction**, so `RowSet::rebuild_all()` builds
|
||||
a *new* figure and the area is handed it (`PlotArea::set_figure()`); it costs no GL
|
||||
state. That makes one path for "a row's traces changed" and "the row count changed",
|
||||
and it is why `rebuild_all()` restates a pinned event window (`pinned_pre_`/
|
||||
@@ -105,6 +105,60 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
an equal division. `sync_layout_editor()` runs every frame on purpose:
|
||||
`gtk_widget_set_margin_*()` returns early on an unchanged value, so a settled layout costs
|
||||
the comparisons and nothing else, and a resize needs no notification.
|
||||
- **A figure row can be split into cells, and every plot index in the host is the flattened one.**
|
||||
`RowSet::row_count()` is the number of **plots** and `row(i)`, `Row::plot`, the cursors, the
|
||||
measurements, `bind()`, the readouts and the persistence layers all index plots row-major (row 0's cells
|
||||
left to right, then row 1's); `figure_row_count()` is the number of figure rows, and
|
||||
`cells_in_row()`/`first_plot_of_row()`/`figure_row_of()`/`cell_of()` translate. **Code that means "the
|
||||
rows of the figure" -- the editor's row `+`, the row drag, the row weights, `fl.rows` -- must say
|
||||
`figure_row_count()`**: with no split the two are equal and a slip passes every unsplit probe, which
|
||||
is how a row weight once drifted off its row (see the `row-weights` reading). The caps are
|
||||
`kMaxCellsPerRow` = 4 per row and `kMaxRows` = 8 **plots** in all (the constant bounds plots now, not
|
||||
figure rows), so every per-plot structure sized for 8 is unchanged. `plot_label()` is what the UI
|
||||
prints for a plot: the row's number (`2`) for a one-cell row, the number and a letter (`2a`..`2d`) for
|
||||
a split one -- never a flat index, which is 0-based and says nothing about where the plot is.
|
||||
What the cells share and what each keeps:
|
||||
- **One time window.** Every cell of every row is a member of the one x link group, so a pan or zoom
|
||||
in any cell moves them all, the crosshair is one x drawn in every cell (the hovered cell shows its
|
||||
y), and the **vertical cursor pair is one shared time drawn in every cell** (`cursors_aligned`).
|
||||
A split is an arrangement of more plots, not a second time scale: nothing about the time axis changes
|
||||
(see [invariants-time-axis-and-window.md](invariants-time-axis-and-window.md)).
|
||||
- **Per-cell y.** Each cell has its own y axis (auto or fixed), its own horizontal rulers and its own
|
||||
fit; a wheel or axis drag acts on the cell under the pointer, an x gesture on any bottom-row cell
|
||||
moves the shared window. The cursors pane's ruler label is `plot 2a` for a plot of a split row and
|
||||
`row N` (the plot index) for one of an unsplit row, so the pane reads the same as before until a row
|
||||
is split.
|
||||
- **Bottom-row x labels only.** The plotter gives every plot that is not in the last figure row
|
||||
`XStrip::None`; the cells of the bottom row each keep their strip, so a middle-drag on the axis
|
||||
works in any of them. The `t0` caption goes on those cells too.
|
||||
- **One left gutter.** The y gutter is one maximum for the whole figure with the same reserved floor
|
||||
as before (`Figure::set_min_y_gutter()`), **not a per-column one**: a per-column gutter would make a
|
||||
cell's width depend on its labels, which "layout must not depend on content" forbids. A cell whose y
|
||||
labels are narrower than the gutter simply has a wider margin.
|
||||
- **Widths are weights with a floor.** The plotter keeps 64 px logical per cell and a column gap
|
||||
(`kColGap`, 6 px); the editor's drag is stricter (below). A weight is a share of what is
|
||||
left *after* the floors, so cells of `{3, 1}` read 533:220 in a 756 px figure, not 3:1, and the
|
||||
editor's 96 px drag floor is a floor on the *share it asks for*: a cell dragged to the limit realises
|
||||
`64 + share * remainder` and reads wider (144 px in the split probe's `pre-insert0` line), so the
|
||||
boundary does not follow the pointer one for one near either extreme.
|
||||
- **A cell is removed with its traces, and a new cell is empty.** `insert_cell(fr, at)` pushes the
|
||||
cells at and after `at` right *with their contents* (the same rule as `insert_row()`), and
|
||||
`remove_cell(plot)` on a one-cell row removes the row. Both return false at a cap or when they would
|
||||
leave no plot, and never grow past 4 / 8.
|
||||
**The editor's controls** (`rebuild_layout_editor()`): a cell `+` (`kLayoutCellKey`, tooltip "split
|
||||
here") at a row's left edge, at its right edge and at every internal boundary -- `n + 1` of them for a
|
||||
row of `n` cells, so a one-cell row can be split from either side -- inserting at that position; a drag
|
||||
arrow (`kLayoutColKey`, `go-next-symbolic`) at each internal boundary that moves the boundary between
|
||||
its two neighbours, **each floored at `kMinCellPx` = 96 px logical** (or half the pair when the pair is
|
||||
narrower) and measured from the two widths at drag start, so the drag does not compound; and the per-plot
|
||||
`×`, which removes the cell in a split row ("remove this plot") and the row in a one-cell row ("remove
|
||||
this row"), insensitive when it would leave no plot. Cell `+` is insensitive at 4 cells in its row or 8
|
||||
plots in all; the row `+` is insensitive at 8 plots. **The cell `+` is keyed by `kLayoutCellKey` (figure row * 8 + position + 1) and
|
||||
never by `set_row_key()`**: `row_key()` is a plot or boundary index, `layout_button()` finds a button by it,
|
||||
and a cell `+` that also carried one would be found as a row button; `sync_layout_editor()` tells the kinds
|
||||
apart by which key it finds. Like every editor control the
|
||||
cell buttons are placed from `FigureLayout::cells`, divided by `device_scale()`, and the editor does
|
||||
not draw while the figure's rects are not yet one per plot.
|
||||
- **The slide-over is the second thing placed over the plot, and it is one opaque widget, not a
|
||||
transparent area.** The same pick rule as the editor's buttons applies, harder: the panel is a
|
||||
`GtkRevealer` aligned to the end edge (`halign=END`, `valign=FILL`) at `kSlideOverWidth` = 360 px, so
|
||||
@@ -189,6 +243,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
per-channel list, and why `format_elapsed()` steps to a coarser unit instead of
|
||||
letting the counter grow: each is a status field whose *width* would otherwise depend
|
||||
on the stream, and the status row is directly above the plot.
|
||||
A split row keeps this rule: cell widths come from the weights and the 64 px floor and never from what
|
||||
a cell's labels need, and the gutter stays one figure-wide maximum.
|
||||
The reservation goes on the **figure** (`Figure::set_min_y_gutter()`, added to the
|
||||
plotter for this), not on the rows: `Figure::layout()` clears every row's
|
||||
`set_min_y_gutter()` to measure what its own labels need, so a floor written onto the
|
||||
|
||||
@@ -51,6 +51,27 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
was parsed into the last calibration record and the enable state was lost without a word.
|
||||
`mkdirs()` used to create only components with a trailing slash, so `~/.config/udpscope` was never
|
||||
made and the *first* save failed silently -- worth knowing when a layout change "does not stick".
|
||||
- **A `[[plot]]` table is one *plot* (one cell), and carries `row` and `width` for the horizontal split.**
|
||||
`plots = N` counts plots, as it always did. `row` is the 0-based **figure row** the plot sits in and
|
||||
`width` the cell's share of that row's width (`LayoutState::Row::row`, `::width`); the tables run in
|
||||
the flattened order, so a row's cells are consecutive and left to right. The row's height share
|
||||
`weight` stays per *figure row* but is **written on every cell of it** (so a table read alone is
|
||||
complete) and **read from the row's first cell**; `collect_layout()` takes it from
|
||||
`row_weights()[figure_row_of(plot)]`, not from `row_weights()[plot]` -- the latter is a plot index into
|
||||
a figure-row vector, right until the first split. The file is written `row`, `width`, `weight`,
|
||||
`signals`; a state built with `row < 0` (nothing in the app does) omits `row` and `width`.
|
||||
Three loading rules, all in `apply_layout()` and none of them a refusal, because a session is the
|
||||
user's arrangement and not worth losing to a malformed key:
|
||||
(1) **a file with no `row` loads as one cell per row** -- every session written before the split --
|
||||
with the per-plot `weight`s as the row heights, exactly as it opened before;
|
||||
(2) **a `row` sequence that cannot be a layout loads the same way**: some plot without a `row`, a row
|
||||
number that goes backwards or skips one, a first row that is not 0, more than 4 cells in a row, or
|
||||
more than 8 plots; the signals stay on the plots they were on (`invalid-rows` is the reading);
|
||||
(3) **cell widths are applied after the arrangement**, because `set_cells_per_row()` resets them, and
|
||||
only for rows of two or more cells -- a one-cell row has no widths and its `width` is ignored.
|
||||
Both new keys are parsed with the file's other forgiving rules: `row` must be an integer >= 0 and
|
||||
`width` a number > 0, anything else leaves the default. A save after loading a pre-split file writes
|
||||
the `row`/`width` keys, which is how an old session is upgraded without anyone asking.
|
||||
- **Measurement options persist under *named* keys -- `threshold`, `threshold2`, `noise` and `hf` -- so a
|
||||
file written before them loads at the defaults**, which are exactly the behaviour it was written against:
|
||||
`noise = 10` is the
|
||||
|
||||
@@ -424,13 +424,23 @@ Two lines of the self-check output are assertions in disguise:
|
||||
the group, because `hold_pinned_window()` prefers a member that has moved since `pinned_stated_`
|
||||
— that is how it tells a gesture from its own echo — so a fit that wrote the group alone reads
|
||||
back as the zoomed range one frame later.
|
||||
- `[layout-probe] <action> rows=N buttons=B done=D done_box=[x,y,WxH] rows_y=[..] close=[..] add=[..] traces=[..]`
|
||||
- `[layout-probe] <action> rows=N buttons=B done=D done_box=[x,y,WxH] rows_y=[..] close=[..] add=[..] traces=[..] cells=[..] colweights=[..]`
|
||||
`side_w` is the sidebar column's width and **must be its own request (280), never 0**: it read 0 while
|
||||
the divider sat at the window's right edge, which is a panel that is not there. It was 280 during the
|
||||
first frames and 0 once the window had settled, so read it from a `placed` line rather than the first.
|
||||
(stderr, `UDPSCOPE_PROBE_LAYOUT_MS`) — **`buttons` must be `2N+1`** while the editor is
|
||||
up (one `×` per row, one `+` per boundary) and **0 on the `editor off` line**: the
|
||||
buttons exist only while the editor does. `done` follows the same rule for the editor's
|
||||
(stderr, `UDPSCOPE_PROBE_LAYOUT_MS`) — **`buttons` must be `3P + 2F + 2`** while the editor is
|
||||
up, for `P` plots in `F` figure rows: one `×` per plot (`P`), one row `+` and one row resize arrow
|
||||
per row boundary (`2(F+1)`, the arrows of the figure's two outer edges counted although hidden), and
|
||||
per row of `n` cells `n + 1` cell `+` buttons and `n - 1` column arrows (`3P` in all, since
|
||||
`sum(n+1) + sum(n-1) = 2P`, plus the `P` of the `×`). With no split that is **7 for one row and 12 for
|
||||
two** (`5` and `8` before the split, the old `2N+1` having long stopped counting the arrows), and a
|
||||
split row adds three per extra cell. **0 on the `editor off` line**: the
|
||||
buttons exist only while the editor does. `rows=` is the plot count and `cells=[..]` the cells of each
|
||||
figure row, comma-separated (`[1,1]` for two unsplit rows, `[1,2]` for a split second row);
|
||||
`colweights=[..]` is each figure row's cell weights, `|` between rows and **empty for a row with none**
|
||||
(every unsplit row, and a split row nobody dragged), so two unsplit rows read `[|]` and a dragged
|
||||
`{3,1}` second row `[|3,1]`. An unsplit run must read `cells=[1,..,1]` and a `colweights` of
|
||||
only separators; anything else is a split the run did not ask for. `done` follows the same rule for the editor's
|
||||
own exit button (1 up, 0 off), and its `done_box` on the `placed` line must sit in the
|
||||
area's top-right corner: `x + W` within a few pixels of the area width and `y` within a
|
||||
few of 0 (the on-switch line reports a box from before the first frame, so read the
|
||||
@@ -661,7 +671,7 @@ Two lines of the self-check output are assertions in disguise:
|
||||
than by the first-run timer.
|
||||
- **The session layout round trip** (`~/.config/udpscope/<host>_<port>.toml`) — run once
|
||||
with a drop and a window change, then again with no interaction: the second run's dump
|
||||
must read `layout loaded from ...` and `layout: rows=R window_s=W row0=[...]` with the
|
||||
must read `layout loaded from ...` and `layout: rows=R window_s=W row0=[...] ... cells=[..] colweights=[..]` with the
|
||||
first run's values, and the signals it names must come back *acquired* (a `row 0 slot 0
|
||||
-> channel ... samples=N` line). Delete the file before a run whose layout you did not
|
||||
mean to inherit (`127.0.0.1_9540.toml` and friends accumulate otherwise). A **persistence
|
||||
@@ -2838,3 +2848,62 @@ any is a badge that does not say its panel is open, or one that stays marked aft
|
||||
the plot's pan, drop and hover; `plot_unchanged=0` is the panel taking room from the plot. **Line 2:** `click_inside_closes=0` with `still_open=1` (a click
|
||||
inside the panel does not close it), `click_outside_closes=1` (a click on the plot does), `esc_closes=1` (`slideover_escape()` closes it) and `closed=1`.
|
||||
`esc_closes=1` is the function called directly; the key *through the window's handler* is the `esc:` reading of the `[keys]` line.
|
||||
|
||||
## `[split-probe]`
|
||||
|
||||
```
|
||||
[split-probe] start plots=1 frows=1 cells=[1] widths=[756] tile=1 x_equal=1 cursors_aligned=1 weights=[-] traces=[1:sine,square,ramp,noise]
|
||||
[split-probe] split plots=2 frows=1 cells=[2] widths=[376,376] tile=1 x_equal=1 cursors_aligned=1 weights=[-] traces=[1a:sine|1b:square]
|
||||
[split-probe] weights 3:1 plots=2 frows=1 cells=[2] widths=[533,220] tile=1 x_equal=1 cursors_aligned=1 weights=[3,1] traces=[1a:sine|1b:square]
|
||||
[split-probe] caps-calls insert_cell=[1,1,0] refused_cell=1 insert_row=[1,1,0] refused_row=1 plots=8
|
||||
[split-probe] roundtrip rt=1 expect=r0(h=2.0000)[w=1.0000:sine] r1(h=1.0000)[w=3.0000:square][w=1.0000:ramp] got=r0(h=2.0000)[w=1.0000:sine] r1(h=1.0000)[w=3.0000:square][w=1.0000:ramp]
|
||||
[split-probe] ruler-labels split=[plot 1a,plot 1b,plot 2a,plot 2b] mixed=[row 0,plot 2a,plot 2b]
|
||||
```
|
||||
|
||||
`UDPSCOPE_PROBE_SPLIT_MS` (stderr; see [verification.md](verification.md)). Nineteen steps, 250 ms apart;
|
||||
each restructures and the *next* step reports the frame that was laid out with it, so a line's label
|
||||
names the arrangement the line **reads**, not the call that made it. The state lines share one format:
|
||||
`plots` (`row_count()`), `frows` (`figure_row_count()`), `cells=[..]` (per figure row), `widths=[..]`
|
||||
(logical pixels of each cell, `|` between rows), `weights=[..]` (the stored cell weights, `-` for a row with
|
||||
none), `traces=[label:signals|..]` with the labels `plot_label()` gives (`1`, `1a`, `2b`). Every state line
|
||||
carries the same three assertions:
|
||||
|
||||
| field | value it must read | the defect a wrong reading names |
|
||||
| --- | --- | --- |
|
||||
| `tile` | **1** on every state line | a row whose cells do not fill its rect edge to edge, are not at its y and height, or are not one `col_gap` apart, or a `FigureLayout` with `cells.size() != plots` / `rows.size() != frows`: the plotter's placement or a host that did not rebuild the figure |
|
||||
| `x_equal` | **1** | a plot whose x range differs from plot 0's: a cell that is not in the x link group (a rebuild that linked rows and forgot cells) |
|
||||
| `cursors_aligned` | **1** | a plot whose two vertical cursors sit elsewhere than plot 0's: a per-cell cursor time instead of the one shared time; the probe turns the cursors on and resets them first so the comparison is of real positions |
|
||||
|
||||
The other lines, in order:
|
||||
|
||||
| line | value it must read | the defect a wrong reading names |
|
||||
| --- | --- | --- |
|
||||
| `start` / `split` | `cells=[1]` then `[2]` with `widths=[376,376]` (equal halves of 756) and the traces on `1a`/`1b`; a second cell starts **empty**, the probe assigns it | a split that moved or dropped the first cell's traces, or unequal default cells |
|
||||
| `weights 3:1` | `widths=[533,220]`, **not** 3:1, and `tile=1` | not a defect: the plotter's 64 px floor per cell is taken off first and the weights share the remainder, so the shares read compressed; a result of exactly 3:1 would be the floor missing |
|
||||
| `row+cell` | `cells=[2,2]`, `weights=[3,1\|-]` | row 0's cell weights lost or moved when a row and a cell were inserted below it (they belong to the figure row) |
|
||||
| `caps-calls` | `insert_cell=[1,1,0] refused_cell=1 insert_row=[1,1,0] refused_row=1 plots=8` | a cap that is not reached or not held: row 0 starts with two cells and takes two more, refusing the third call at 4; two rows then fit before the plot total reaches 8 and the third is refused; `plots` must stop at **8** |
|
||||
| `caps` | `plots=8 frows=4 cells=[4,2,1,1]`, `tile=1` | a ninth plot, or a fifth cell in a row |
|
||||
| `remove` | `plots=7 cells=[3,2,1,1]` | `remove_cell()` on a multi-cell row that removed the row, or that dropped a neighbour's traces |
|
||||
| `roundtrip-io` | `wrote=1 read=1 plots_in_file=3` | a save that was refused, or a file that lost a `[[plot]]` table |
|
||||
| `roundtrip` (state line) | `cells=[1,2] widths=[756\|533,220] weights=[-\|3,1]` and the three traces on `1`, `2a`, `2b` | the arrangement did not survive `collect_layout()` -> file -> `apply_layout()` |
|
||||
| `roundtrip rt=` | **`rt=1`** with `got=` equal to `expect=` (row weights `h=`, cell weights `w=` and each cell's trace, read back at the file's `%.4f`) | **`rt=0`** names the difference: a weight written per plot index instead of per figure row, widths applied before the arrangement (which resets them), or a trace that moved to another cell |
|
||||
| `legacy-io` | `read=1 plots_in_file=3 row_unset=1` | the stripped file still carrying a `row`, which would make the next line test nothing |
|
||||
| `legacy` | `cells=[1,1,1]`, `frows=3`, `widths=[756\|756\|756]`, `traces=[1:sine\|2:square\|3:ramp]` | a session from before the split that did not open as one cell per row, or lost its signals |
|
||||
| `invalid-rows` | `ok=1 frows/plots=[5/5 3/3 3/3 3/3]` | one of four unusable `row` sequences (five cells in a row; a skipped row; a row that goes back; a first row that is not 0) that was refused, or kept as a split instead of loading one cell per row |
|
||||
| `editor-on` -> `cell-split` | `cells=[1]` then `cell-split-ck cells=2` and `cell-split` with `widths=[376,376]`, the new `1b` empty | the editor's cell `+` not reaching `insert_cell()`, or the figure not re-pointed after it |
|
||||
| `cell-assigned` | `1a:sine\|1b:square` | an assignment into the new cell that landed on another plot |
|
||||
| `col-drag` | `left_gt=1 both_above_min=1 left_gt_right=1 tile=1`; `pair_sum` constant (753 at the 756 px window) | the column drag not moving the boundary (`left_gt=0`), a cell dragged below `kMinCellPx` (`both_above_min=0`), or a drag that changes the pair's total instead of redistributing it. The probe applies +80 px, then +/-10000 px through `layout_col_apply()` |
|
||||
| `pre-insert0` (state line) | after the extreme drags `widths=[144,609]` | not a defect: the drag floors the left share at 96 px of the pair, but the plotter realises `64 + share * remainder`, so a cell dragged to the limit reads wider than 96 (see [invariants-plot-and-layout.md](invariants-plot-and-layout.md)) |
|
||||
| `insert0-move` | `move_ok=1 before=[sine,square] after=[,sine,square]` | an insert at the left edge that renumbered the cells instead of moving their traces right with them |
|
||||
| `remove-mid` | `rm_ok=1 after=[,square]` | removing a middle cell that dropped or shifted the wrong neighbour |
|
||||
| `row-weights` | `wt_ok=1 before=[3,1,1] after=[1,1]` | the row weights not following an insert or removal of a *figure row* (the vector had the plot-count length once and drifted off the rows) |
|
||||
| `sensitivity` | `cell_plus_sensitive=0 row_plus_sensitive=0 plots=8` | `-1` is a button the probe could not find; `1` is a `+` left clickable at the 4-cell cap (cell) or at 8 plots (row) |
|
||||
| `two-split-rows` | `cells=[2,2]`, labels `1a 1b 2a 2b`, all empty | `set_cells_per_row()` leaving the old plots' contents or labels behind |
|
||||
| `drop-rowat` | `ok=1 rowat=[1a=0,1b=1,2a=2,2b=3]` | `PlotArea::row_at(x, y)` choosing by y alone, which would put every cell of a row on its first plot: a drop or a right click on `1b` landing in `1a` |
|
||||
| `plot-on-buttons` | `[1a:row 1, cell 1 of 2\|1b:row 1, cell 2 of 2\|2a:row 2, cell 1 of 2\|2b:row 2, cell 2 of 2]` | a signals-page button labelled with the flat index, or without the cell tooltip; a one-cell row keeps its plain label and the tooltip "draw this signal on this plot" |
|
||||
| `assign-1b` / `drop-2b` | `assigned=1 notice='plotting 'sine' on plot 1b'`; `dropped=1 notice='plotting 'square' on plot 2b'` | a notice that names the plot by number alone |
|
||||
| `after-assign` (state line) | `traces=[1a:-\|1b:sine\|2a:-\|2b:square]` | the signal went to a neighbouring cell |
|
||||
| `ruler-labels` | `split=[plot 1a,plot 1b,plot 2a,plot 2b]` and, with cells `{1,2}`, `mixed=[row 0,plot 2a,plot 2b]` | the cursors pane's per-plot ruler line (a label read through the pane's own widgets): a split row's plots are `plot <label>`, an unsplit row keeps `row <plot index>` (**0-based**, as it always was -- `row 0` is the first plot, not a defect) |
|
||||
|
||||
The run ends with the usual `gtk warnings: 0` and no `Gtk-CRITICAL`. `x_equal=1` and `cursors_aligned=1` on every state line are
|
||||
the only readings of the shared time window across cells.
|
||||
@@ -26,6 +26,15 @@ shortens `plot=` by the 36 px bar; and the `tour started: steps=` count. A self-
|
||||
`c572ec2` is no longer the baseline for those lines. The rest is the mock's own jitter (the sign of a
|
||||
`square y=[...]` or a `trig engine:` figure, the position of one `trigger: edge` line), which the
|
||||
normalisation does not remove -- anything else is a finding.
|
||||
For the horizontal plot split the same diff is taken against the commit before it (`18b91c8`, with
|
||||
`deps/plotter` at its pre-split pin `f79a97b`), and it is "unchanged except" exactly two things on the
|
||||
layout lines: **`buttons=`** grows (the editor now carries the cell `+` buttons and the column arrows:
|
||||
`3P + 2F + 2` for `P` plots in `F` figure rows, so `5 -> 7` for one row and `8 -> 12` for two), and the
|
||||
layout probe line and the exit dump's layout line gain the **`cells=[..] colweights=[..]`** fields.
|
||||
Everything else -- cursors, measurements, readouts, signal pages, math, trigger, help figures, the
|
||||
E-1608 -- reads as before; a difference there is a finding, not the feature. Take a second baseline run
|
||||
as a jitter control before believing one (a few mock readings, such as the sine's membership of the
|
||||
cursors mix list or a V/mV flip, move between two runs of the *same* build).
|
||||
A clean build emits **one** warning and no more: a `-Woverlength-strings` on the icons'
|
||||
generated GResource `.c`, whose blob is **9248** against C99's 4095-byte string-literal limit with
|
||||
the five action icons and the two app icons that remain (a note about a generated file, which GCC
|
||||
@@ -196,6 +205,19 @@ precedent, not an exception.
|
||||
with, so a synchronous sequence would read every position one action stale. It clicks by
|
||||
emitting `::clicked`, never `gtk_widget_activate()`, which holds a `GtkButton` for its
|
||||
250 ms `ACTIVATE_TIMEOUT` and then emits into a button the rebuild has already destroyed.
|
||||
- `UDPSCOPE_PROBE_SPLIT_MS` — drive the horizontal plot split, 19 steps 250 ms apart (so a run needs
|
||||
`UDPSCOPE_PROBE_SPLIT_MS=1500 UDPSCOPE_EXIT_AFTER_MS=7500` at least; it enables and assigns the
|
||||
signals itself, so `UDPSCOPE_ENABLE_ALL_MS` is not needed). Each step restructures the cells,
|
||||
re-points the area at the new figure and lets the *next* step read a frame that was actually laid out
|
||||
with them, which is why every report line describes the previous step's change. It splits and
|
||||
weights a row, fills the caps, round-trips a session through `collect_layout()` / `save_layout()` /
|
||||
`load_layout()` / `apply_layout()` (and a copy with its `row`/`width` lines stripped), clicks the layout
|
||||
editor's real cell `+` button, drives the column drag handler, and checks the signals page, the drop's
|
||||
`row_at()` and the cursors pane's ruler labels. Run it once, serially, on its own mock port, from a
|
||||
fresh `XDG_CONFIG_HOME` (the probe deletes the temp files `udpscope-split-probe.toml` and `.legacy` it
|
||||
writes under `g_get_tmp_dir()`, but the editor's clicks schedule a real session save). Read it against
|
||||
the `[split-probe]` section of [probe-readings.md](probe-readings.md); the run must still end with
|
||||
`gtk warnings: 0`.
|
||||
- `[levels-probe] N measurement(s) over K channel(s), shares='...' -> W window(s), L level
|
||||
walk(s)` (stderr, `UDPSCOPE_PROBE_LEVELS_MS`) — **prices a measurement pane**, which is what a
|
||||
report of "it is slow with many measurements" has to become before it is a number. It adds one
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<info>
|
||||
<link type="guide" xref="index#main"/>
|
||||
<link type="seealso" xref="signals"/>
|
||||
<desc>How the figure's rows and traces are arranged, how to drop a signal onto a row, and
|
||||
how the layout editor adds and removes rows.</desc>
|
||||
<desc>How the figure's rows, plots and traces are arranged, how to drop a signal onto a plot, and
|
||||
how the layout editor adds and removes rows and splits a row into plots side by side.</desc>
|
||||
<credit type="author copyright">
|
||||
<name>Martino Ferrari</name>
|
||||
</credit>
|
||||
@@ -14,9 +14,10 @@
|
||||
|
||||
<title>Rows, layout and drag & drop</title>
|
||||
|
||||
<p>The figure is a stack of rows, 1 to 8 of them, each holding up to 4 traces. Rows share
|
||||
one time axis and one pair of side margins, so their y labels line up, their data areas
|
||||
start and end on the same two columns, and their windows cannot drift apart. The left
|
||||
<p>The figure is a stack of rows holding 1 to 8 plots in all, and a row can be split into up
|
||||
to 4 plots side by side. Each plot holds up to 4 traces. Every plot shares one time axis and
|
||||
the figure has one pair of side margins, so the y labels line up, the data areas start and
|
||||
end on the same two columns, and the windows cannot drift apart. The left
|
||||
margin holds the y labels; the right one is empty, and is there so the newest tick's label
|
||||
and the newest sample's ink have somewhere to land instead of sitting on the frame.</p>
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<p>Press <key>l</key>, or pick <gui>Layout editor</gui> from the overflow menu, to add
|
||||
and remove rows: a <gui>×</gui> appears at the top left of each row and a <gui>+</gui>
|
||||
and remove rows: a <gui>×</gui> appears at the top left of each plot and a <gui>+</gui>
|
||||
on each boundary between rows, including above the first and below the last.</p>
|
||||
</item>
|
||||
<item>
|
||||
@@ -38,6 +39,29 @@
|
||||
</steps>
|
||||
</section>
|
||||
|
||||
<section id="split">
|
||||
<title>Splitting a row</title>
|
||||
<steps>
|
||||
<item>
|
||||
<p>In the layout editor, click the <gui>+</gui> at a row's left or right edge, or the
|
||||
<gui>+</gui> between two plots, to add a plot to that row. The new plot starts empty.</p>
|
||||
</item>
|
||||
<item>
|
||||
<p>Drag the arrow on the boundary between two plots to resize them. Neither plot can be
|
||||
made narrower than about 96 px, and the widths are saved with the session.</p>
|
||||
</item>
|
||||
<item>
|
||||
<p>Click the <gui>×</gui> of a plot in a split row to remove that plot and its traces.
|
||||
In a row with a single plot the <gui>×</gui> removes the row instead.</p>
|
||||
</item>
|
||||
</steps>
|
||||
<p>All the plots of a row, and of the whole figure, share one time window: zooming or
|
||||
panning along time in any of them moves all of them, and the vertical cursors are drawn in
|
||||
every plot. The y axes, the horizontal cursors and the fits belong to each plot. Only the
|
||||
bottom row carries the time labels. A row holds at most 4 plots and the figure at most 8
|
||||
in total; the <gui>+</gui> buttons go grey at the limit.</p>
|
||||
</section>
|
||||
|
||||
<section id="knowing">
|
||||
<title>Worth knowing</title>
|
||||
<list>
|
||||
@@ -47,10 +71,11 @@
|
||||
<gui>Back</gui> row to return. That is how a trace comes off a row without going through
|
||||
the layout editor.</p></item>
|
||||
<item><p>A drop <em>adds</em> a trace to the row's next free slot; it does not replace
|
||||
one. With four traces already on a row the drop is refused.</p></item>
|
||||
<item><p>Inserting a row moves the rows below it <em>with their contents</em>. Inserting
|
||||
above row 1 puts what row 1 plotted onto row 2 and leaves the new row empty — the rows
|
||||
are not renumbered under you.</p></item>
|
||||
one. With four traces already on a plot the drop is refused.</p></item>
|
||||
<item><p>Inserting a row moves the rows below it <em>with their contents</em>, split
|
||||
rows included. Inserting above row 1 puts what row 1 plotted onto row 2 and leaves the
|
||||
new row empty — the rows are not renumbered under you. A split row's plots are named
|
||||
<em>2a</em>, <em>2b</em> and so on.</p></item>
|
||||
<item><p>A trace is remembered by the signal's <em>name</em>, not by its channel number,
|
||||
because a new CONFIG or an enable renumbers the channels and an index would silently
|
||||
re-point the trace.</p></item>
|
||||
@@ -62,10 +87,11 @@
|
||||
|
||||
<section id="example">
|
||||
<title>Example</title>
|
||||
<p>With three traces on row 1, pressing <key>l</key> shows 3 buttons (one <gui>×</gui>,
|
||||
two <gui>+</gui>). Clicking the <gui>+</gui> above row 1 gives 2 rows and 5 buttons, with
|
||||
the three traces now on row 2 — the layout probe reports exactly this as
|
||||
<code>rows=2 buttons=5 traces=[0:-|1:sine,ramp,noise]</code>.</p>
|
||||
<p>With three traces on row 1, pressing <key>l</key> shows 7 buttons: the <gui>×</gui>,
|
||||
the two <gui>+</gui> above and below the row with their two resize arrows, and the two
|
||||
<gui>+</gui> at the row's left and right edges. Clicking the <gui>+</gui> above row 1 gives 2 rows and 12 buttons,
|
||||
with the three traces now on row 2 — the layout probe reports exactly this as
|
||||
<code>rows=2 buttons=12 traces=[0:-|1:sine,ramp,noise]</code>.</p>
|
||||
</section>
|
||||
|
||||
</page>
|
||||
@@ -69,9 +69,10 @@ struct HelpHint {
|
||||
/// the page is on screen, so the dock needs no hint of its own and the layer has one code path.
|
||||
constexpr HelpHint kHelpHints[] = {
|
||||
{"plot", "The plot",
|
||||
"Every signal you have switched on is drawn here, one row per group of traces, with all rows "
|
||||
"sharing this one time axis. The wheel zooms about the pointer, a drag pans, and a right-click "
|
||||
"on a row opens that plot's own menu.",
|
||||
"Every signal you have switched on is drawn here, one plot per group of traces, with all plots "
|
||||
"sharing this one time axis. The layout editor (L) adds rows and splits a row into plots side "
|
||||
"by side. The wheel zooms about the pointer, a drag pans, and a right-click on a plot opens "
|
||||
"its own menu.",
|
||||
"", &App::area_overlay},
|
||||
{"trigger", "The trigger",
|
||||
"Freezes the view on an event you describe: a signal, a condition (an edge, a pulse width, a "
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ void clear_box(GtkWidget* box) {
|
||||
|
||||
/// The strip's cell widths, in characters. Fixed like the dock's, so the strip's chips are as wide with
|
||||
/// `--` as with `-123.456 mV` (what does not fit scrolls).
|
||||
constexpr int kStripWhoChars = 6;
|
||||
constexpr int kStripWhoChars = 7;
|
||||
constexpr int kStripTypeChars = 10;
|
||||
|
||||
/// A flat text button that opens a slide-over page. It only opens a page, so a rebuild can never free the
|
||||
|
||||
+1
-1
@@ -441,7 +441,7 @@ struct TourStep {
|
||||
|
||||
constexpr TourStep kTourSteps[] = {
|
||||
{"plot", nullptr, "The plot",
|
||||
"Every signal you have switched on is drawn here, all rows sharing one time axis. The wheel "
|
||||
"Every signal you have switched on is drawn here, all plots 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},
|
||||
{"badges", nullptr, "The badges",
|
||||
|
||||
Reference in new issue
Block a user