Skip to main content

ImprovApp

Struct ImprovApp 

Source
pub struct ImprovApp {
Show 35 fields db: String, model: Model, engine: Option<Engine>, snapshot: Snapshot, matrices: Vec<Matrix>, focus: usize, current_view: Option<ViewId>, status: String, formula_buf: String, formula_for: Option<MeasureId>, formula_error: Option<Diagnostic>, locale: Locale, new_formula_line: String, new_formula_error: Option<Diagnostic>, view_name: String, browser_sel: Option<ViewId>, show_chart: bool, chart_line: bool, show_csv_wizard: bool, import_form: ImportForm, show_inspector: bool, fn_help: Browser, formula_target: FormulaEditor, show_browser: bool, quit_armed: bool, dirty: bool, save_token: SaveToken, last_title: String, status_shown: String, status_at: f64, menu: MenuState, export_form: ExportForm, undo_stack: Vec<Model>, redo_stack: Vec<Model>, formula_row_rects: Vec<(MeasureId, Rect)>,
}
Expand description

The running GUI application.

Fields§

§db: String

The store path (empty = in-memory scratch); used for saving.

§model: Model§engine: Option<Engine>

Live incremental engine over all derived measures, plus its snapshot.

§snapshot: Snapshot§matrices: Vec<Matrix>

The matrices on the canvas, in canvas order. Always non-empty: index 0 is the view’s primary matrix (see [View]’s flat fields), the rest are its placements.

§focus: usize

Which matrix has the keyboard: the only one that consumes grid shortcuts and the one every “selected measure” surface (formula editor, inspector, chart, status readout) reads from. Always a valid index into matrices.

§current_view: Option<ViewId>

The saved view this canvas was loaded from, if any — the highlighted document tab (see ImprovApp::document_tabs).

§status: String§formula_buf: String

The formula-editor text for the selected derived measure.

§formula_for: Option<MeasureId>

The measure whose formula formula_buf currently holds (so we reload the buffer when the selection changes).

§formula_error: Option<Diagnostic>

The last failed commit_formula’s error, located in its text (the span the editor underlines, the message and hint under the row). Set on a failed commit; cleared on a successful one or when the buffer is edited again.

§locale: Locale

The formula locale: how formulas are typed and shown (decimal comma, ;, translated names). Locale::from_env in the real app; English in tests so they do not depend on the machine’s locale.

§new_formula_line: String

Text buffer for the formula list’s last line, where Name = formula adds a derived measure.

§new_formula_error: Option<Diagnostic>

The last failed add’s error, located in new_formula_line (as formula_error is for the selected row).

§view_name: String

Text buffer for the “Save view” name field.

§browser_sel: Option<ViewId>

The model browser’s selected view row (single click). A double-click opens it; None or a deleted id falls back to the current view.

§show_chart: bool

Whether the read-only chart panel is shown, and its bar/line toggle.

§chart_line: bool§show_csv_wizard: bool

CSV/TSV import/export wizard: whether the panel is shown, and its two forms (see csv_wizard).

§import_form: ImportForm§show_inspector: bool

Whether the Info panel window is shown (off until Info ▸ Info Panel…).

§fn_help: Browser

The function help browser (Help ▸ Functions…, F1).

§formula_target: FormulaEditor

The formula editor last focused: where the help browser inserts.

§show_browser: bool

Whether the model browser window is shown (View ▸ Model Browser).

§quit_armed: bool

Quit was picked once on an unsaved in-memory model; the next Quit closes. Any other command disarms it.

§dirty: bool

The model has edits that are not persisted: an in-memory model with undo history, or a db model whose last autosave failed. Shown as the title’s •. (Canvas-layout changes since the last saved view are not tracked.)

§save_token: SaveToken

The store high-water this model was loaded/last saved at, so a save refuses to clobber another process’s writes (StoreError::Conflict).

§last_title: String

The native title last sent, so update only sends a change.

§status_shown: String

The status the status bar last drew, and when (egui time) it first drew it: a message clears itself STATUS_TTL seconds later.

§status_at: f64§menu: MenuState

The floating application menu’s position and open submenu.

§export_form: ExportForm§undo_stack: Vec<Model>

Undo/redo history: whole-Model snapshots, oldest first. undo_stack holds the states BEFORE each recorded mutation; redo_stack the states undone away. See ImprovApp::undo and UNDO_DEPTH.

§redo_stack: Vec<Model>§formula_row_rects: Vec<(MeasureId, Rect)>

Where each formula-list row landed in the last laid-out frame, as (measure, row rect) in display order. Recorded by ImprovApp::formula_list; empty until the pane has rendered. Layout output, not model state — the same role gutters plays for the grid, and what lets a headless test click a row where a user would.

Implementations§

Source§

impl ImprovApp

Source

pub fn load(db: &str) -> Result<ImprovApp, String>

Load a model from the store at db ("" = fresh in-memory model) and build the live engine over its derived measures.

Source

pub(crate) fn focused(&self) -> &Matrix

The focused matrix — the one the keyboard, the formula editor, the inspector, the chart and the selection readout all follow.

matrices is never empty (an empty model still has one matrix, showing no measure) and focus is clamped here rather than trusted, so removing a matrix can never leave a dangling focus.

Source

pub(crate) fn focused_mut(&mut self) -> &mut Matrix

The focused matrix, mutably (see Self::focused).

Source

pub fn set_focus(&mut self, i: usize)

Give the keyboard to matrix i (clamped). Focus is what makes grid shortcuts unambiguous with several matrices on one canvas.

Source

pub fn focus_index(&self) -> usize

Which matrix has the keyboard.

Source

pub fn matrix_count(&self) -> usize

How many matrices are on the canvas (always >= 1).

Source

pub fn add_matrix(&mut self, measure: Option<MeasureId>) -> usize

Add a matrix showing measure to the canvas, cascaded down-right from the last one so it does not land exactly on top of it, and focus it. Returns its index.

Canvas layout only — the model is untouched, so this is deliberately NOT an undo point; see Self::undo.

Source

pub fn remove_matrix(&mut self, i: usize) -> bool

Remove matrix i from the canvas. The LAST matrix is never removed (a canvas with none has nowhere to show a measure) — that is a no-op returning false. Layout only, like Self::add_matrix.

Source

pub(crate) fn selected(&self) -> Option<MeasureId>

Source

pub(crate) fn chart_axes_pub( &self, ) -> Option<(Vec<CategoryId>, Vec<CategoryId>, Vec<(CategoryId, ItemId)>)>

Row/column category stacks and pinned pages for the current pivot — the general (stacked) form the chart needs. Row/col tuples come from axis_tuples_pub; keys from cell_key_multi_pub.

None when a PAGE category is filtered to zero items: nothing can be pinned for it, so every cell key would omit that category. Same rule as the grid (grid_dims/cursor_key) — chart nothing rather than plot under-specified keys.

Reads the FOCUSED matrix: with several matrices on the canvas the chart follows the keyboard, as every other single-measure surface does.

Source

pub(crate) fn axis_tuples_pub( &self, cats: &[CategoryId], ) -> Vec<Vec<(ItemId, String)>>

The Cartesian product of cats’ filtered items (see axis_tuples), exposed for the chart. Empty cats -> one empty tuple.

Source

pub(crate) fn cell_key_multi_pub( &self, row_cats: &[CategoryId], row_tuple: &[(ItemId, String)], col_cats: &[CategoryId], col_tuple: &[(ItemId, String)], pinned: &[(CategoryId, ItemId)], ) -> CoordKey

The sorted CoordKey for a stacked cell (see cell_key_multi).

Source

pub(crate) fn values_for_pub( &self, measure: MeasureId, ) -> HashMap<CoordKey, f64>

Source

pub(crate) fn category_name_pub(&self, c: CategoryId) -> Option<String>

Source

fn values_for(&self, measure: MeasureId) -> HashMap<CoordKey, f64>

The numeric value map for a measure (input cells, or the derived snapshot), projected to numbers for the CHART, which can only plot numbers. Non-numeric cells are absent (a gap).

The GRID must not use this: a Text/Boolean/DateTime cell would render blank. Display goes through Self::cell_text, which is type-aware.

Source

fn cell_value(&self, measure: MeasureId, key: &CoordKey) -> Option<CellValue>

The typed value of a cell: the engine snapshot for a derived measure, the model’s stored Value (mapped to a CellValue) for an input one. None = the cell genuinely holds nothing.

Source

fn cell_text(&self, measure: MeasureId, key: &CoordKey) -> Option<String>

The display text for any cell — derived OR input — via CellValue’s Display: numbers as numbers, text as text, booleans as true/false, dates as RFC3339, error cells as #ERR. A cell is blank only when it holds no value, never merely because its value is not a number.

Source

pub fn set_cell( &mut self, measure: MeasureId, coord: CoordKey, value: Value, ) -> Result<(), String>

Set an input cell to a TYPED value and push the edit through the live engine, refreshing the snapshot.

value’s type must match the measure’s DECLARED value_type: a Text measure refuses a Value::Number (and vice versa) rather than silently replacing a typed cell with a number. Editing UI must parse by declared type — see Self::commit_cell_text.

Returns an error string on failure (derived cell, type mismatch, or a failed autosave); on failure the model, engine, and snapshot are left as they were.

Source

pub fn clear_cell( &mut self, measure: MeasureId, coord: CoordKey, ) -> Result<(), String>

Remove an input cell’s value (the empty-commit path) and push the retraction through the live engine. Same atomicity as Self::set_cell.

Source

fn write_cell( &mut self, measure: MeasureId, coord: CoordKey, value: Option<Value>, ) -> Result<(), String>

The one write path for input cells: Some(v) sets, None clears.

Source

fn push_engine( &mut self, measure: MeasureId, coord: &CoordKey, value: Option<&Value>, ) -> Result<(), String>

Push one input-cell edit through the live engine and adopt the recomputed snapshot. No engine (no derived measures) -> nothing to do.

A value of the measure’s type (number, boolean, text, date) enters the engine as that cell; anything else (text in a number measure, an error) CLEARS it, exactly what a rebuild would seed ([improv_engine::session::input_cell]). Without that, retyping a numeric cell as text would leave derived measures computing with a number the cell no longer holds.

Source

pub fn commit_cell_text( &mut self, measure: MeasureId, coord: CoordKey, text: &str, ) -> Result<String, String>

Commit a grid edit buffer for measure[coord], interpreting the text by the measure’s DECLARED value_type. Returns the status message.

  • An EMPTY (or whitespace-only) buffer clears the cell. Escape already cancels an edit, so an empty commit is the GUI’s only way to delete a value; treating it as a second cancel would make cells un-clearable.
  • Otherwise the declared type wins over the text’s shape: "42" typed into a Text measure stores Value::Text("42"), never a number.
  • Text that does not parse as the declared type is REJECTED (Err); the prior typed value stays exactly as it was.
Source

fn with_focused<R>(&mut self, f: impl FnOnce(&mut Matrix, &Model) -> R) -> R

Run f on the focused matrix with the model alongside it. Two disjoint field borrows (matrices mutably, model immutably), which is why the per-matrix methods take &Model instead of holding one.

Source

fn sync_axis_state(&mut self)

Re-home every matrix’s pivot state whose measure changed, and clamp the focus. Called each frame before rendering.

Source

pub fn pivot_rotate(&mut self)

Pivot the focused matrix: swap its row and column stacks (the toolbar’s and the grid header’s Pivot button).

Source

pub fn build_view(&self, id: ViewId, name: &str) -> Option<View>

Build a View capturing the WHOLE canvas: every matrix’s measure, axis order, pinned page items, filters and geometry. Presentation only.

Matrix 0 becomes the view’s primary matrix (its flat fields), the rest its placements — which is exactly what makes a one-matrix canvas save as a view a pre-canvas Improv would also understand. A matrix with no measure (an empty model) contributes nothing; if that leaves no matrices at all there is no view to build (None).

Source

fn next_view_id(&self) -> ViewId

The smallest unused view id (>= 1).

Source

pub fn save_view(&mut self, name: &str) -> Option<ViewId>

Save the current canvas as a named view: mint an id, add it to the model, and autosave. Returns the id, or None when nothing was saved (no measure on the canvas, blank name, or a failed store write — in which case the view is rolled back and status holds the error).

Source

pub fn apply_view(&mut self, view: &View)

Apply a saved View to the canvas: rebuild the matrices from its placements (measure, axis order, page items, filters, geometry), focus the first, and mark it the current document tab. Presentation only — measures and data untouched.

Legacy views load unchanged. A view written before canvases has no placements, so View::matrices() yields exactly one matrix — its flat fields — and the canvas is the single-matrix canvas it always was, down to the default geometry.

A placement whose measure no longer exists is dropped rather than shown blank; if that leaves nothing the view is refused and the canvas is left exactly as it was.

Source

pub fn document_tabs(&self) -> Vec<(ViewId, String, bool)>

The saved views as document tabs, in id order: (id, name, is current). The reference’s Welcome / Concepts / P&L Canvas strip — switching tabs switches canvases via Self::apply_view.

Source

pub fn open_view(&mut self, id: ViewId)

Switch to the saved view id (a document-tab click). No-op on an unknown id.

Source

pub fn toggle_csv_wizard(&mut self)

Toggle the CSV/TSV wizard panel. On first open, prefill the import form’s measure id with the next free one (mirrors next_view_id’s auto-assign pattern) if the field is still blank.

Source

pub fn run_csv_import(&mut self)

Run the import wizard: build an ImportSpec from self.import_form, import it into the live model, rebuild the engine (structure changed), autosave, and report the cell count (or the error) in self.status. Never panics — every failure path (bad form, bad file, CsvError) lands in self.status as a clear message.

Source

pub fn run_csv_export(&mut self)

Run the export wizard: resolve self.export_form and write the selected measure’s cells to disk, reporting the row count (or the error) in self.status. Never panics.

Source

fn rebuild_engine(&mut self)

Rebuild the live engine after a structural change (formula edit / new derived measure) and refresh the snapshot.

Source

pub fn cursor_key(&self) -> Option<CoordKey>

The CoordKey of the cell under the focused matrix’s cursor (see Matrix::cursor_key).

Source

pub fn cursor_is_editable(&self) -> bool

True if the focused matrix’s cursor cell is an editable input cell: its measure is an input measure AND the cursor addresses a real, fully-specified coordinate (see cursor_key). Derived measures are read-only.

Source

fn begin_edit_cursor(&mut self)

Begin editing the focused matrix’s cursor cell if it is editable, seeding its buffer with the current value. On a derived cell, sets the status message instead. Mirrors the TUI’s begin_edit.

Source

pub fn formula_source(&self, measure: MeasureId) -> Option<String>

The editable source text the formula editor shows for measure: symbolic DSL (Price * Quantity) whenever the v1 grammar can spell the formula exactly, else the controlled-English description (Price times Quantity).

None when there is nothing editable to show: measure is unknown or an input measure, or neither surface language can spell the formula. Since plan Step 4b the printer quotes awkward names ('Unit Price'), so a CSV-imported measure name is no longer a reason to go read-only; what remains unspellable is a formula shape the v1 grammar has no form for (see formula_dsl) whose controlled-English rendering also fails to round-trip. The bar renders that read-only instead of inviting a commit of text that no parser accepts.

Whatever this returns, Self::commit_formula accepts unchanged and leaves the identical AST — the invariant this pair exists to keep. It is checked here, by reparsing the candidate text through the very same Self::parse_formula_text commit_formula uses, so no printer/parser drift can quietly violate it.

Source

pub fn commit_formula( &mut self, measure: MeasureId, text: &str, ) -> Result<(), String>

Parse text as the RHS expression for an existing measure and make it derived (replacing any prior formula/input kind). Rebuilds the engine (structure changed), refreshes the snapshot, and autosaves.

Atomic: the change is validated on a candidate model (parse, then a real engine build) and only published once the engine is known good and the store write succeeded. On any failure the previous model, engine, and snapshot are all left intact and the error is returned; on a parse or compile error formula_error is also set (located in text, for the editor’s underline and the explanation under the row). It is cleared the moment text parses — a later save failure is a save error, not a fault at some place in text that has none.

text may be either the symbolic DSL (Price * Quantity) or the controlled English the formula editor displays (Price times Quantity); see Self::formula_source.

Source

pub fn set_formula_enabled( &mut self, measure: MeasureId, enabled: bool, ) -> Result<(), String>

Enable or disable measure’s formula — the reference’s per-formula checkmark, as a real model operation (see DISABLED_MARKER).

Disabled, the measure keeps its formula but computes nothing, and every dependent recomputes without it. Because the flag lives on the measure’s description, it survives save/reload unchanged.

Atomic and undoable on exactly the same terms as Self::commit_formula — it goes through Self::publish, so a toggle that cannot build or cannot save leaves the model, engine and snapshot untouched.

A no-op (still Ok) on an input measure or an unknown id: neither has a formula, so there is nothing to toggle and nothing to report.

Source

pub fn formula_rows(&self) -> Vec<FormulaRow>

Every formula in the model, in id order, as the formula pane lists them (see FormulaRow and Self::formula_list). Numbering is the caller’s: row i is displayed as i + 1, matching the reference’s 1-based list.

Derived measures only — an input measure has no formula, so it has no row.

Source

fn formula_row_error(&self, id: MeasureId, f: &Formula) -> Option<String>

The inline error for one formula row, or None if the formula is sound.

Two distinct failures, both of which the reference’s list shows in place rather than hiding:

  • It does not build — a type/dimension error or a cycle. Found by compiling this one formula, not by rebuilding the whole engine, so listing N formulas costs N compiles and no dataflow graphs.
  • It does not round-trip — no surface spelling of it parses back to the identical AST, so the text shown is a description, not source. Said plainly, because that row is the read-only one in the editor above.

A DISABLED formula is still checked: an unchecked row with a broken formula should say so, not look clean because it is not running.

Source

fn parse_formula_text(&self, text: &str) -> Result<Formula, ParseError>

Parse formula text in either supported surface language: the symbolic DSL first (the primary language, and the one whose error positions the formula bar highlights), then the controlled English of improv_nl_formula. The DSL error is what surfaces if both fail.

Source

fn publish(&mut self, candidate: Model) -> Result<(), String>

Replace the model with candidate, but only if it builds a working engine and persists: build first, save second, publish third. On failure nothing is touched (self keeps its model, engine, and snapshot). On success the replaced model becomes an undo point.

ponytail: validation is a full model clone + a full engine rebuild per structural edit (briefly two live engines). That is the price of atomicity at GUI edit rates; if formula editing ever needs to be interactive on huge models, validate against a compile-only pass (engine::compiler::compile_formula + derived_build_order) instead of a real Engine::new.

Source

fn swap_model(&mut self, candidate: Model) -> Result<Model, String>

The atomic model swap behind Self::publish and undo/redo: build an engine for candidate, save it, and only then adopt it (model, engine, snapshot together). Returns the model it replaced. Records no history — callers decide (an undo must not become an undo point of its own).

Source

fn record_history(&mut self, prior: Model)

Record prior (the model as it was before the mutation that just succeeded) as an undo point, and drop the redo stack — a fresh mutation makes the undone future unreachable (standard semantics).

Source

pub fn can_undo(&self) -> bool

True when there is a recorded state to undo (for enabling UI).

Source

pub fn can_redo(&self) -> bool

True when there is an undone state to redo.

Source

pub fn undo(&mut self) -> Result<(), String>

Undo the last model mutation: restore the previous model, rebuild the engine and snapshot from it, and persist it (so undo-then-quit does not resurrect the undone state). The current model moves to the redo stack.

An empty history is a no-op, not an error. A failed restore (a store write failure) leaves everything as it was and keeps the undo point, so the step is not lost; the error is returned for the caller to surface.

Source

pub fn redo(&mut self) -> Result<(), String>

Redo the last undone mutation (the mirror of Self::undo).

Source

fn undo_with_status(&mut self)

Self::undo, with the outcome reported in the status line (the UI path). A store-write failure during undo is surfaced, never swallowed; an empty history says so rather than looking like a broken key.

Source

fn redo_with_status(&mut self)

Self::redo, with the outcome reported in the status line.

Source

fn after_history_restore(&mut self)

Re-point the transient UI state at the restored model: a measure the restored model no longer has (an undone CSV import, an undone new derived measure) cannot stay on a matrix or half-edited. Applied to EVERY matrix on the canvas, not just the focused one — an undo that removes a measure must not leave another matrix pointing at it.

Source

pub fn add_derived_measure( &mut self, name: &str, text: &str, ) -> Result<MeasureId, String>

Create a new derived measure named name with RHS text. Categories are inferred as the union of the referenced measures’ categories (same rule as the CLI’s add-derived). Rebuilds the engine and autosaves. On any failure (parse error, duplicate name, engine build failure, failed store write) the model, engine, and snapshot are unchanged.

A parse or compile error is also left in new_formula_error, located in text.

Source

fn next_measure_id(&self) -> u32

The smallest unused measure id (>= 1).

Source

fn save(&mut self) -> Result<(), String>

Autosave the model to the store when db is set. In-memory ("") models skip saving. Returns the failure message so callers can report it — never overwrite a save failure with a success message.

Source

pub fn revert_to_saved(&mut self) -> Result<(), String>

File ▸ Revert to Saved: reload the model from the store (with a fresh save token) and rebuild the engine. After a save conflict this is the way forward: the edit that conflicted was already rolled back, and the reload picks up the other writer’s changes. Undo history is dropped (it describes a model that no longer matches the store). On failure nothing changes.

Source

fn inspector_data(&self, measure: MeasureId) -> Option<InspectorData>

Read-only inspector facts for measure (see inspector panel).

Source§

impl ImprovApp

Source

fn handle_command_keys(&mut self, ctx: &Context)

App-wide key equivalents (menu::key_command): Ctrl/Cmd+S saves, Ctrl/Cmd+Z undoes, Ctrl/Cmd+Shift+Z and Ctrl/Cmd+Y redo, Ctrl/Cmd+H hides, Ctrl/Cmd+Q quits. Gated by the same grid_keys_enabled predicate the grid uses, so the chords never fire while a cell editor or any other text field owns the keyboard. Handled here (not in handle_grid_keys) because the grid isn’t rendered when no measure is selected — which is exactly the state an undone import can leave behind.

Source

fn menu_bar(&mut self, ctx: &Context, ws: Rect)

Draw the application menu (menu::show) and run what it returns. It starts at the top-left of the workspace ws, clear of the panels.

Source

fn command_enabled(&self, c: Command) -> bool

Whether c can run now: the menu draws the rest disabled.

Source

fn run_command(&mut self, ctx: &Context, c: Command)

Run a menu command or key equivalent.

Source

fn quit_allowed(&mut self) -> bool

Whether Quit may close now. Edits to an in-memory model can’t be saved, so the first Quit only warns and arms; a second one closes.

Source

fn model_browser(&mut self, ctx: &Context, ws: Rect)

The model browser (spec §4.2): a NeXT window, <file> - Model, at the workspace’s bottom left, listing Categories (with their items), Measures (input vs derived; clicking one selects it) and Views. View ▸ Model Browser shows and hides it.

Source

fn view_description(&self, v: &View) -> String

A saved view’s description for the browser: its primary measure, the row and column categories, then any pages — Revenue by Time × Product; pages: Scenario. Computed (a view stores no description).

Source

fn views_section(&mut self, ui: &mut Ui)

Views section at the top of the model browser (spec §4.2): a white list of saved views as Name - Worksheet rows with a worksheet icon (click selects, double-click opens), a description of the selected view, then a name field + “Save view” button that captures the current CANVAS.

This is the NeXTSTEP reference’s model navigator (docs/reviews/refs/improv-next-nested-headers.jpg: a Model of Divisional Report window listing Worksheet 1 / Worksheet 2), and the same set the application menu’s View submenu lists. The current view is highlighted in both.

Source

fn info_panel(&mut self, ctx: &Context, ws: Rect)

The Info panel (spec §4.7): a NeXT panel window at the workspace’s top right showing the selected measure’s facts. Info ▸ Info Panel… opens it; its close box closes it.

Source

fn function_help(&mut self, ctx: &Context, ws: Rect)

Help ▸ Functions… (F1): a NeXT window at the workspace’s right with a search line over the built-in functions, the matches grouped by category (each with its signature), and the selected one’s details. Insert (or a double-click, or Enter in the search line) puts NAME( into the formula last edited, at its caret.

Source

fn insert_function(&mut self, ctx: &Context, name: &str)

Insert NAME( into the formula editor last focused (the selected row’s while it is shown, else the Name = formula line), at its caret, and give that editor the keyboard.

Source

fn formula_list(&mut self, ui: &mut Ui, mi: usize)

The Step 2 formula pane (plan docs/reviews/2026-09-22-gui-reconstruction-plan.md): the whole model’s logic as one persistent, numbered, individually-checkable list — the shape every reference shows (✓ 1. Gross Margin = Sum of Revenue - Sum of Cost of Sales), and the thing a single-line bar for the selected measure alone can never be.

Reference-faithful in the parts that carry meaning:

  • a split bar across the top with a Calc button that recomputes;
  • one row per derived measure: a checkbox (the ✓), a bold 1-based number, then Target = formula;
  • the selected row highlighted, and clicking any row selecting that measure so the grid follows;
  • inline error text under a row whose formula does not compile.

The selected row is the editor (Self::formula_editor), and a last line takes Name = formula to add one; there is no separate bar.

Source

fn formula_editor( &mut self, ui: &mut Ui, mid: MeasureId, target: &str, row_error: Option<&str>, ) -> Response

The selected row’s in-place editor: Name = and a highlighted single-line edit over formula_buf. Enter commits (errors show under the row), Esc reverts. A formula with no editable spelling shows its English text read-only (see Self::formula_source).

Source

fn new_formula_line(&mut self, ui: &mut Ui, editing: bool)

The formula list’s last line: type Name = formula, press Enter, and a derived measure is added and selected. editing (the focused window) gets the global id formula_new; other windows scope theirs.

Source

fn add_from_formula_line(&mut self)

Add the measure the new-formula line spells (Name = formula), selecting it; on failure keep the line and locate the error in it.

Source

fn chart_panel(&mut self, ctx: &Context)

Bottom-right chart panel (shown only when the “Chart” toggle is on): a read-only bar chart of the selected measure with a bar/line toggle.

Source

fn csv_wizard_panel(&mut self, ctx: &Context)

A toggle-able window: CSV/TSV import (left) + export (right), mirroring the CLI’s import-csv/export-csv exactly. All fields are plain text (no file-dialog dependency, same pattern as the “Save view” name box) — see csv_wizard::build_import_spec/build_export_args for the pure validation this panel drives.

Source

fn csv_import_form(&mut self, ui: &mut Ui)

The import half of the wizard: file path, delimiter/header toggles, measure id (auto-assigned)/name, value column, and a repeatable list of dimension-mapping rows with +/- controls.

Source

fn csv_export_form(&mut self, ui: &mut Ui)

The export half of the wizard: a measure combo box (from the current model, like the browser’s measure list), a target path, and a delimiter toggle.

Source

fn status_bar(&mut self, ctx: &Context)

The status bar (plan Step 5): the status message on the left and the selection-aggregate readout on the right, which is where Quantrix puts its Sum.

Source

pub fn selection_readout(&self) -> String

The selection aggregate for the focused matrix, as the status bar shows it: the reference’s Sum.

Selection is ONE cell today (there is no range selection yet), so the sum over the selection is that cell’s number. Written as a sum rather than as “the cell’s value” because that is what it means and what a range selection will widen to: Self::selection_sum takes the cursor cell’s numeric value, and a non-numeric or absent cell contributes nothing, so the readout says Sum \u2014 rather than inventing a zero.

Source

pub fn selection_sum(&self) -> Option<f64>

The numeric sum over the focused matrix’s selection, or None when nothing numeric is selected (no measure, no addressable cell, an empty cell, or a text/boolean/date/error cell). See Self::selection_readout.

Source

fn canvas_panel(&mut self, ctx: &Context) -> Rect

The workspace: a #555555 desktop with each matrix floating on it as its own NeXT worksheet window (spec §5). The workspace does not scroll; each window’s grid scrolls itself, and Matrix::rect holds the window rect relative to the workspace’s top-left, so views persist it.

The focused matrix’s window is made key before the windows are shown, so a code-driven focus change (add_matrix, apply_view) shows as the key title bar; a press on another window then makes it key, and that is read back into the focus.

Source

fn matrix_window(&mut self, ctx: &Context, ws: Rect, mi: usize) -> bool

Matrix mi as a worksheet window on the workspace ws: filter shelf, the gutter-framed grid, and the formula list (with Calc) below it. Returns false when its close box was clicked (shown only while there is more than one matrix).

Source

pub fn window_title(&self, mi: usize) -> String

A worksheet window’s title (spec §4.3): Sales: North - Worksheet - demo, the file being the store’s stem or Untitled when in memory.

Source

fn sync_native_title(&mut self, ctx: &Context)

Send the native title when it changed. An in-memory model is dirty whenever it has undo history (nothing ever persists it).

Source

fn native_title(&self) -> String

The native window title: Improv — <file>, plus • while dirty.

Source

fn file_name(&self) -> &str

The store’s file stem, or Untitled when in memory.

Source

pub fn matrix_title(&self, mi: usize) -> String

A matrix’s title text: its measure’s name, plus the pinned page items after a colon (Sales: North) \u2014 the reference’s Property Financials: Virginia Ave.

Source

fn gutter_frame(&mut self, ui: &mut Ui, mi: usize)

The grid inside its margin gutters — the Improv pivot surface.

Reference: docs/reviews/refs/improv-pivot-gesture.jpg. Category tiles are docked in gutters that frame the table, and the pivot gesture IS dragging a tile from one gutter to another:

  +- corner --+- TOP gutter: column-axis tiles, stacked ---+
  | (blank)   |  || Travel v                               |
  |           |  || Hours  v                               |
  +-----------+----------------------------------------------+
  | LEFT      |                                              |
  | gutter:   |    the table (ruled headers + cells)         |
  | row-axis  |                                              |
  | tiles     |                                              |
  +-----------+----------------------------------------------+
  | WELL (bottom-left, inline with the horizontal scrollbar  |
  | in the reference): page/unplaced tiles + page selectors  |
  +----------------------------------------------------------+

Adjacency is constructed, not hoped for: the gutters are nested [egui::SidePanel]/[egui::TopBottomPanel]s inside this Ui, and a panel’s contract is that it consumes its strip and moves the parent cursor to its far edge, so whatever is laid out next starts exactly at that edge. Each panel’s own rect and the table’s min_rect are recorded in that MATRIX’s own gutters field, debug_assert!ed against gutters_frame_table every frame, and asserted in the headless layout test — per matrix, so every matrix on the canvas frames its own table.

Gutter thickness along the docked axis is left to the panels: they size to their content (a panel re-reads its extent from the previous frame’s PanelState), so stacking another tile makes the gutter grow rather than clip it against a hard-coded height. NeXT layout: page tiles (each over its item field) top-left, column tiles top-right, row tiles in the bottom strip; there is no gutter beside the table.

Source

fn gutter_style() -> Frame

The NeXT-groove frame a gutter is painted with.

Source

fn gutter_drop_zone( ui: &mut Ui, axis: Axis, fill: bool, moves: &mut Vec<(CategoryId, Axis)>, contents: impl FnOnce(&mut Ui, &mut Vec<(CategoryId, Axis)>), )

Make the whole of ui a drop target for category tiles: a category released here is recorded as a move to axis, which Matrix::set_axis applies (appending to that axis’ stack). The zone is at least one tile tall so an empty axis is still droppable; fill stretches it across the rest of the strip (never along a self-sizing panel’s own axis, which would ratchet the panel bigger every frame).

The drop highlight is painted only while a tile is in flight; at rest the zone is just its tiles, and a non-filling empty zone (Pages) takes no room until a drag needs a target.

Source

fn page_field( ui: &mut Ui, uid: u64, c: CategoryId, items: &[String], cur: usize, ) -> Option<usize>

A page category’s item field: a white drop-down showing the current item. Returns the index picked this frame, if any.

Source

fn tile( ui: &mut Ui, model: &Model, m: &Matrix, c: CategoryId, from: Axis, moves: &mut Vec<(CategoryId, Axis)>, freqs: &mut Vec<FilterReq>, )

One category tile: the NeXT chip — a fixed 17 px dark-gray box with the category name in white. The whole chip is a drag source, so dragging it into another gutter re-pivots (the drop is handled by ImprovApp::gutter_drop_zone). Right-click opens a “Move to …” menu, the drag-free (and accessible) way to re-pivot, and below it a checkbox per item filtering c in matrix m (plus “Show all”). A filtered category’s tile reads Name*.

Source

fn handle_grid_keys(&mut self, ui: &Ui, mi: usize)

Handle keyboard navigation for the grid. Arrow keys (and h/j/k/l) move the cursor; Enter/F2 begin editing the cursor cell; [/] and PageUp/PageDown page the first page dimension; n/N cycle the selected measure; Ctrl/Cmd+Z undoes and Ctrl/Cmd+Shift+Z (or Ctrl+Y) redoes. Swallowed while a cell text field is open, or while ANY other egui widget owns keyboard focus (so typing in the formula editor, the new-measure form, the CSV wizard, or the view-name box never also drives the grid). n/N (not Tab) drive measure cycling because egui reserves Tab for widget focus.

Only the FOCUSED matrix consumes them (plan Step 3): mi is the matrix being rendered, and a matrix that does not have the keyboard returns immediately — otherwise one arrow key would move every matrix’s cursor at once.

Source

fn page_first(&mut self, delta: isize)

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

Source

fn cycle_measure(&mut self, delta: isize)

Cycle the focused matrix’s measure by delta (wrapping) in id order.

Source

fn render_grid( &mut self, ui: &mut Ui, mi: usize, measure: MeasureId, ) -> Option<ScrollAreaOutput<(ScrollAreaOutput<()>, Rect)>>

Render matrix mi’s measure as a 2-D pivot grid using THAT MATRIX’s axis order: its row stack on rows, its column stack on columns, the rest pinned to its selected page items. Input cells are editable; derived read-only.

Every read here goes through matrix mi, so two matrices showing the same measure with different pivots render differently, and only the focused one takes keystrokes (see Self::handle_grid_keys).

Source§

impl ImprovApp

Source

pub fn chart_series(&self) -> ChartData

Build the chart data for the FOCUSED matrix’s measure from the current snapshot, honoring THAT matrix’s active filters and pinned page items (same visible-item set as its grid). The x-axis is the full Cartesian product of the ROW categories (x-label = joined tuple item names, e.g. “2024Q1 / North”); one series per full COLUMN tuple (series name = joined column tuple names). The single-category case is just the 1-length-tuple case. A 1-D grid (no column categories) is a single unnamed series. Non-numeric / missing cells are gaps (None). Pure: no egui, no mutation.

Empty ChartData (no x, no series) whenever nothing is addressable: no measure on the focused matrix, an axis category filtered to zero items, or a page category with no pinnable item. render_chart reports that as “no data” — the chart never plots an under-specified key. Matches Matrix::grid_dims.

Trait Implementations§

Source§

impl App for ImprovApp

Source§

fn update(&mut self, ctx: &Context, _frame: &mut Frame)

Called each time the UI needs repainting, which may be many times per second. Read more
§

fn save(&mut self, _storage: &mut dyn Storage)

Called on shutdown, and perhaps at regular intervals. Allows you to save state. Read more
§

fn on_exit(&mut self, _gl: Option<&Context>)

Called once on shutdown, after [Self::save]. Read more
§

fn auto_save_interval(&self) -> Duration

Time between automatic calls to [Self::save]
§

fn clear_color(&self, _visuals: &Visuals) -> [f32; 4]

Background color values for the app, e.g. what is sent to gl.clearColor. Read more
§

fn persist_egui_memory(&self) -> bool

Controls whether or not the egui memory (window positions etc) will be persisted (only if the “persistence” feature is enabled).
§

fn raw_input_hook(&mut self, _ctx: &Context, _raw_input: &mut RawInput)

A hook for manipulating or filtering raw input before it is processed by [Self::update]. 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,

§

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