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: StringThe 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: usizeWhich 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: StringThe 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: LocaleThe 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: StringText 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: StringText 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: boolWhether the read-only chart panel is shown, and its bar/line toggle.
chart_line: bool§show_csv_wizard: boolCSV/TSV import/export wizard: whether the panel is shown, and its two
forms (see csv_wizard).
import_form: ImportForm§show_inspector: boolWhether the Info panel window is shown (off until Info ▸ Info Panel…).
fn_help: BrowserThe function help browser (Help ▸ Functions…, F1).
formula_target: FormulaEditorThe formula editor last focused: where the help browser inserts.
show_browser: boolWhether the model browser window is shown (View ▸ Model Browser).
quit_armed: boolQuit was picked once on an unsaved in-memory model; the next Quit closes. Any other command disarms it.
dirty: boolThe 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: SaveTokenThe store high-water this model was loaded/last saved at, so a save
refuses to clobber another process’s writes (StoreError::Conflict).
last_title: StringThe native title last sent, so update only sends a change.
status_shown: StringThe status the status bar last drew, and when (egui time) it first
drew it: a message clears itself STATUS_TTL seconds later.
status_at: f64The 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
impl ImprovApp
Sourcepub fn load(db: &str) -> Result<ImprovApp, String>
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.
Sourcepub(crate) fn focused(&self) -> &Matrix
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.
Sourcepub(crate) fn focused_mut(&mut self) -> &mut Matrix
pub(crate) fn focused_mut(&mut self) -> &mut Matrix
The focused matrix, mutably (see Self::focused).
Sourcepub fn set_focus(&mut self, i: usize)
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.
Sourcepub fn focus_index(&self) -> usize
pub fn focus_index(&self) -> usize
Which matrix has the keyboard.
Sourcepub fn matrix_count(&self) -> usize
pub fn matrix_count(&self) -> usize
How many matrices are on the canvas (always >= 1).
Sourcepub fn add_matrix(&mut self, measure: Option<MeasureId>) -> usize
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.
Sourcepub fn remove_matrix(&mut self, i: usize) -> bool
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.
pub(crate) fn selected(&self) -> Option<MeasureId>
Sourcepub(crate) fn chart_axes_pub(
&self,
) -> Option<(Vec<CategoryId>, Vec<CategoryId>, Vec<(CategoryId, ItemId)>)>
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.
Sourcepub(crate) fn axis_tuples_pub(
&self,
cats: &[CategoryId],
) -> Vec<Vec<(ItemId, String)>>
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.
Sourcepub(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
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).
pub(crate) fn values_for_pub( &self, measure: MeasureId, ) -> HashMap<CoordKey, f64>
pub(crate) fn category_name_pub(&self, c: CategoryId) -> Option<String>
Sourcefn values_for(&self, measure: MeasureId) -> HashMap<CoordKey, f64>
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.
Sourcefn cell_value(&self, measure: MeasureId, key: &CoordKey) -> Option<CellValue>
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.
Sourcefn cell_text(&self, measure: MeasureId, key: &CoordKey) -> Option<String>
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.
Sourcepub fn set_cell(
&mut self,
measure: MeasureId,
coord: CoordKey,
value: Value,
) -> Result<(), String>
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.
Sourcepub fn clear_cell(
&mut self,
measure: MeasureId,
coord: CoordKey,
) -> Result<(), String>
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.
Sourcefn write_cell(
&mut self,
measure: MeasureId,
coord: CoordKey,
value: Option<Value>,
) -> Result<(), String>
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.
Sourcefn push_engine(
&mut self,
measure: MeasureId,
coord: &CoordKey,
value: Option<&Value>,
) -> Result<(), String>
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.
Sourcepub fn commit_cell_text(
&mut self,
measure: MeasureId,
coord: CoordKey,
text: &str,
) -> Result<String, String>
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 storesValue::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.
Sourcefn with_focused<R>(&mut self, f: impl FnOnce(&mut Matrix, &Model) -> R) -> R
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.
Sourcefn sync_axis_state(&mut self)
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.
Sourcepub fn pivot_rotate(&mut self)
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).
Sourcepub fn build_view(&self, id: ViewId, name: &str) -> Option<View>
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).
Sourcefn next_view_id(&self) -> ViewId
fn next_view_id(&self) -> ViewId
The smallest unused view id (>= 1).
Sourcepub fn save_view(&mut self, name: &str) -> Option<ViewId>
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).
Sourcepub fn apply_view(&mut self, view: &View)
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.
Sourcepub fn document_tabs(&self) -> Vec<(ViewId, String, bool)>
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.
Sourcepub fn open_view(&mut self, id: ViewId)
pub fn open_view(&mut self, id: ViewId)
Switch to the saved view id (a document-tab click). No-op on an
unknown id.
Sourcepub fn toggle_csv_wizard(&mut self)
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.
Sourcepub fn run_csv_import(&mut self)
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.
Sourcepub fn run_csv_export(&mut self)
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.
Sourcefn rebuild_engine(&mut self)
fn rebuild_engine(&mut self)
Rebuild the live engine after a structural change (formula edit / new derived measure) and refresh the snapshot.
Sourcepub fn cursor_key(&self) -> Option<CoordKey>
pub fn cursor_key(&self) -> Option<CoordKey>
The CoordKey of the cell under the focused matrix’s cursor (see
Matrix::cursor_key).
Sourcepub fn cursor_is_editable(&self) -> bool
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.
Sourcefn begin_edit_cursor(&mut self)
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.
Sourcepub fn formula_source(&self, measure: MeasureId) -> Option<String>
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.
Sourcepub fn commit_formula(
&mut self,
measure: MeasureId,
text: &str,
) -> Result<(), String>
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.
Sourcepub fn set_formula_enabled(
&mut self,
measure: MeasureId,
enabled: bool,
) -> Result<(), String>
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.
Sourcepub fn formula_rows(&self) -> Vec<FormulaRow>
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.
Sourcefn formula_row_error(&self, id: MeasureId, f: &Formula) -> Option<String>
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.
Sourcefn parse_formula_text(&self, text: &str) -> Result<Formula, ParseError>
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.
Sourcefn publish(&mut self, candidate: Model) -> Result<(), String>
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.
Sourcefn swap_model(&mut self, candidate: Model) -> Result<Model, String>
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).
Sourcefn record_history(&mut self, prior: Model)
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).
Sourcepub fn undo(&mut self) -> Result<(), String>
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.
Sourcepub fn redo(&mut self) -> Result<(), String>
pub fn redo(&mut self) -> Result<(), String>
Redo the last undone mutation (the mirror of Self::undo).
Sourcefn undo_with_status(&mut self)
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.
Sourcefn redo_with_status(&mut self)
fn redo_with_status(&mut self)
Self::redo, with the outcome reported in the status line.
Sourcefn after_history_restore(&mut self)
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.
Sourcepub fn add_derived_measure(
&mut self,
name: &str,
text: &str,
) -> Result<MeasureId, String>
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.
Sourcefn next_measure_id(&self) -> u32
fn next_measure_id(&self) -> u32
The smallest unused measure id (>= 1).
Sourcefn save(&mut self) -> Result<(), String>
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.
Sourcepub fn revert_to_saved(&mut self) -> Result<(), String>
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.
Sourcefn inspector_data(&self, measure: MeasureId) -> Option<InspectorData>
fn inspector_data(&self, measure: MeasureId) -> Option<InspectorData>
Read-only inspector facts for measure (see inspector panel).
Source§impl ImprovApp
impl ImprovApp
Sourcefn handle_command_keys(&mut self, ctx: &Context)
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.
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.
Sourcefn command_enabled(&self, c: Command) -> bool
fn command_enabled(&self, c: Command) -> bool
Whether c can run now: the menu draws the rest disabled.
Sourcefn run_command(&mut self, ctx: &Context, c: Command)
fn run_command(&mut self, ctx: &Context, c: Command)
Run a menu command or key equivalent.
Sourcefn quit_allowed(&mut self) -> bool
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.
Sourcefn model_browser(&mut self, ctx: &Context, ws: Rect)
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.
Sourcefn view_description(&self, v: &View) -> String
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).
Sourcefn views_section(&mut self, ui: &mut Ui)
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.
Sourcefn info_panel(&mut self, ctx: &Context, ws: Rect)
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.
Sourcefn function_help(&mut self, ctx: &Context, ws: Rect)
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.
Sourcefn insert_function(&mut self, ctx: &Context, name: &str)
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.
Sourcefn formula_list(&mut self, ui: &mut Ui, mi: usize)
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
Calcbutton that recomputes; - one row per derived measure: a checkbox (the
✓), a bold 1-based number, thenTarget = 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.
Sourcefn formula_editor(
&mut self,
ui: &mut Ui,
mid: MeasureId,
target: &str,
row_error: Option<&str>,
) -> Response
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).
Sourcefn new_formula_line(&mut self, ui: &mut Ui, editing: bool)
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.
Sourcefn add_from_formula_line(&mut self)
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.
Sourcefn chart_panel(&mut self, ctx: &Context)
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.
Sourcefn csv_wizard_panel(&mut self, ctx: &Context)
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.
Sourcefn csv_import_form(&mut self, ui: &mut Ui)
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.
Sourcefn csv_export_form(&mut self, ui: &mut Ui)
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.
Sourcefn status_bar(&mut self, ctx: &Context)
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.
Sourcepub fn selection_readout(&self) -> String
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.
Sourcepub fn selection_sum(&self) -> Option<f64>
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.
Sourcefn canvas_panel(&mut self, ctx: &Context) -> Rect
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.
Sourcefn matrix_window(&mut self, ctx: &Context, ws: Rect, mi: usize) -> bool
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).
Sourcepub fn window_title(&self, mi: usize) -> String
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.
Sourcefn sync_native_title(&mut self, ctx: &Context)
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).
Sourcefn native_title(&self) -> String
fn native_title(&self) -> String
The native window title: Improv — <file>, plus • while dirty.
Sourcepub fn matrix_title(&self, mi: usize) -> String
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.
Sourcefn gutter_frame(&mut self, ui: &mut Ui, mi: usize)
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.
Sourcefn gutter_style() -> Frame
fn gutter_style() -> Frame
The NeXT-groove frame a gutter is painted with.
Sourcefn gutter_drop_zone(
ui: &mut Ui,
axis: Axis,
fill: bool,
moves: &mut Vec<(CategoryId, Axis)>,
contents: impl FnOnce(&mut Ui, &mut Vec<(CategoryId, Axis)>),
)
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.
Sourcefn page_field(
ui: &mut Ui,
uid: u64,
c: CategoryId,
items: &[String],
cur: usize,
) -> Option<usize>
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.
Sourcefn tile(
ui: &mut Ui,
model: &Model,
m: &Matrix,
c: CategoryId,
from: Axis,
moves: &mut Vec<(CategoryId, Axis)>,
freqs: &mut Vec<FilterReq>,
)
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*.
Sourcefn handle_grid_keys(&mut self, ui: &Ui, mi: usize)
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.
Sourcefn page_first(&mut self, delta: isize)
fn page_first(&mut self, delta: isize)
Cycle the focused matrix’s first page dimension by delta (wrapping).
Sourcefn cycle_measure(&mut self, delta: isize)
fn cycle_measure(&mut self, delta: isize)
Cycle the focused matrix’s measure by delta (wrapping) in id order.
Sourcefn render_grid(
&mut self,
ui: &mut Ui,
mi: usize,
measure: MeasureId,
) -> Option<ScrollAreaOutput<(ScrollAreaOutput<()>, Rect)>>
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
impl ImprovApp
Sourcepub fn chart_series(&self) -> ChartData
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
impl App for ImprovApp
Source§fn update(&mut self, ctx: &Context, _frame: &mut Frame)
fn update(&mut self, ctx: &Context, _frame: &mut Frame)
§fn save(&mut self, _storage: &mut dyn Storage)
fn save(&mut self, _storage: &mut dyn Storage)
§fn auto_save_interval(&self) -> Duration
fn auto_save_interval(&self) -> Duration
Self::save]§fn clear_color(&self, _visuals: &Visuals) -> [f32; 4]
fn clear_color(&self, _visuals: &Visuals) -> [f32; 4]
gl.clearColor. Read more§fn persist_egui_memory(&self) -> bool
fn persist_egui_memory(&self) -> bool
§fn raw_input_hook(&mut self, _ctx: &Context, _raw_input: &mut RawInput)
fn raw_input_hook(&mut self, _ctx: &Context, _raw_input: &mut RawInput)
Self::update]. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for ImprovApp
impl !UnwindSafe for ImprovApp
impl Freeze for ImprovApp
impl Send for ImprovApp
impl Sync for ImprovApp
impl Unpin for ImprovApp
impl UnsafeUnpin for ImprovApp
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
§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 moreimpl<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.