docs: horizontal plot split
This commit is contained in:
1 parent
7730b35fe3
commit
2183317953
13 files changed
+319
-39
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user