Skip to main content

densa_arrow/
live.rs

1use densa_data::{LiveTable, RowRef, View, ViewId};
2use densa_grid::{CellStyle, CellText, Color, Column, GridSource, Palette};
3
4use crate::table::{ArrowTable, CellState, TableError};
5
6/// Shows views of a `densa_data::LiveTable` in the grid, formatted like an [`ArrowTable`].
7///
8/// It keeps a formatter for each of the live table's batches; [`view`](Self::view) brings them
9/// up to date first, which allocates only when batches were added or compacted since the
10/// last call. Columns are sized from the first rows, as in [`ArrowTable`]. Given another
11/// table (or a clone), it rebuilds its formatters; for a table with another schema it also
12/// resets its column settings.
13#[derive(Debug, Clone)]
14pub struct LiveGrid {
15    mirror: ArrowTable,
16    /// The table object (`LiveTable::instance`) and generation the formatters were built for.
17    table: (u64, u64),
18}
19
20impl LiveGrid {
21    /// A grid for views of `table`, with formatters for its current batches.
22    pub fn new(table: &LiveTable) -> Self {
23        let mut grid = Self {
24            mirror: ArrowTable::new(table.schema().clone()),
25            table: (table.instance(), table.generation()),
26        };
27        grid.sync(table);
28        grid
29    }
30
31    /// Grid columns, as [`ArrowTable::columns`].
32    pub fn columns(&self) -> Vec<Column> {
33        self.mirror.columns()
34    }
35
36    /// As [`ArrowTable::set_decimals`].
37    ///
38    /// # Errors
39    /// [`TableError::NoSuchColumn`] if there is no such column.
40    pub fn set_decimals(
41        &mut self,
42        column: usize,
43        decimals: Option<usize>,
44    ) -> Result<(), TableError> {
45        self.mirror.set_decimals(column, decimals)
46    }
47
48    /// As [`ArrowTable::set_column_chars`].
49    ///
50    /// # Errors
51    /// [`TableError::NoSuchColumn`] if there is no such column.
52    pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
53        self.mirror.set_column_chars(column, chars)
54    }
55
56    /// A [`GridSource`] showing view `view` of `table` from `first_row` (scrolled), or
57    /// `None` if the view does not exist. Call it each frame, after the table's changes.
58    pub fn view<'a>(
59        &'a mut self,
60        table: &'a LiveTable,
61        view: ViewId,
62        first_row: usize,
63        palette: &Palette,
64    ) -> Option<LiveGridView<'a>> {
65        self.sync(table);
66        Some(LiveGridView {
67            mirror: &self.mirror,
68            rows: table.view(view)?,
69            first_row,
70            text: palette.text,
71            dim: palette.dim_text,
72        })
73    }
74
75    /// Whether rows have sized the columns (they are sized once, by the first rows).
76    pub(crate) fn is_sized(&self) -> bool {
77        self.mirror.is_sized()
78    }
79
80    /// Whether the formatters match `table`'s batches, so a [`RowRef`] of it reads the
81    /// right values.
82    pub(crate) fn is_synced(&self, table: &LiveTable) -> bool {
83        self.table == (table.instance(), table.generation())
84            && self.mirror.stored_batches() == table.batches().len()
85    }
86
87    /// Writes the value of `column` at `at` (a row of the synced table) into `text`.
88    pub(crate) fn write(&self, at: RowRef, column: usize, text: &mut CellText) -> CellState {
89        self.mirror.write_at(at.batch, at.row, column, text)
90    }
91
92    /// Adds formatters for new batches, or rebuilds them after a compaction or for another
93    /// table.
94    pub(crate) fn sync(&mut self, table: &LiveTable) {
95        let current = (table.instance(), table.generation());
96        if current != self.table {
97            if self.mirror.schema() != table.schema() {
98                self.mirror = ArrowTable::new(table.schema().clone());
99            }
100            self.mirror.clear_batches();
101            self.table = current;
102        }
103        let new = table
104            .batches()
105            .get(self.mirror.stored_batches()..)
106            .unwrap_or_default();
107        for batch in new {
108            // Same schema and never empty (a live table stores no empty batch), so a push
109            // cannot fail; if it ever did, show nothing rather than misaligned rows.
110            if self.mirror.push(batch.clone()).is_err() {
111                self.mirror.clear_batches();
112                return;
113            }
114        }
115    }
116}
117
118/// A live view scrolled to a first row, as a grid's data. Cheap to create each frame.
119#[derive(Debug, Clone, Copy)]
120pub struct LiveGridView<'a> {
121    mirror: &'a ArrowTable,
122    rows: View<'a>,
123    first_row: usize,
124    text: Color,
125    dim: Color,
126}
127
128impl GridSource for LiveGridView<'_> {
129    fn rows(&self) -> usize {
130        self.rows.len().saturating_sub(self.first_row)
131    }
132
133    fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
134        let state = match self.rows.row(self.first_row.saturating_add(row)) {
135            Some(at) => self.mirror.write_at(at.batch, at.row, column, text),
136            None => CellState::OutOfRange,
137        };
138        let color = if state == CellState::Value {
139            self.text
140        } else {
141            self.dim
142        };
143        CellStyle::new(color)
144    }
145}