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    /// Compare two column contents for sort.
117    fn compare_cell(&self, row_a: &R, row_b: &R, column: usize) -> std::cmp::Ordering {
118        let _ = (row_a, row_b, column);
119        std::cmp::Ordering::Equal
120    }
121
122    /// Get hash value of a filter. This is used to determine if the filter has changed.
123    fn row_filter_hash(&mut self) -> &impl std::hash::Hash {
124        &()
125    }
126
127    /// Filter single row. If this returns false, the row will be hidden.
128    fn filter_row(&mut self, row: &R) -> bool {
129        let _ = row;
130        true
131    }
132
133    /// Display values of the cell. Any input will be consumed before table renderer;
134    /// therefore any widget rendered inside here is read-only.
135    ///
136    /// To deal with input, use `cell_edit` method. If you need to deal with drag/drop,
137    /// see [`RowViewer::on_cell_view_response`] which delivers resulting response of
138    /// containing cell.
139    fn show_cell_view(&mut self, ui: &mut egui::Ui, row: &R, column: usize);
140
141    /// Use this to check if given cell is going to take any dropped payload / use as drag
142    /// source.
143    fn on_cell_view_response(
144        &mut self,
145        row: &R,
146        column: usize,
147        resp: &egui::Response,
148    ) -> Option<Box<R>> {
149        let _ = (row, column, resp);
150        None
151    }
152
153    /// Edit values of the cell.
154    fn show_cell_editor(
155        &mut self,
156        ui: &mut egui::Ui,
157        row: &mut R,
158        column: usize,
159    ) -> Option<egui::Response>;
160
161    /// Set the value of a column in a row.
162    fn set_cell_value(&mut self, src: &R, dst: &mut R, column: usize);
163
164    /// In the write context that happens outside of `show_cell_editor`, this method is
165    /// called on every cell value editions.
166    fn confirm_cell_write_by_ui(
167        &mut self,
168        current: &R,
169        next: &R,
170        column: usize,
171        context: CellWriteContext,
172    ) -> bool {
173        let _ = (current, next, column, context);
174        true
175    }
176
177    /// Before removing each row, this method is called to confirm the deletion from the
178    /// viewer. This won't be called during the undo/redo operation!
179    fn confirm_row_deletion_by_ui(&mut self, row: &R) -> bool {
180        let _ = row;
181        true
182    }
183
184    /// Create a new empty row.
185    fn new_empty_row(&mut self) -> R;
186
187    /// Create a new empty row under the given context.
188    fn new_empty_row_for(&mut self, context: EmptyRowCreateContext) -> R {
189        let _ = context;
190        self.new_empty_row()
191    }
192
193    /// Create duplication of existing row.
194    ///
195    /// You may want to override this method for more efficient duplication.
196    fn clone_row(&mut self, row: &R) -> R {
197        let mut dst = self.new_empty_row();
198        for i in 0..self.num_columns() {
199            self.set_cell_value(row, &mut dst, i);
200        }
201        dst
202    }
203
204    /// Create duplication of existing row for insertion.
205    fn clone_row_for_insertion(&mut self, row: &R) -> R {
206        self.clone_row(row)
207    }
208
209    /// Create duplication of existing row for clipboard. Useful when you need to specify
210    /// different behavior for clipboard duplication. (e.g. unset transient flag)
211    fn clone_row_as_copied_base(&mut self, row: &R) -> R {
212        self.clone_row(row)
213    }
214
215    /// Called when a cell is selected/highlighted.
216    fn on_highlight_cell(&mut self, row: &R, column: usize) {
217        let _ = (row, column);
218    }
219
220    /// Called when a row selected/highlighted status changes.
221    fn on_highlight_change(&mut self, highlighted: &[&R], unhighlighted: &[&R]) {
222        let (_, _) = (highlighted, unhighlighted);
223    }
224
225    /// Return hotkeys for the current context.
226    fn hotkeys(&mut self, context: &UiActionContext) -> Vec<(egui::KeyboardShortcut, UiAction)> {
227        self::default_hotkeys(context)
228    }
229
230    /// If you want to keep UI state on storage(i.e. persist over sessions), return true from this
231    /// function.
232    #[cfg(feature = "persistency")]
233    fn persist_ui_state(&self) -> bool {
234        false
235    }
236}
237
238/* ------------------------------------------- Context ------------------------------------------ */
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241#[non_exhaustive]
242pub enum CellWriteContext {
243    /// Value is being pasted/duplicated from different row.
244    Paste,
245
246    /// Value is being cleared by cut/delete operation.
247    Clear,
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251#[non_exhaustive]
252pub enum EmptyRowCreateContext {
253    /// Row is created to be used as simple default template.
254    Default,
255
256    /// Row is created to be used as explicit `empty` value when deletion
257    DeletionDefault,
258
259    /// Row is created to be inserted as a new row.
260    InsertNewLine,
261}
262
263/* ------------------------------------------- Hotkeys ------------------------------------------ */
264
265/// Base context for determining current input state.
266#[derive(Debug, Clone)]
267#[non_exhaustive]
268pub struct UiActionContext {
269    pub cursor: UiCursorState,
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum UiCursorState {
274    Idle,
275    Editing,
276    SelectOne,
277    SelectMany,
278}
279
280impl UiCursorState {
281    pub fn is_idle(&self) -> bool {
282        matches!(self, Self::Idle)
283    }
284
285    pub fn is_editing(&self) -> bool {
286        matches!(self, Self::Editing)
287    }
288
289    pub fn is_selecting(&self) -> bool {
290        matches!(self, Self::SelectOne | Self::SelectMany)
291    }
292}
293
294/* ----------------------------------------- Ui Actions ----------------------------------------- */
295
296/// Represents a user interaction, calculated from the UI input state.
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
298#[non_exhaustive]
299pub enum UiAction {
300    SelectionStartEditing,
301
302    CancelEdition,
303    CommitEdition,
304
305    CommitEditionAndMove(MoveDirection),
306
307    Undo,
308    Redo,
309
310    MoveSelection(MoveDirection),
311    CopySelection,
312    CutSelection,
313
314    PasteInPlace,
315    PasteInsert,
316
317    DuplicateRow,
318    DeleteSelection,
319    DeleteRow,
320
321    NavPageDown,
322    NavPageUp,
323    NavTop,
324    NavBottom,
325
326    SelectionDuplicateValues,
327    SelectAll,
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
331pub enum MoveDirection {
332    Up,
333    Down,
334    Left,
335    Right,
336}
337
338pub fn default_hotkeys(context: &UiActionContext) -> Vec<(KeyboardShortcut, UiAction)> {
339    let c = context.cursor;
340
341    fn shortcut(actions: &[(Modifiers, Key, UiAction)]) -> Vec<(egui::KeyboardShortcut, UiAction)> {
342        actions
343            .iter()
344            .map(|(m, k, a)| (egui::KeyboardShortcut::new(*m, *k), *a))
345            .collect()
346    }
347
348    let none = Modifiers::NONE;
349    let ctrl = Modifiers::CTRL;
350    let alt = Modifiers::ALT;
351    let shift = Modifiers::SHIFT;
352
353    use UiAction::CommitEditionAndMove;
354    type MD = MoveDirection;
355
356    if c.is_editing() {
357        shortcut(&[
358            (none, Key::Escape, UiAction::CommitEdition),
359            (ctrl, Key::Escape, UiAction::CancelEdition),
360            (shift, Key::Enter, CommitEditionAndMove(MD::Up)),
361            (ctrl, Key::Enter, CommitEditionAndMove(MD::Down)),
362            (shift, Key::Tab, CommitEditionAndMove(MD::Left)),
363            (none, Key::Tab, CommitEditionAndMove(MD::Right)),
364        ])
365    } else {
366        shortcut(&[
367            (ctrl, Key::X, UiAction::CutSelection),
368            (ctrl, Key::C, UiAction::CopySelection),
369            (ctrl | shift, Key::V, UiAction::PasteInsert),
370            (ctrl, Key::V, UiAction::PasteInPlace),
371            (ctrl, Key::Y, UiAction::Redo),
372            (ctrl, Key::Z, UiAction::Undo),
373            (none, Key::Enter, UiAction::SelectionStartEditing),
374            (none, Key::ArrowUp, UiAction::MoveSelection(MD::Up)),
375            (none, Key::ArrowDown, UiAction::MoveSelection(MD::Down)),
376            (none, Key::ArrowLeft, UiAction::MoveSelection(MD::Left)),
377            (none, Key::ArrowRight, UiAction::MoveSelection(MD::Right)),
378            (shift, Key::V, UiAction::PasteInsert),
379            (alt, Key::V, UiAction::PasteInsert),
380            (ctrl | shift, Key::D, UiAction::DuplicateRow),
381            (ctrl, Key::D, UiAction::SelectionDuplicateValues),
382            (ctrl, Key::A, UiAction::SelectAll),
383            (ctrl, Key::Delete, UiAction::DeleteRow),
384            (none, Key::Delete, UiAction::DeleteSelection),
385            (none, Key::Backspace, UiAction::DeleteSelection),
386            (none, Key::PageUp, UiAction::NavPageUp),
387            (none, Key::PageDown, UiAction::NavPageDown),
388            (none, Key::Home, UiAction::NavTop),
389            (none, Key::End, UiAction::NavBottom),
390        ])
391    }
392}