Skip to main content

densa_arrow/
model.rs

1use densa_data::{DataError, Filter, LiveTable, Order, RecordId, View, ViewId, ViewSpec};
2use densa_grid::{CellStyle, CellText, Column, GridModel, Palette, SortOrder};
3
4use crate::live::LiveGrid;
5use crate::table::{CellState, TableError};
6
7/// A `densa_data::LiveTable` as the model of a grid widget (`densa_widgets::DataGrid`): one
8/// view of the table, sorted when the user clicks a column title and filtered by the app,
9/// with rows keyed by their record so a selection follows its row as the data changes.
10///
11/// Change the table through [`table_mut`](Self::table_mut); the view follows each change
12/// incrementally. Cells are formatted as in [`ArrowTable`](crate::ArrowTable), from
13/// formatters brought up to date by [`GridModel::prepare`], which the grid calls before it
14/// paints; until then, rows from batches added since show empty cells rather than stale
15/// values. Columns are sized from the first rows to arrive and widened when later numbers
16/// no longer fit, and the grid takes in new sizes by itself
17/// ([`GridModel::columns_revision`]), keeping widths the user dragged.
18///
19/// Costs: a frame without changes allocates nothing; one after upserts allocates a
20/// formatter per new batch, and after a compaction rebuilds them all. Sorting or filtering
21/// builds a new view, `O(n log n)` in the table's rows.
22#[derive(Debug)]
23pub struct LiveModel {
24    table: LiveTable,
25    grid: LiveGrid,
26    columns: Vec<Column>,
27    view: ViewId,
28    filter: Option<Filter>,
29    sort: Option<(usize, SortOrder)>,
30    /// The table object (`LiveTable::instance`) shown.
31    instance: u64,
32    /// Bumped when the columns change, and when the table is replaced.
33    columns_revision: u64,
34    generation: u64,
35}
36
37impl LiveModel {
38    /// A model showing every row of `table`, unsorted (in row-id order: the order keys
39    /// arrived, until deleted keys' places are reused).
40    ///
41    /// # Errors
42    /// Those of `LiveTable::add_view`; none for a view of every row, unsorted.
43    pub fn new(mut table: LiveTable) -> Result<Self, DataError> {
44        let view = table.add_view(ViewSpec::new())?;
45        let grid = LiveGrid::new(&table);
46        Ok(Self {
47            columns: grid.columns(),
48            grid,
49            view,
50            filter: None,
51            sort: None,
52            instance: table.instance(),
53            table,
54            columns_revision: 0,
55            generation: 0,
56        })
57    }
58
59    /// The table.
60    pub fn table(&self) -> &LiveTable {
61        &self.table
62    }
63
64    /// The table, to upsert, delete or compact; the view follows. Another table put in its
65    /// place is shown from the next paint, filtered and sorted the same way where its columns
66    /// allow, and the grid starts over (no selection, its columns read again).
67    pub fn table_mut(&mut self) -> &mut LiveTable {
68        &mut self.table
69    }
70
71    /// The rows shown, in order (`None` only between replacing the table and the next
72    /// paint).
73    pub fn view(&self) -> Option<View<'_>> {
74        self.table.view(self.view)
75    }
76
77    /// The record of the row at `index`, as shown.
78    pub fn record(&self, index: usize) -> Option<RecordId> {
79        self.view()?.record(index)
80    }
81
82    /// The filter rows are shown by.
83    pub fn filter(&self) -> Option<&Filter> {
84        self.filter.as_ref()
85    }
86
87    /// Shows only the rows `filter` keeps (all of them for `None`), in the same sort. A
88    /// selected row the filter hides is no longer selected.
89    ///
90    /// # Errors
91    /// Those of `LiveTable::add_view` for the filter (an unknown column, a comparison
92    /// with a value of another type); the model is unchanged then.
93    pub fn set_filter(&mut self, filter: Option<Filter>) -> Result<(), DataError> {
94        self.replace_view(filter, self.sort)
95    }
96
97    /// As [`ArrowTable::set_decimals`](crate::ArrowTable::set_decimals); the grid takes in
98    /// the new width.
99    ///
100    /// # Errors
101    /// [`TableError::NoSuchColumn`] if there is no such column.
102    pub fn set_decimals(
103        &mut self,
104        column: usize,
105        decimals: Option<usize>,
106    ) -> Result<(), TableError> {
107        self.grid.set_decimals(column, decimals)?;
108        self.read_columns();
109        Ok(())
110    }
111
112    /// As [`ArrowTable::set_column_chars`](crate::ArrowTable::set_column_chars); the grid
113    /// takes in the new width.
114    ///
115    /// # Errors
116    /// [`TableError::NoSuchColumn`] if there is no such column.
117    pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
118        self.grid.set_column_chars(column, chars)?;
119        self.read_columns();
120        Ok(())
121    }
122
123    /// Reads the columns again and tells the grid.
124    fn read_columns(&mut self) {
125        self.columns = self.grid.columns();
126        self.columns_revision = self.columns_revision.wrapping_add(1);
127    }
128
129    /// Builds the view for `filter` and `sort`, then drops the old one; on error nothing
130    /// changes.
131    fn replace_view(
132        &mut self,
133        filter: Option<Filter>,
134        sort: Option<(usize, SortOrder)>,
135    ) -> Result<(), DataError> {
136        let view = self.table.add_view(self.spec(filter.clone(), sort))?;
137        self.table.remove_view(self.view);
138        self.view = view;
139        self.filter = filter;
140        self.sort = sort;
141        Ok(())
142    }
143
144    /// The view spec for `filter` and `sort` (a sort the table cannot do is left out: see
145    /// [`sort_name`](Self::sort_name)).
146    fn spec(&self, filter: Option<Filter>, sort: Option<(usize, SortOrder)>) -> ViewSpec {
147        let mut spec = ViewSpec::new();
148        if let Some(filter) = filter {
149            spec = spec.filter(filter);
150        }
151        if let Some((column, order)) = sort {
152            if let Some(name) = self.sort_name(column) {
153                let order = match order {
154                    SortOrder::Descending => Order::Descending,
155                    _ => Order::Ascending,
156                };
157                spec = spec.sort_by(name, order);
158            }
159        }
160        spec
161    }
162
163    /// The name views sort `column` by: none if the table has no such column, or if an
164    /// earlier column has the same name (views find columns by name).
165    fn sort_name(&self, column: usize) -> Option<String> {
166        let schema = self.table.schema();
167        let name = schema.fields().get(column)?.name();
168        let (first, _) = schema.column_with_name(name)?;
169        (first == column).then(|| name.clone())
170    }
171
172    /// Shows the table put in place of the last one: the same filter and sort where its
173    /// columns allow (else none), and a fresh start for the grid.
174    fn adopt_table(&mut self) {
175        self.instance = self.table.instance();
176        self.generation = self.generation.wrapping_add(1);
177        if self
178            .sort
179            .is_some_and(|(column, _)| self.sort_name(column).is_none())
180        {
181            self.sort = None;
182        }
183        if self.view().is_none() {
184            let spec = self.spec(self.filter.clone(), self.sort);
185            let view = self.table.add_view(spec).or_else(|_| {
186                self.filter = None;
187                self.sort = None;
188                self.table.add_view(ViewSpec::new())
189            });
190            if let Ok(view) = view {
191                self.view = view;
192            }
193        }
194    }
195}
196
197impl GridModel for LiveModel {
198    fn columns(&self) -> &[Column] {
199        &self.columns
200    }
201
202    fn rows(&self) -> usize {
203        self.view().map_or(0, |view| view.len())
204    }
205
206    fn cell(&self, row: usize, column: usize, text: &mut CellText, palette: &Palette) -> CellStyle {
207        let at = self.view().and_then(|view| view.row(row));
208        let state = match at {
209            Some(at) if self.grid.is_synced(&self.table) => self.grid.write(at, column, text),
210            _ => CellState::OutOfRange,
211        };
212        let color = if state == CellState::Value {
213            palette.text
214        } else {
215            palette.dim_text
216        };
217        CellStyle::new(color)
218    }
219
220    fn prepare(&mut self) {
221        let replaced = self.instance != self.table.instance();
222        if replaced {
223            self.adopt_table();
224        }
225        let widths = self.grid.widths();
226        self.grid.sync(&self.table);
227        // Another table (maybe of another schema), or rows sized or widened the columns:
228        // read them again (allocates, rarely: a width only grows).
229        if replaced || widths != self.grid.widths() {
230            self.read_columns();
231        }
232    }
233
234    fn sort(&mut self, column: usize, order: SortOrder) -> bool {
235        self.sort_name(column).is_some()
236            && self
237                .replace_view(self.filter.clone(), Some((column, order)))
238                .is_ok()
239    }
240
241    fn sort_state(&self) -> Option<(usize, SortOrder)> {
242        self.sort
243    }
244
245    fn row_key(&self, row: usize) -> Option<u64> {
246        self.record(row).map(RecordId::to_bits)
247    }
248
249    fn row_of_key(&self, key: u64) -> Option<usize> {
250        self.view()?.position(RecordId::from_bits(key))
251    }
252
253    fn columns_revision(&self) -> u64 {
254        self.columns_revision
255    }
256
257    fn generation(&self) -> u64 {
258        self.generation
259    }
260}