pub(crate) struct Matrix {Show 16 fields
uid: u64,
measure: Option<MeasureId>,
axis_order: Vec<CategoryId>,
n_rows: usize,
n_cols: usize,
page_idx: Vec<usize>,
axis_for: Option<MeasureId>,
filters: Vec<Filter>,
cursor_row: usize,
cursor_col: usize,
anchor: Option<(usize, usize)>,
editing: Option<(MeasureId, CoordKey)>,
edit_buf: String,
rect: CanvasRect,
gutters: Option<GutterRects>,
rendered_axes: Option<(Vec<CategoryId>, Vec<CategoryId>)>,
}Expand description
One matrix on the canvas: a measure, that matrix’s OWN pivot / filter /
cursor / cell-editor state, and where it sits (rect).
This is the GUI-side twin of [improv_core_model::MatrixPlacement] (which is
the persisted form): a placement plus the transient bits a placement has no
business storing — the keyboard cursor, the open cell editor, and the
measured gutter geometry. Several of these live on one canvas
(ImprovApp::matrices), each independently pivotable, which is plan Step 3
(docs/reviews/2026-09-22-gui-reconstruction-plan.md).
Every method here takes &Model rather than holding one: a matrix is pure
presentation over the shared model, exactly as the per-view state was before
it became a list.
Fields§
§uid: u64Keys this matrix’s window and widget state for its lifetime, so they
stay with it when an earlier matrix is removed (an index would shift).
Unique per process (next_matrix_uid); a clone keeps it.
measure: Option<MeasureId>The measure this matrix shows. None only for an empty model.
axis_order: Vec<CategoryId>A permutation of measure’s categories: the first n_rows are stacked
on rows (outer→inner), the next n_cols on columns, the rest are pages.
Pivoting reorders this without touching formulas. Reset to the measure’s
natural order when this matrix’s measure changes.
n_rows: usizeHow many leading axis_order categories are stacked on the ROW axis,
and how many (after those) on the COLUMN axis. The rest are pages.
Default 1/1 (one category per axis); increasing them stacks categories
on an axis (nested group headers over the Cartesian product of items).
n_cols: usize§page_idx: Vec<usize>Selected item index for each page (extra) dimension, positionally by
page dim (i.e. axis_order[n_rows + n_cols + i]).
axis_for: Option<MeasureId>The measure axis_order/page_idx currently describe (so the pivot
state resets when this matrix’s measure changes).
filters: Vec<Filter>Active per-category display filters for this matrix. Presentation only (hides items from the grid; never touches data). Captured when saving a view; reset on measure switch.
cursor_row: usizeKeyboard cell cursor into this matrix’s grid (row/col indices), clamped to its dimensions.
cursor_col: usize§anchor: Option<(usize, usize)>The fixed corner of a range selection (Shift+arrow, Shift+click,
drag); None selects just the cursor cell. Transient, never persisted.
editing: Option<(MeasureId, CoordKey)>The cell currently being edited in THIS matrix, and its text buffer.
edit_buf: String§rect: CanvasRectIts worksheet window’s outer rect, relative to the workspace’s top-left (persisted in the view).
gutters: Option<GutterRects>Where THIS matrix’s margin gutters and table ended up in the last
laid-out frame (see GutterRects). None until it has rendered once.
Layout output, not state.
rendered_axes: Option<(Vec<CategoryId>, Vec<CategoryId>)>The row and column category stacks THIS matrix’s grid was last rendered
with, as (rows, columns). None until it has rendered once.
Render output, the same role gutters plays for the geometry — and for
the same reason: it makes “each matrix renders its OWN pivot” checkable
instead of merely intended. Asserting on axis_order alone cannot see a
renderer that ignores its matrix index and draws the focused matrix’s
layout N times.
Implementations§
Source§impl Matrix
impl Matrix
Sourcefn new(model: &Model, measure: Option<MeasureId>) -> Matrix
fn new(model: &Model, measure: Option<MeasureId>) -> Matrix
A matrix showing measure with its natural axis order and the default
canvas geometry.
Sourcefn from_placement(model: &Model, p: &MatrixPlacement) -> Option<Matrix>
fn from_placement(model: &Model, p: &MatrixPlacement) -> Option<Matrix>
A matrix restoring a saved [MatrixPlacement] verbatim: its measure,
axis split, filters and geometry, with the page pins resolved
positionally by page dimension (an item that no longer exists falls back
to the first one).
None when the placement’s measure is gone — the caller decides whether
that is an error (a whole view) or one matrix to drop.
Sourcefn to_placement(&self, model: &Model) -> Option<MatrixPlacement>
fn to_placement(&self, model: &Model) -> Option<MatrixPlacement>
This matrix as a persistable [MatrixPlacement] — everything a saved
view needs to reproduce it, and nothing transient (no cursor, no open
editor). None when no measure is shown: there is no matrix to save.
Sourcefn select(&mut self, measure: Option<MeasureId>)
fn select(&mut self, measure: Option<MeasureId>)
Show measure in this matrix, dropping any open cell editor. The pivot
state re-homes on the next Matrix::sync_axis_state.
Sourcefn sync_axis_state(&mut self, model: &Model)
fn sync_axis_state(&mut self, model: &Model)
Reset this matrix’s pivot state to its measure’s natural order when its measure has changed. Called each frame before rendering.
Sourcefn measure_dims(&self, model: &Model) -> Vec<CategoryId>
fn measure_dims(&self, model: &Model) -> Vec<CategoryId>
This matrix’s measure’s own dimensions, in declared order.
axis_order is not guaranteed to match them: restoring a view keeps a
saved order verbatim, and a CSV re-import can overwrite
measure.categories, so a stale category can linger in axis_order
while no longer being a dimension of the measure on screen. Such a
category must not influence this matrix’s grid at all — without this
filter, a stale PAGE category filtered to zero items made
pinned_pages_opt return None and blanked a grid whose every cell was
fully specified.
This is distinct from the case 6a4b862 fixed: a category that IS a real dimension of the measure and is filtered to nothing still yields zero lines, because those coordinates genuinely would be under-specified.
Sourcefn live_axis_slice(
&self,
model: &Model,
skip: usize,
take: usize,
) -> Vec<CategoryId>
fn live_axis_slice( &self, model: &Model, skip: usize, take: usize, ) -> Vec<CategoryId>
axis_order restricted to the measure’s dimensions, preserving the
user’s axis placement/order. The n_rows/n_cols split indexes
axis_order, so the split is applied first and each slice is filtered.
fn row_cats(&self, model: &Model) -> Vec<CategoryId>
fn col_cats(&self, model: &Model) -> Vec<CategoryId>
fn page_cats(&self, model: &Model) -> Vec<CategoryId>
Sourcefn pinned_pages(&self, model: &Model) -> Vec<(CategoryId, ItemId)>
fn pinned_pages(&self, model: &Model) -> Vec<(CategoryId, ItemId)>
The pinned (category, item) for each page dimension, from page_idx.
A page category filtered to zero items pins nothing and is simply
absent, so the result can be SHORTER than page_cats() — grid/cursor
code must use pinned_pages_opt, which rejects that case.
Sourcefn pinned_pages_opt(&self, model: &Model) -> Option<Vec<(CategoryId, ItemId)>>
fn pinned_pages_opt(&self, model: &Model) -> Option<Vec<(CategoryId, ItemId)>>
The pinned page items, or None if any page category is filtered to zero
items. In that case no item can be pinned for it, so every cell
coordinate would omit that category — under-specified for the measure’s
dimensions. Render nothing rather than a cell at such a key.
Sourcefn axis_tuples(
&self,
model: &Model,
cats: &[CategoryId],
) -> Vec<Vec<(ItemId, String)>>
fn axis_tuples( &self, model: &Model, cats: &[CategoryId], ) -> Vec<Vec<(ItemId, String)>>
The Cartesian product of cats’ filtered items, outer category first.
Each returned element is one axis line: a tuple of (ItemId, name) in
cats order. An empty cats yields a single empty tuple (a 1-line
axis, i.e. a scalar in that direction). Any empty category collapses the
product to nothing (no lines).
Sourcefn axis_item_lists(
&self,
model: &Model,
cats: &[CategoryId],
) -> Vec<Vec<(ItemId, String)>>
fn axis_item_lists( &self, model: &Model, cats: &[CategoryId], ) -> Vec<Vec<(ItemId, String)>>
The item lists (sorted, filtered) for each of cats, in order. Used to
virtualize the row axis: with these lists we can compute the total row
count as a product of lengths and decode the i-th row tuple on demand
(nth_tuple) without materializing the whole Cartesian product.
Sourcefn sorted_items(&self, model: &Model, c: CategoryId) -> Vec<(ItemId, String)>
fn sorted_items(&self, model: &Model, c: CategoryId) -> Vec<(ItemId, String)>
A category’s items sorted by id, honoring THIS matrix’s filters (shared by paging and grid rendering). A category without a filter shows all items; filtering is presentation only — it never touches model data.
Sourcefn set_axis(&mut self, model: &Model, category: CategoryId, axis: Axis)
fn set_axis(&mut self, model: &Model, category: CategoryId, axis: Axis)
Move category to axis in THIS matrix, appending it as the innermost
entry of that axis (so categories stack: dropping a second category on
Rows nests it under the first). Removes it from its previous axis. No-op
if the category is not among this measure’s dimensions. Pivoting is
formula-free re-projection (the Improv/Quantrix signature move).
Sourcefn rebuild_axis_order(
&mut self,
model: &Model,
rows: Vec<CategoryId>,
cols: Vec<CategoryId>,
pages: Vec<CategoryId>,
)
fn rebuild_axis_order( &mut self, model: &Model, rows: Vec<CategoryId>, cols: Vec<CategoryId>, pages: Vec<CategoryId>, )
Flatten the three axis groups back into axis_order + n_rows/n_cols,
and resize page_idx to the new page count.
Sourcefn pivot_rotate(&mut self, model: &Model)
fn pivot_rotate(&mut self, model: &Model)
Pivot: swap this matrix’s entire row stack with its entire column stack (Rows ↔ Columns), keeping pages put. For the classic one-per-axis case this is the familiar row/column swap; with stacked categories it swaps the two groups. No-op if there is nothing on either of rows/columns.
Sourcefn set_page(&mut self, model: &Model, dim_index: usize, item_index: usize)
fn set_page(&mut self, model: &Model, dim_index: usize, item_index: usize)
Set the pinned item index for page dimension dim_index (its position
among this matrix’s page dims), clamped to that dimension’s item count.
No-op if out of range.
Sourcefn page_first(&mut self, model: &Model, delta: isize)
fn page_first(&mut self, model: &Model, delta: isize)
Cycle this matrix’s first page dimension by delta (wrapping).
Sourcefn toggle_filter_item(
&mut self,
model: &Model,
category: CategoryId,
item: ItemId,
)
fn toggle_filter_item( &mut self, model: &Model, category: CategoryId, item: ItemId, )
Toggle whether item of category is shown in THIS matrix. On first
toggle the filter starts from all items minus this one; toggling so all
items are kept drops the filter. Presentation only.
Sourcefn clear_filter(&mut self, model: &Model, category: CategoryId)
fn clear_filter(&mut self, model: &Model, category: CategoryId)
Drop THIS matrix’s filter on category, showing all its items again.
Sourcefn grid_dims(&self, model: &Model) -> (usize, usize)
fn grid_dims(&self, model: &Model) -> (usize, usize)
This matrix’s (row_count, col_count). An axis with NO categories is
genuinely scalar and has ONE line; an axis whose category is filtered to
zero items has ZERO lines (an empty grid with headers — never a synthetic
line whose coordinate would omit that category). If a PAGE category is
filtered to zero items nothing is addressable at all, so both counts are
0. Matches render_grid.
Sourcefn move_cursor(&mut self, model: &Model, drow: isize, dcol: isize)
fn move_cursor(&mut self, model: &Model, drow: isize, dcol: isize)
Move this matrix’s cursor by (drow, dcol), clamped to its grid (never
out of range). Mirrors the TUI’s move_cursor.
Sourcefn step(&mut self, model: &Model, drow: isize, dcol: isize, extend: bool)
fn step(&mut self, model: &Model, drow: isize, dcol: isize, extend: bool)
An arrow step: extend (Shift held) keeps or sets the anchor at the
current cursor before moving, growing a range; otherwise it collapses.
Sourcefn selection(&self) -> (RangeInclusive<usize>, RangeInclusive<usize>)
fn selection(&self) -> (RangeInclusive<usize>, RangeInclusive<usize>)
The selected rectangle’s (rows, cols): anchor to cursor, inclusive.
Sourcefn selection_keys(&self, model: &Model) -> Vec<CoordKey> ⓘ
fn selection_keys(&self, model: &Model) -> Vec<CoordKey> ⓘ
The CoordKeys of every selected cell, row-major (see
Self::cursor_key for when there are none).
Sourcefn clamp_cursor(&mut self, model: &Model)
fn clamp_cursor(&mut self, model: &Model)
Clamp the cursor into this matrix’s grid (called after a pivot / measure switch that may have shrunk it).
Sourcefn cursor_key(&self, model: &Model) -> Option<CoordKey>
fn cursor_key(&self, model: &Model) -> Option<CoordKey>
The CoordKey of the cell under this matrix’s cursor, or None when
there is no cell there: any axis (row, column, or page) category filtered
to zero items means no coordinate fully specifies the measure’s
dimensions, so there is nothing to address. An axis with no categories at
all is scalar in that direction and still has one line.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Matrix
impl RefUnwindSafe for Matrix
impl Send for Matrix
impl Sync for Matrix
impl Unpin for Matrix
impl UnsafeUnpin for Matrix
impl UnwindSafe for Matrix
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> Data for Twhere
T: Clone + 'static,
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> NoneValue for Twhere
T: Default,
impl<T> NoneValue for Twhere
T: Default,
type NoneType = T
§fn null_value() -> T
fn null_value() -> T
§impl<T> ProgressEventTimestamp for T
impl<T> ProgressEventTimestamp for T
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
§impl<'a, S, T> Semigroup<&'a S> for Twhere
T: Semigroup<S>,
impl<'a, S, T> Semigroup<&'a S> for Twhere
T: Semigroup<S>,
§fn plus_equals(&mut self, rhs: &&'a S)
fn plus_equals(&mut self, rhs: &&'a S)
std::ops::AddAssign, for types that do not implement AddAssign.