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 SkipCell,
11
12 SkipRow,
14
15 #[default]
17 Abort,
18}
19
20pub trait RowCodec<R> {
24 type DeserializeError;
25
26 fn create_empty_decoded_row(&mut self) -> R;
28
29 fn encode_column(&mut self, src_row: &R, column: usize, dst: &mut String);
33
34 fn decode_column(
36 &mut self,
37 src_data: &str,
38 column: usize,
39 dst_row: &mut R,
40 ) -> Result<(), DecodeErrorBehavior>;
41}
42
43impl<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
67pub trait RowViewer<R>: 'static {
70 fn num_columns(&mut self) -> usize;
73
74 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 fn try_create_codec(&mut self, is_encoding: bool) -> Option<impl RowCodec<R>> {
92 let _ = is_encoding;
93 None::<()>
94 }
95
96 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 fn is_sortable_column(&mut self, column: usize) -> bool {
112 let _ = column;
113 false
114 }
115
116 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 fn row_filter_hash(&mut self) -> &impl std::hash::Hash {
124 &()
125 }
126
127 fn filter_row(&mut self, row: &R) -> bool {
129 let _ = row;
130 true
131 }
132
133 fn show_cell_view(&mut self, ui: &mut egui::Ui, row: &R, column: usize);
140
141 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 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 fn set_cell_value(&mut self, src: &R, dst: &mut R, column: usize);
163
164 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 fn confirm_row_deletion_by_ui(&mut self, row: &R) -> bool {
180 let _ = row;
181 true
182 }
183
184 fn new_empty_row(&mut self) -> R;
186
187 fn new_empty_row_for(&mut self, context: EmptyRowCreateContext) -> R {
189 let _ = context;
190 self.new_empty_row()
191 }
192
193 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 fn clone_row_for_insertion(&mut self, row: &R) -> R {
206 self.clone_row(row)
207 }
208
209 fn clone_row_as_copied_base(&mut self, row: &R) -> R {
212 self.clone_row(row)
213 }
214
215 fn on_highlight_cell(&mut self, row: &R, column: usize) {
217 let _ = (row, column);
218 }
219
220 fn on_highlight_change(&mut self, highlighted: &[&R], unhighlighted: &[&R]) {
222 let (_, _) = (highlighted, unhighlighted);
223 }
224
225 fn hotkeys(&mut self, context: &UiActionContext) -> Vec<(egui::KeyboardShortcut, UiAction)> {
227 self::default_hotkeys(context)
228 }
229
230 #[cfg(feature = "persistency")]
233 fn persist_ui_state(&self) -> bool {
234 false
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241#[non_exhaustive]
242pub enum CellWriteContext {
243 Paste,
245
246 Clear,
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251#[non_exhaustive]
252pub enum EmptyRowCreateContext {
253 Default,
255
256 DeletionDefault,
258
259 InsertNewLine,
261}
262
263#[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#[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}