Skip to content

Table

Headless tabular data model. Provides rows, columns, cells, merged cells, ordering, and swap operations — without any grid rendering, formula engine, or cell type constraints.


Overview

flowchart TD
    subgraph TBL[Table]
        TB[Table<br/>rows + columns + cells + merged_cells + ordering]
        RW[Row<br/>id + name]
        CL[Column<br/>id + name]
        CE[CellId<br/>row + column]
        CV[CellValue<br/>None/Text/Int/Float/Bool]
    end
    TBL -->|operations| OPS[TableOperation]
    OPS -->|apply| OUT[TableOutcome]

Key Types

Type Fields Purpose
Table rows, columns, row_order, column_order, cells, merged_cells, id counters Container owning all tabular data
Row id: RowId, name: Option<String> Row definition with optional name
Column id: ColumnId, name: Option<String> Column definition with optional name
CellId row: RowId, column: ColumnId Composite cell address (carries both row and column)
CellValue None, Text(String), Int(i64), Float(f64), Bool(bool) Value stored in a cell
RowId u64 (opaque) Stable row identifier
ColumnId u64 (opaque) Stable column identifier
TableError RowNotFound, ColumnNotFound, InvalidRowIndex, InvalidColumnIndex Error type
TableOperation 13 variants Value-type operation intent
TableOutcome 13 variants Result with undo data

Table Methods

Row management

Method Returns Description
insert_row(name) RowId Add a row (appended to row_order)
restore_row(row, cells) RowId Restore a row and its cells (for undo)
remove_row(id) Result<(Row, Vec<(CellId, CellValue)>), TableError> Remove row + all its cells, returns both
rename_row(id, name) Result<Option<String>, TableError> Rename, returns previous name
move_row(id, new_index) Result<usize, TableError> Reorder row, returns previous index
swap_rows(a, b) () Swap two rows by position
row(id) Option<&Row> Read access
rows() impl Iterator<Item = &Row> Iterate rows in order
row_ids() &[RowId] Ordered row ids
row_count() usize Number of rows
contains_row(id) bool Existence check

Column management

Method Returns Description
insert_column(name) ColumnId Add a column (appended to column_order)
restore_column(column, cells) ColumnId Restore a column and its cells (for undo)
remove_column(id) Result<(Column, Vec<(CellId, CellValue)>), TableError> Remove column + all its cells
rename_column(id, name) Result<Option<String>, TableError> Rename, returns previous name
move_column(id, new_index) Result<usize, TableError> Reorder column, returns previous index
swap_columns(a, b) () Swap two columns by position
column(id) Option<&Column> Read access
columns() impl Iterator<Item = &Column> Iterate columns in order
column_ids() &[ColumnId] Ordered column ids
column_count() usize Number of columns
contains_column(id) bool Existence check

Cell management

Method Returns Description
set_cell_value(row, column, value) Result<CellValue, TableError> Set cell value, returns previous
clear_cell(row, column) Result<CellValue, TableError> Clear cell (set to None), returns previous
cell(row, column) &CellValue Get cell value (None if unset or missing)
cell_by_id(id) &CellValue Get cell by CellId
cell_count() usize Number of set cells

Merged cells

Method Returns Description
merge_cells(from, to) Result<(), TableError> Merge a rectangular region; top-left becomes master
unmerge_cells(master) Result<(), TableError> Unmerge all cells merged into master
is_merged(id) bool Whether a cell is merged into another
master_cell(id) CellId The master cell (self if not merged)

Other

Method Returns Description
clear() () Remove all rows, columns, cells, merges (id counters preserved)
is_empty() bool Whether table has no rows AND no columns
len() usize Same as cell_count()

CellId Design

CellId is a composite struct carrying both RowId and ColumnId:

pub struct CellId {
    pub row: RowId,
    pub column: ColumnId,
}

This means given a CellId, you can immediately find both its row and column without a separate lookup. This is used by remove_row (which scans cells by CellId.row) and merge_cells (which constructs CellIds from the row/column ordering).


Row and Column Ordering

Rows and columns have a defined order via row_order: Vec<RowId> and column_order: Vec<ColumnId>, separate from the HashMap storage. This allows:

  • O(1) lookup by id (HashMap)
  • Defined iteration order (Vec)
  • Reordering (move_row, move_column)
  • Swapping (swap_rows, swap_columns)
let mut table = Table::new();
let r1 = table.insert_row(Some("Header".into()));
let r2 = table.insert_row(Some("Data 1".into()));
let r3 = table.insert_row(Some("Data 2".into()));

assert_eq!(table.row_ids(), &[r1, r2, r3]);

table.swap_rows(0, 2); // swap by position
assert_eq!(table.row_ids(), &[r3, r2, r1]);

Merged Cells

Merge a rectangular region from from (top-left) to to (bottom-right). The top-left cell becomes the master; all others are marked as merged:

Before merge:          After merge_cells(from=(r1,c1), to=(r2,c2)):
┌───┬───┬───┐         ┌───┬───┬───┐
│ A │ B │ C │         │       │ C │
├───┼───┼───┤   →     ├───────┼───┤
│ D │ E │ F │         │       │ F │
└───┴───┴───┘         └───────┴───┘
  ↑   ↑   ↑             ↑       ↑
 r1c1 r1c2 r1c3       master   r1c3
  • is_merged(r1c2) → true
  • is_merged(r1c1) → false (master is not "merged")
  • master_cell(r2c2) → r1c1

unmerge_cells(master) frees all cells merged into that master.


CellValue

pub enum CellValue {
    None,           // Empty / unset
    Text(String),   // Text content
    Int(i64),       // Integer
    Float(f64),     // Floating point
    Bool(bool),     // Boolean
}

Every cell accepts any CellValue. The Table does not enforce per-column type constraints. A Domain Engine that needs "column A only accepts Int" uses the validation utility to enforce that rule.

Cell formulas and calculation dependencies are not part of the Table environment. A Domain Engine can define cell dependencies using the Relations system.


Operations

Operation Outcome Undo
InsertRow { name } RowInserted { id } Remove the row
InsertColumn { name } ColumnInserted { id } Remove the column
RemoveRow { id } RowRemoved { row, cells } Restore row + cells
RemoveColumn { id } ColumnRemoved { column, cells } Restore column + cells
SetCellValue { row, column, value } CellValueSet { previous } Restore previous value
ClearCell { row, column } CellCleared { previous } Restore previous value
MoveRow { id, new_index } RowMoved { previous_index } Restore previous index
MoveColumn { id, new_index } ColumnMoved { previous_index } Restore previous index
RenameRow { id, name } RowRenamed { previous_name } Restore previous name
RenameColumn { id, name } ColumnRenamed { previous_name } Restore previous name
MergeCells { from, to } CellsMerged Unmerge
UnmergeCells { master } CellsUnmerged Re-merge
ClearTable TableCleared (limited — no stored data to restore)

IntoIterator

// Iterate over cells: (CellId, CellValue)
for (cell_id, value) in &table {
    println!("{:?} = {:?}", cell_id, value);
}

Example

use app_shell::domain_foundation::environments::table::*;

let mut table = Table::new();

// Create structure
let header = table.insert_row(Some("Name".into()));
let row1 = table.insert_row(Some("Alice".into()));
let row2 = table.insert_row(Some("Bob".into()));
let name_col = table.insert_column(Some("Name".into()));
let age_col = table.insert_column(Some("Age".into()));

// Set cell values
table.set_cell_value(header, name_col, CellValue::Text("Name".into())).unwrap();
table.set_cell_value(header, age_col, CellValue::Text("Age".into())).unwrap();
table.set_cell_value(row1, age_col, CellValue::Int(30)).unwrap();
table.set_cell_value(row2, age_col, CellValue::Int(25)).unwrap();

assert_eq!(table.cell_count(), 4);
assert_eq!(table.cell(row1, age_col), &CellValue::Int(30));

// Merge cells
table.merge_cells(
    CellId { row: header, column: name_col },
    CellId { row: header, column: age_col },
).unwrap();
assert!(table.is_merged(CellId { row: header, column: age_col }));

// Swap rows
table.swap_rows(1, 2);
assert_eq!(table.row_ids(), &[header, row2, row1]);

What Table Does NOT Own

Concern Owner
Column type constraints (e.g., "column A = Int only") Domain Engine (via validation utility)
Cell formulas / calculation dependencies Domain Engine (via Relations)
Grid rendering, cell editors, headers UI Layer
Frozen rows/columns UI Layer
Column width / row height UI Layer
CSV import/export Domain Engine / External
Data validation rules Domain Engine (via validation utility)
Sorting by column Domain Engine (via sorting utility)
Filtering visible rows Domain Engine (via filtering utility)

Next Steps