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