Skip to main content

Matrix

Struct Matrix 

Source
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: u64

Keys 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: usize

How 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: usize

Keyboard 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: CanvasRect

Its 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

Source

fn new(model: &Model, measure: Option<MeasureId>) -> Matrix

A matrix showing measure with its natural axis order and the default canvas geometry.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

fn row_cats(&self, model: &Model) -> Vec<CategoryId>

Source

fn col_cats(&self, model: &Model) -> Vec<CategoryId>

Source

fn page_cats(&self, model: &Model) -> Vec<CategoryId>

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

fn page_first(&mut self, model: &Model, delta: isize)

Cycle this matrix’s first page dimension by delta (wrapping).

Source

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.

Source

fn clear_filter(&mut self, model: &Model, category: CategoryId)

Drop THIS matrix’s filter on category, showing all its items again.

Source

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.

Source

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.

Source

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.

Source

fn selection(&self) -> (RangeInclusive<usize>, RangeInclusive<usize>)

The selected rectangle’s (rows, cols): anchor to cursor, inclusive.

Source

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).

Source

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).

Source

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§

Source§

impl Clone for Matrix

Source§

fn clone(&self) -> Matrix

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Matrix

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Matrix

Source§

fn default() -> Matrix

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> Data for T
where T: Clone + 'static,

§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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 T
where T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> ProgressEventTimestamp for T
where T: Data + Debug + Any,

§

fn as_any(&self) -> &(dyn Any + 'static)

Upcasts this ProgressEventTimestamp to Any. Read more
§

fn type_name(&self) -> &'static str

Returns the name of the concrete type of this object. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<'a, S, T> Semigroup<&'a S> for T
where T: Semigroup<S>,

§

fn plus_equals(&mut self, rhs: &&'a S)

The method of std::ops::AddAssign, for types that do not implement AddAssign.
§

impl<T> SerializableAny for T
where T: 'static + Any + Clone + for<'a> Send + Sync,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more