Skip to main content

rat_ftable2/edit/
state.rs

1use crate::_private::NonExhaustive;
2use crate::derive::derive_table_widget_state;
3use crate::edit::{Mode, TableEditorState};
4use crate::selection::RowSelection;
5use crate::table::TableState;
6use rat_cursor::HasScreenCursor;
7use rat_event::crossterm::event::Event;
8use rat_event::util::MouseFlags;
9use rat_event::{HandleEvent, Outcome, Regular, ct_event, event_flow};
10use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
11use rat_reloc::RelocatableState;
12use ratatui_core::layout::Rect;
13
14/// State for [TableEdit][crate::edit::TableEdit].
15///
16/// It has a mode-field for insert/edit/view.
17///
18/// __Focus__
19///
20/// When editing the widget blocks the Focus it is part of.
21/// You can't leave the table as long as it's editing.
22///
23/// It is intended that the editor-widget sets up a separate
24/// Focus for all the widgets and navigates independently of
25/// the main application Focus.
26///
27pub struct TableEditState<T, E> {
28    /// Editing mode.
29    pub mode: Mode,
30    /// Backing table.
31    pub table: TableState<RowSelection>,
32    /// Editor
33    pub editor: E,
34    /// Data store
35    pub data: Vec<T>,
36
37    pub mouse: MouseFlags,
38
39    pub non_exhaustive: NonExhaustive,
40}
41
42impl<T, E> Default for TableEditState<T, E>
43where
44    E: Default,
45{
46    fn default() -> Self {
47        Self {
48            mode: Default::default(),
49            table: Default::default(),
50            editor: Default::default(),
51            data: Default::default(),
52            mouse: Default::default(),
53            non_exhaustive: NonExhaustive,
54        }
55    }
56}
57
58impl<T, E> HasFocus for TableEditState<T, E> {
59    fn build(&self, builder: &mut FocusBuilder) {
60        builder.leaf_widget(self);
61    }
62
63    fn focus(&self) -> FocusFlag {
64        self.table.focus.clone()
65    }
66
67    fn area(&self) -> Rect {
68        self.table.area
69    }
70
71    fn navigable(&self) -> Navigation {
72        match self.mode {
73            Mode::View => self.table.focus.navigable(),
74            Mode::Edit | Mode::Insert => Navigation::Lock,
75        }
76    }
77}
78
79impl<T, E> HasScreenCursor for TableEditState<T, E>
80where
81    E: HasScreenCursor,
82{
83    fn screen_cursor(&self) -> Option<(u16, u16)> {
84        match self.mode {
85            Mode::View => None,
86            Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
87        }
88    }
89}
90
91impl<T, E> RelocatableState for TableEditState<T, E>
92where
93    E: RelocatableState,
94{
95    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
96        self.table.relocate(shift, clip);
97        match self.mode {
98            Mode::View => {}
99            Mode::Edit | Mode::Insert => {
100                self.editor.relocate(shift, clip);
101            }
102        }
103    }
104}
105
106impl<T, E> TableEditState<T, E> {
107    pub fn new(editor: E) -> Self {
108        Self {
109            mode: Mode::View,
110            table: Default::default(),
111            editor,
112            data: Default::default(),
113            mouse: Default::default(),
114            non_exhaustive: NonExhaustive,
115        }
116    }
117
118    pub fn named(name: &str, editor: E) -> Self {
119        Self {
120            mode: Mode::View,
121            table: TableState::named(name),
122            editor,
123            data: Default::default(),
124            mouse: Default::default(),
125            non_exhaustive: NonExhaustive,
126        }
127    }
128
129    /// Populate the table-data.
130    pub fn with(mut self, data: impl IntoIterator<Item = T>) -> Self {
131        self.data.extend(data.into_iter());
132        self
133    }
134}
135
136impl<T, E> TableEditState<T, E> {
137    derive_table_widget_state!(BASE);
138}
139
140impl<T, E> TableEditState<T, E> {
141    derive_table_widget_state!(AREAS);
142}
143
144impl<T, E> TableEditState<T, E> {
145    derive_table_widget_state!(OFFSET);
146}
147
148impl<T, E> TableEditState<T, E> {
149    derive_table_widget_state!(SELECTION);
150}
151
152impl<T, E> TableEditState<T, E> {
153    derive_table_widget_state!(ROWSELECTION);
154}
155
156impl<T, E> TableEditState<T, E>
157where
158    E: TableEditorState<T>,
159    T: Clone,
160{
161    /// Set the data.
162    pub fn set_value(&mut self, data: Vec<T>) {
163        self.data = data;
164        self.table.rows_changed(self.data.len());
165    }
166
167    /// Get the data.
168    ///
169    /// This will not contain the data of the currently
170    /// editing row. Instead, it will contain the value
171    /// before the edit started.
172    pub fn value(&self) -> &Vec<T> {
173        &self.data
174    }
175
176    /// Clear the widget.
177    pub fn clear(&mut self) {
178        self.mode = Mode::View;
179        self.table.clear_offset();
180        self.table.clear_selection();
181        self.data.clear();
182    }
183
184    /// Editing is active?
185    pub fn is_editing(&self) -> bool {
186        self.mode == Mode::Edit || self.mode == Mode::Insert
187    }
188
189    /// Is the current edit an insert?
190    pub fn is_insert(&self) -> bool {
191        self.mode == Mode::Insert
192    }
193
194    /// Remove the item at the given row.
195    pub fn remove(&mut self, row: usize) {
196        if self.mode != Mode::View {
197            return;
198        }
199        if row < self.data.len() {
200            self.data.remove(row);
201            self.table.items_removed(row, 1);
202            if !self.table.scroll_to_row(row) {
203                self.table.scroll_to_row(row.saturating_sub(1));
204            }
205        }
206    }
207
208    /// Edit a new item inserted at the selected row.
209    pub fn edit_new<'a>(&mut self, row: usize, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
210        if self.mode != Mode::View {
211            return Ok(());
212        }
213
214        let value = self.editor.create_value(ctx)?;
215        self.editor.set_value(value.clone(), ctx)?;
216        self._start(None, row, Mode::Insert, ctx)?;
217
218        self.data.insert(row, value);
219        self.table.items_added(row, 1);
220
221        Ok(())
222    }
223
224    /// Edit the item at the given row.
225    /// A starting column can be provided too.
226    pub fn edit<'a>(
227        &mut self,
228        col: Option<usize>,
229        row: usize,
230        ctx: &'a E::Context<'a>,
231    ) -> Result<(), E::Err> {
232        if self.mode != Mode::View {
233            return Ok(());
234        }
235
236        let value = &self.data[row];
237        self.editor.set_value(value.clone(), ctx)?;
238        self._start(col, row, Mode::Edit, ctx)?;
239
240        Ok(())
241    }
242
243    /// Cancel editing.
244    /// When inserting a new row, the row will be removed.
245    pub fn cancel<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
246        if self.mode == Mode::View {
247            return Ok(());
248        }
249        let Some(row) = self.table.selected_checked() else {
250            return Ok(());
251        };
252
253        self._stop(ctx)?;
254
255        if self.mode == Mode::Insert {
256            self.data.remove(row);
257            self.table.items_removed(row, 1);
258        }
259
260        Ok(())
261    }
262
263    /// Commit the changes in the editor.
264    pub fn commit<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
265        if self.mode == Mode::View {
266            return Ok(());
267        }
268        let Some(row) = self.table.selected_checked() else {
269            return Ok(());
270        };
271
272        self._stop(ctx)?;
273
274        let value = self.editor.value(ctx)?;
275        if let Some(value) = value {
276            self.data[row] = value;
277        } else {
278            self.data.remove(row);
279            self.table.items_removed(row, 1);
280        }
281
282        Ok(())
283    }
284
285    /// Commit the current changes and start editing
286    /// the next row. If the end of the table is reached
287    /// a row will be appended.
288    pub fn commit_and_append<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
289        self.commit(ctx)?;
290        if let Some(row) = self.table.selected_checked() {
291            self.edit_new(row + 1, ctx)?;
292        }
293        Ok(())
294    }
295
296    /// Commit the current changes and start editing
297    /// the next row. Does not append at the end of the
298    /// table.
299    pub fn commit_and_edit<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
300        let Some(row) = self.table.selected_checked() else {
301            return Ok(());
302        };
303
304        self.commit(ctx)?;
305        if row + 1 < self.data.len() {
306            self.table.select(Some(row + 1));
307            self.edit(None, row + 1, ctx)?;
308        }
309        Ok(())
310    }
311
312    fn _start<'a>(
313        &mut self,
314        col: Option<usize>,
315        row: usize,
316        mode: Mode,
317        ctx: &'a E::Context<'a>,
318    ) -> Result<(), E::Err> {
319        self.mode = mode;
320        self.table.move_to(row);
321        self.editor.start(col, ctx)?;
322        if let Some(col) = self.editor.focused_col() {
323            self.table.scroll_to_col(col);
324        } else {
325            self.table.scroll_to_col(0);
326        }
327        Ok(())
328    }
329
330    fn _stop<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
331        self.mode = Mode::View;
332        self.editor.stop(ctx)?;
333        self.table.scroll_to_col(0);
334        Ok(())
335    }
336}
337
338impl<'a, T, S> HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>
339    for TableEditState<T, S>
340where
341    S: HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>,
342    S: TableEditorState<T>,
343    T: Clone,
344{
345    #[allow(clippy::collapsible_if)]
346    fn handle(&mut self, event: &Event, ctx: &'a S::Context<'a>) -> Result<Outcome, S::Err> {
347        if self.mode == Mode::Edit || self.mode == Mode::Insert {
348            if self.is_focused() {
349                event_flow!({
350                    let r = self.editor.handle(event, ctx)?;
351                    if let Some(col) = self.editor.focused_col() {
352                        if self.table.scroll_to_col(col) {
353                            Outcome::Changed
354                        } else {
355                            r
356                        }
357                    } else {
358                        r
359                    }
360                });
361
362                event_flow!(match event {
363                    ct_event!(keycode press Esc) => {
364                        self.cancel(ctx)?;
365                        Outcome::Changed
366                    }
367                    ct_event!(keycode press Enter) => {
368                        if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1))
369                        {
370                            self.commit_and_edit(ctx)?;
371                            Outcome::Changed
372                        } else {
373                            self.commit_and_append(ctx)?;
374                            Outcome::Changed
375                        }
376                    }
377                    ct_event!(keycode press Up) => {
378                        self.commit(ctx)?;
379                        if self.data.is_empty() {
380                            self.edit_new(0, ctx)?;
381                        } else if let Some(row) = self.table.selected_checked()
382                            && row > 0
383                        {
384                            self.table.select(Some(row - 1));
385                        }
386                        Outcome::Changed
387                    }
388                    ct_event!(keycode press Down) => {
389                        self.commit(ctx)?;
390                        if self.data.is_empty() {
391                            self.edit_new(0, ctx)?;
392                        } else if let Some(row) = self.table.selected_checked()
393                            && row + 1 < self.data.len()
394                        {
395                            self.table.select(Some(row + 1));
396                        }
397                        Outcome::Changed
398                    }
399                    _ => Outcome::Continue,
400                });
401            }
402
403            Ok(Outcome::Continue)
404        } else {
405            if self.table.focus.gained_focus() {
406                if self.data.is_empty() {
407                    self.edit_new(0, ctx)?;
408                }
409            }
410
411            event_flow!(match event {
412                ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
413                    if let Some((col, row)) = self.table.cell_at_clicked((m.column, m.row)) {
414                        self.edit(Some(col), row, ctx)?;
415                        Outcome::Changed
416                    } else {
417                        Outcome::Continue
418                    }
419                }
420                _ => Outcome::Continue,
421            });
422
423            if self.is_focused() {
424                event_flow!(match event {
425                    ct_event!(keycode press Insert) => {
426                        if let Some(row) = self.table.selected() {
427                            self.edit_new(row, ctx)?;
428                        }
429                        Outcome::Changed
430                    }
431                    ct_event!(keycode press Delete) => {
432                        if let Some(row) = self.table.selected_checked() {
433                            self.remove(row);
434                            if self.data.is_empty() {
435                                self.edit_new(0, ctx)?;
436                            }
437                        }
438                        Outcome::Changed
439                    }
440                    ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
441                        if let Some(row) = self.table.selected_checked() {
442                            self.edit(None, row, ctx)?;
443                            Outcome::Changed
444                        } else if self.table.rows() == 0 {
445                            self.edit_new(0, ctx)?;
446                            Outcome::Changed
447                        } else {
448                            Outcome::Continue
449                        }
450                    }
451                    ct_event!(keycode press Down) => {
452                        if let Some(row) = self.table.selected() {
453                            if row == self.table.rows().saturating_sub(1) {
454                                self.edit_new(row + 1, ctx)?;
455                                Outcome::Changed
456                            } else {
457                                Outcome::Continue
458                            }
459                        } else if self.table.rows() == 0 {
460                            self.edit_new(0, ctx)?;
461                            Outcome::Changed
462                        } else {
463                            Outcome::Continue
464                        }
465                    }
466                    _ => {
467                        Outcome::Continue
468                    }
469                });
470            }
471
472            event_flow!(self.table.handle(event, Regular));
473
474            Ok(Outcome::Continue)
475        }
476    }
477}