egui_data_table/
viewer.rs

1use std::borrow::Cow;
2
3use egui::{Key, KeyboardShortcut, Modifiers};
4pub use egui_extras::Column as TableColumnConfig;
5use tap::prelude::Pipe;
6
7#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DecodeErrorBehavior {
9    /// Skip the cell and continue decoding.
10    SkipCell,
11
12    /// Skip the whole row
13    SkipRow,
14
15    /// Stop decoding and return error.
16    #[default]
17    Abort,
18}
19
20/// A trait for encoding/decoding row data. Any valid UTF-8 string can be used for encoding,
21/// however, as csv is used for clipboard operations, it is recommended to serialize data in simple
22/// string format as possible.
23pub trait RowCodec<R> {
24    type DeserializeError;
25
26    /// Creates a new empty row for decoding
27    fn create_empty_decoded_row(&mut self) -> R;
28
29    /// Tries encode column data of given row into a string. As the cell for CSV row is already
30    /// occupied, if any error or unsupported data is found for that column, just empty out the
31    /// destination string buffer.
32    fn encode_column(&mut self, src_row: &R, column: usize, dst: &mut String);
33
34    /// Tries decode column data from a string into a row.
35    fn decode_column(
36        &mut self,
37        src_data: &str,
38        column: usize,
39        dst_row: &mut R,
40    ) -> Result<(), DecodeErrorBehavior>;
41}
42
43/// A placeholder codec for row viewers that not require serialization.
44impl<R> RowCodec<R> for () {
45    type DeserializeError = ();
46
47    fn create_empty_decoded_row(&mut self) -> R {
48        unimplemented!()
49    }
50
51    fn encode_column(&mut self, src_row: &R, column: usize, dst: &mut String) {
52        let _ = (src_row, column, dst);
53        unimplemented!()
54    }
55
56    fn decode_column(
57        &mut self,
58        src_data: &str,
59        column: usize,
60        dst_row: &mut R,
61    ) -> Result<(), DecodeErrorBehavior> {
62        let _ = (src_data, column, dst_row);
63        unimplemented!()
64    }
65}
66
67/// The primary trait for the spreadsheet viewer.
68// TODO: When lifetime for `'static` is stabilized; remove the `static` bound.
69pub trait RowViewer<R>: 'static {
70    /// Number of columns. Changing this will completely invalidate the table rendering status,
71    /// including undo histories. Therefore, frequently changing this value is discouraged.
72    fn num_columns(&mut self) -> usize;
73
74    /// Name of the column. This can be dynamically changed.
75    fn column_name(&mut self, column: usize) -> Cow<'static, str> {
76        Cow::Borrowed(
77            &" 0 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132"
78                [(column % 10 * 2).pipe(|x| x..x + 2)],
79        )
80    }
81
82    /// Tries to create a codec for the row (de)serialization. If this returns `Some`, it'll use
83    /// the system clipboard for copy/paste operations.
84    ///
85    /// `is_encoding` parameter is provided to determine if we're creating the codec as encoding
86    /// mode or decoding mode.
87    ///
88    /// It is just okay to choose not to implement both encoding and decoding; returning `None`
89    /// conditionally based on `is_encoding` parameter is also valid. It is guaranteed that created
90    /// codec will be used only for the same mode during its lifetime.
91    fn try_create_codec(&mut self, is_encoding: bool) -> Option<impl RowCodec<R>> {
92        let _ = is_encoding;
93        None::<()>
94    }
95
96    /// Returns the rendering configuration for the column.
97    fn column_render_config(
98        &mut self,
99        column: usize,
100        is_last_visible_column: bool,
101    ) -> TableColumnConfig {
102        let _ = column;
103        if is_last_visible_column {
104            TableColumnConfig::remainder().at_least(24.0)
105        } else {
106            TableColumnConfig::auto().resizable(true)
107        }
108    }
109
110    /// Returns if given column is 'sortable'
111    fn is_sortable_column(&mut self, column: usize) -> bool {
112        let _ = column;
113        false
114    }
115
116    /// Returns if a given cell is 'editable'.
117    /// 
118    /// i.e.
119    /// * true to allow editing of a cell
120    /// * false to disable editing of a cell
121    fn is_editable_cell(&mut self, column: usize, row: usize, row_value: &R) -> bool {
122        let _ = column;
123        let _ = row;
124        let _ = row_value;
125        true
126    }
127
128    /// Returns if row insertions are allowed.
129    fn allow_row_insertions(&mut self) -> bool {
130        true
131    }
132
133    /// Returns if row deletions are allowed.
134    fn allow_row_deletions(&mut self) -> bool {
135        true
136    }
137
138    /// Compare two column contents for sort.
139    fn compare_cell(&self, row_a: &R, row_b: &R, column: usize) -> std::cmp::Ordering {
140        let _ = (row_a, row_b, column);
141        std::cmp::Ordering::Equal
142    }
143
144    /// Get hash value of a filter. This is used to determine if the filter has changed.
145    fn row_filter_hash(&mut self) -> &impl std::hash::Hash {
146        &()
147    }
148
149    /// Filter single row. If this returns false, the row will be hidden.
150    fn filter_row(&mut self, row: &R) -> bool {
151        let _ = row;
152        true
153    }
154
155    /// Display values of the cell. Any input will be consumed before table renderer;
156    /// therefore any widget rendered inside here is read-only.
157    ///
158    /// To deal with input, use `cell_edit` method. If you need to deal with drag/drop,
159    /// see [`RowViewer::on_cell_view_response`] which delivers resulting response of
160    /// containing cell.
161    fn show_cell_view(&mut self, ui: &mut egui::Ui, row: &R, column: usize);
162
163    /// Use this to check if given cell is going to take any dropped payload / use as drag
164    /// source.
165    fn on_cell_view_response(
166        &mut self,
167        row: &R,
168        column: usize,
169        resp: &egui::Response,
170    ) -> Option<Box<R>> {
171        let _ = (row, column, resp);
172        None
173    }
174
175    /// Edit values of the cell.
176    fn show_cell_editor(
177        &mut self,
178        ui: &mut egui::Ui,
179        row: &mut R,
180        column: usize,
181    ) -> Option<egui::Response>;
182
183    /// Set the value of a column in a row.
184    fn set_cell_value(&mut self, src: &R, dst: &mut R, column: usize);
185
186    /// In the write context that happens outside of `show_cell_editor`, this method is
187    /// called on every cell value editions.
188    fn confirm_cell_write_by_ui(
189        &mut self,
190        current: &R,
191        next: &R,
192        column: usize,
193        context: CellWriteContext,
194    ) -> bool {
195        let _ = (current, next, column, context);
196        true
197    }
198
199    /// Before removing each row, this method is called to confirm the deletion from the
200    /// viewer. This won't be called during the undo/redo operation!
201    fn confirm_row_deletion_by_ui(&mut self, row: &R) -> bool {
202        let _ = row;
203        true
204    }
205
206    /// Create a new empty row.
207    fn new_empty_row(&mut self) -> R;
208
209    /// Create a new empty row under the given context.
210    fn new_empty_row_for(&mut self, context: EmptyRowCreateContext) -> R {
211        let _ = context;
212        self.new_empty_row()
213    }
214
215    /// Create duplication of existing row.
216    ///
217    /// You may want to override this method for more efficient duplication.
218    fn clone_row(&mut self, row: &R) -> R {
219        let mut dst = self.new_empty_row();
220        for i in 0..self.num_columns() {
221            self.set_cell_value(row, &mut dst, i);
222        }
223        dst
224    }
225
226    /// Create duplication of existing row for insertion.
227    fn clone_row_for_insertion(&mut self, row: &R) -> R {
228        self.clone_row(row)
229    }
230
231    /// Create duplication of existing row for clipboard. Useful when you need to specify
232    /// different behavior for clipboard duplication. (e.g. unset transient flag)
233    fn clone_row_as_copied_base(&mut self, row: &R) -> R {
234        self.clone_row(row)
235    }
236
237    /// Called when a cell is selected/highlighted.
238    fn on_highlight_cell(&mut self, row: &R, column: usize) {
239        let _ = (row, column);
240    }
241
242    /// Called when a row selected/highlighted status changes.
243    fn on_highlight_change(&mut self, highlighted: &[&R], unhighlighted: &[&R]) {
244        let (_, _) = (highlighted, unhighlighted);
245    }
246    
247    /// Called when a row is updated, including when undoing/redoing
248    fn on_row_updated(&mut self, row_index: usize, new_row: &R, old_row: &R) {
249        let (_, _, _) = (row_index, new_row, old_row);
250    }
251
252    /// Called when a row has been inserted, including when undoing/redoing
253    fn on_row_inserted(&mut self, row_index: usize, row: &R) {
254        let (_, _) = (row_index, row);
255    }
256
257    /// Called when a row has been removed, including when undoing/redoing
258    fn on_row_removed(&mut self, row_index: usize, row: &R) {
259        let (_, _) = (row_index, row);
260    }
261
262    /// Return hotkeys for the current context.
263    fn hotkeys(&mut self, context: &UiActionContext) -> Vec<(egui::KeyboardShortcut, UiAction)> {
264        self::default_hotkeys(context)
265    }
266
267    /// If you want to keep UI state on storage(i.e. persist over sessions), return true from this
268    /// function.
269    #[cfg(feature = "persistency")]
270    fn persist_ui_state(&self) -> bool {
271        false
272    }
273}
274
275/* ------------------------------------------- Context ------------------------------------------ */
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278#[non_exhaustive]
279pub enum CellWriteContext {
280    /// Value is being pasted/duplicated from different row.
281    Paste,
282
283    /// Value is being cleared by cut/delete operation.
284    Clear,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288#[non_exhaustive]
289pub enum EmptyRowCreateContext {
290    /// Row is created to be used as simple default template.
291    Default,
292
293    /// Row is created to be used as explicit `empty` value when deletion
294    DeletionDefault,
295
296    /// Row is created to be inserted as a new row.
297    InsertNewLine,
298}
299
300/* ------------------------------------------- Hotkeys ------------------------------------------ */
301
302/// Base context for determining current input state.
303#[derive(Debug, Clone)]
304#[non_exhaustive]
305pub struct UiActionContext {
306    pub cursor: UiCursorState,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum UiCursorState {
311    Idle,
312    Editing,
313    SelectOne,
314    SelectMany,
315}
316
317impl UiCursorState {
318    pub fn is_idle(&self) -> bool {
319        matches!(self, Self::Idle)
320    }
321
322    pub fn is_editing(&self) -> bool {
323        matches!(self, Self::Editing)
324    }
325
326    pub fn is_selecting(&self) -> bool {
327        matches!(self, Self::SelectOne | Self::SelectMany)
328    }
329}
330
331/* ----------------------------------------- Ui Actions ----------------------------------------- */
332
333/// Represents a user interaction, calculated from the UI input state.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335#[non_exhaustive]
336pub enum UiAction {
337    SelectionStartEditing,
338
339    CancelEdition,
340    CommitEdition,
341
342    CommitEditionAndMove(MoveDirection),
343
344    Undo,
345    Redo,
346
347    MoveSelection(MoveDirection),
348    CopySelection,
349    CutSelection,
350
351    PasteInPlace,
352    PasteInsert,
353
354    DuplicateRow,
355    DeleteSelection,
356    DeleteRow,
357
358    NavPageDown,
359    NavPageUp,
360    NavTop,
361    NavBottom,
362
363    SelectionDuplicateValues,
364    SelectAll,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
368pub enum MoveDirection {
369    Up,
370    Down,
371    Left,
372    Right,
373}
374
375pub fn default_hotkeys(context: &UiActionContext) -> Vec<(KeyboardShortcut, UiAction)> {
376    let c = context.cursor;
377
378    fn shortcut(actions: &[(Modifiers, Key, UiAction)]) -> Vec<(egui::KeyboardShortcut, UiAction)> {
379        actions
380            .iter()
381            .map(|(m, k, a)| (egui::KeyboardShortcut::new(*m, *k), *a))
382            .collect()
383    }
384
385    let none = Modifiers::NONE;
386    let ctrl = Modifiers::CTRL;
387    let alt = Modifiers::ALT;
388    let shift = Modifiers::SHIFT;
389
390    use UiAction::CommitEditionAndMove;
391    type MD = MoveDirection;
392
393    if c.is_editing() {
394        shortcut(&[
395            (none, Key::Escape, UiAction::CommitEdition),
396            (ctrl, Key::Escape, UiAction::CancelEdition),
397            (shift, Key::Enter, CommitEditionAndMove(MD::Up)),
398            (ctrl, Key::Enter, CommitEditionAndMove(MD::Down)),
399            (shift, Key::Tab, CommitEditionAndMove(MD::Left)),
400            (none, Key::Tab, CommitEditionAndMove(MD::Right)),
401        ])
402    } else {
403        shortcut(&[
404            (ctrl, Key::X, UiAction::CutSelection),
405            (ctrl, Key::C, UiAction::CopySelection),
406            (ctrl | shift, Key::V, UiAction::PasteInsert),
407            (ctrl, Key::V, UiAction::PasteInPlace),
408            (ctrl, Key::Y, UiAction::Redo),
409            (ctrl, Key::Z, UiAction::Undo),
410            (none, Key::Enter, UiAction::SelectionStartEditing),
411            (none, Key::ArrowUp, UiAction::MoveSelection(MD::Up)),
412            (none, Key::ArrowDown, UiAction::MoveSelection(MD::Down)),
413            (none, Key::ArrowLeft, UiAction::MoveSelection(MD::Left)),
414            (none, Key::ArrowRight, UiAction::MoveSelection(MD::Right)),
415            (shift, Key::V, UiAction::PasteInsert),
416            (alt, Key::V, UiAction::PasteInsert),
417            (ctrl | shift, Key::D, UiAction::DuplicateRow),
418            (ctrl, Key::D, UiAction::SelectionDuplicateValues),
419            (ctrl, Key::A, UiAction::SelectAll),
420            (ctrl, Key::Delete, UiAction::DeleteRow),
421            (none, Key::Delete, UiAction::DeleteSelection),
422            (none, Key::Backspace, UiAction::DeleteSelection),
423            (none, Key::PageUp, UiAction::NavPageUp),
424            (none, Key::PageDown, UiAction::NavPageDown),
425            (none, Key::Home, UiAction::NavTop),
426            (none, Key::End, UiAction::NavBottom),
427        ])
428    }
429}