rat_ftable/edit/
vec.rs

1//! Specialized editing in a table. Keeps a Vec of
2//! the row-data.
3//!
4//! A widget that renders the table and can render
5//! an edit-widget on top.
6//!
7//! __Examples__
8//! For examples go to the rat-widget crate.
9//! There is `examples/table_edit2.rs`.
10
11use crate::edit::{Mode, TableEditor, TableEditorState};
12use crate::rowselection::RowSelection;
13use crate::textdata::Row;
14use crate::{Table, TableContext, TableData, TableState};
15use log::warn;
16use rat_cursor::HasScreenCursor;
17use rat_event::util::MouseFlags;
18use rat_event::{ct_event, try_flow, HandleEvent, Outcome, Regular};
19use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
20use rat_reloc::RelocatableState;
21use ratatui::buffer::Buffer;
22use ratatui::layout::{Constraint, Rect};
23use ratatui::style::Style;
24use ratatui::widgets::StatefulWidget;
25use std::cell::RefCell;
26use std::fmt::{Debug, Formatter};
27use std::rc::Rc;
28
29/// Extends TableData with the capability to set the actual data
30/// at a later point in time.
31///
32/// This is needed to inject the data during rendering, while
33/// leaving the rendering to the caller.
34///
35/// Due to life-time issues the data is given as Rc<>.
36pub trait TableDataVec<D>: TableData<'static> {
37    /// Set the actual table data.
38    fn set_data(&mut self, data: Rc<RefCell<Vec<D>>>);
39}
40
41/// Widget that supports row-wise editing of a table.
42///
43/// This widget keeps a `Vec<RowData>` and modifies it.
44///
45/// It's parameterized with a `Editor` widget, that renders
46/// the input line and handles events.
47pub struct EditableTableVec<'a, E>
48where
49    E: TableEditor + 'a,
50{
51    table: Table<'a, RowSelection>,
52    table_data: Box<dyn TableDataVec<<<E as TableEditor>::State as TableEditorState>::Value>>,
53    editor: E,
54}
55
56/// State for EditTable.
57///
58/// Contains `mode` to differentiate between edit/non-edit.
59/// This will lock the focus to the input line while editing.
60///
61pub struct EditableTableVecState<S>
62where
63    S: TableEditorState,
64{
65    /// Editing mode.
66    mode: Mode,
67
68    /// Backing table.
69    pub table: TableState<RowSelection>,
70    /// Editor
71    editor: S,
72    /// Data store
73    editor_data: Rc<RefCell<Vec<S::Value>>>,
74
75    mouse: MouseFlags,
76}
77
78impl<'a, E> EditableTableVec<'a, E>
79where
80    E: TableEditor + 'a,
81{
82    pub fn new(
83        table_data: impl TableDataVec<<<E as TableEditor>::State as TableEditorState>::Value> + 'static,
84        table: Table<'a, RowSelection>,
85        editor: E,
86    ) -> Self {
87        Self {
88            table,
89            table_data: Box::new(table_data),
90            editor,
91        }
92    }
93}
94
95impl<'a, D> TableData<'a> for Box<dyn TableDataVec<D> + 'a> {
96    fn rows(&self) -> usize {
97        (**self).rows()
98    }
99
100    fn header(&self) -> Option<Row<'a>> {
101        (**self).header()
102    }
103
104    fn footer(&self) -> Option<Row<'a>> {
105        (**self).footer()
106    }
107
108    fn row_height(&self, row: usize) -> u16 {
109        (**self).row_height(row)
110    }
111
112    fn row_style(&self, row: usize) -> Option<Style> {
113        (**self).row_style(row)
114    }
115
116    fn widths(&self) -> Vec<Constraint> {
117        (**self).widths()
118    }
119
120    fn render_cell(
121        &self,
122        ctx: &TableContext,
123        column: usize,
124        row: usize,
125        area: Rect,
126        buf: &mut Buffer,
127    ) {
128        (**self).render_cell(ctx, column, row, area, buf)
129    }
130}
131
132impl<'a, E> Debug for EditableTableVec<'a, E>
133where
134    E: Debug,
135    E: TableEditor + 'a,
136{
137    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138        f.debug_struct("EditVec")
139            .field("table", &self.table)
140            .field("table_data", &"..dyn..")
141            .field("editor", &self.editor)
142            .finish()
143    }
144}
145
146impl<'a, E> StatefulWidget for EditableTableVec<'a, E>
147where
148    E: TableEditor + 'a,
149{
150    type State = EditableTableVecState<E::State>;
151
152    #[allow(clippy::collapsible_else_if)]
153    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
154        self.table_data.set_data(state.editor_data.clone());
155        self.table
156            .data(self.table_data)
157            .render(area, buf, &mut state.table);
158
159        if state.mode == Mode::Insert || state.mode == Mode::Edit {
160            if let Some(row) = state.table.selected() {
161                // but it might be out of view
162                if let Some((row_area, cell_areas)) = state.table.row_cells(row) {
163                    self.editor
164                        .render(row_area, &cell_areas, buf, &mut state.editor);
165                }
166            } else {
167                if cfg!(debug_assertions) {
168                    warn!("no row selection, not rendering editor");
169                }
170            }
171        }
172    }
173}
174
175impl<S> Default for EditableTableVecState<S>
176where
177    S: TableEditorState + Default,
178{
179    fn default() -> Self {
180        Self {
181            mode: Mode::View,
182            table: Default::default(),
183            editor: S::default(),
184            editor_data: Rc::new(RefCell::new(Vec::default())),
185            mouse: Default::default(),
186        }
187    }
188}
189
190impl<S> Debug for EditableTableVecState<S>
191where
192    S: TableEditorState + Debug,
193    S::Value: Debug,
194{
195    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
196        f.debug_struct("EditVecState")
197            .field("mode", &self.mode)
198            .field("table", &self.table)
199            .field("editor", &self.editor)
200            .field("editor_data", &self.editor_data)
201            .field("mouse", &self.mouse)
202            .finish()
203    }
204}
205
206impl<S> HasFocus for EditableTableVecState<S>
207where
208    S: TableEditorState,
209{
210    fn build(&self, builder: &mut FocusBuilder) {
211        builder.leaf_widget(self);
212    }
213
214    fn focus(&self) -> FocusFlag {
215        self.table.focus()
216    }
217
218    fn area(&self) -> Rect {
219        self.table.area()
220    }
221
222    fn navigable(&self) -> Navigation {
223        match self.mode {
224            Mode::View => self.table.navigable(),
225            Mode::Edit | Mode::Insert => Navigation::Lock,
226        }
227    }
228
229    fn is_focused(&self) -> bool {
230        self.table.is_focused()
231    }
232
233    fn lost_focus(&self) -> bool {
234        self.table.lost_focus()
235    }
236
237    fn gained_focus(&self) -> bool {
238        self.table.gained_focus()
239    }
240}
241
242impl<S> HasScreenCursor for EditableTableVecState<S>
243where
244    S: TableEditorState + HasScreenCursor,
245{
246    fn screen_cursor(&self) -> Option<(u16, u16)> {
247        match self.mode {
248            Mode::View => None,
249            Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
250        }
251    }
252}
253
254impl<S> RelocatableState for EditableTableVecState<S>
255where
256    S: TableEditorState + RelocatableState,
257{
258    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
259        match self.mode {
260            Mode::View => {}
261            Mode::Edit | Mode::Insert => {
262                self.editor.relocate(shift, clip);
263            }
264        }
265    }
266}
267
268impl<S> EditableTableVecState<S>
269where
270    S: TableEditorState,
271{
272    pub fn new(editor: S) -> Self {
273        Self {
274            mode: Mode::View,
275            table: TableState::new(),
276            editor,
277            editor_data: Rc::new(RefCell::new(vec![])),
278            mouse: Default::default(),
279        }
280    }
281
282    pub fn named(name: &str, editor: S) -> Self {
283        Self {
284            mode: Mode::View,
285            table: TableState::named(name),
286            editor,
287            editor_data: Rc::new(RefCell::new(vec![])),
288            mouse: Default::default(),
289        }
290    }
291}
292
293impl<S> EditableTableVecState<S>
294where
295    S: TableEditorState,
296{
297    /// Set the edit data.
298    pub fn set_value(&mut self, data: Vec<S::Value>) {
299        self.editor_data = Rc::new(RefCell::new(data));
300    }
301
302    /// Get the edit data.
303    pub fn value(&self) -> Vec<S::Value> {
304        self.editor_data.borrow().clone()
305    }
306
307    /// Editing is active?
308    pub fn is_editing(&self) -> bool {
309        self.mode == Mode::Edit || self.mode == Mode::Insert
310    }
311
312    /// Is the current edit an insert?
313    pub fn is_insert(&self) -> bool {
314        self.mode == Mode::Insert
315    }
316
317    /// Remove the item at the selected row.
318    pub fn remove(&mut self, row: usize) {
319        if self.mode != Mode::View {
320            return;
321        }
322        if row < self.editor_data.borrow().len() {
323            self.editor_data.borrow_mut().remove(row);
324            self.table.items_removed(row, 1);
325            if !self.table.scroll_to_row(row) {
326                self.table.scroll_to_row(row.saturating_sub(1));
327            }
328        }
329    }
330
331    /// Edit a new item inserted at the selected row.
332    pub fn edit_new(&mut self, row: usize, ctx: S::Context<'_>) -> Result<(), S::Err> {
333        if self.mode != Mode::View {
334            return Ok(());
335        }
336        let value = self.editor.create_value(ctx.clone())?;
337        self.editor.set_value(value.clone(), ctx.clone())?;
338        self.editor_data.borrow_mut().insert(row, value);
339        self._start(row, Mode::Insert);
340        Ok(())
341    }
342
343    /// Edit the item at the selected row.
344    pub fn edit(&mut self, row: usize, ctx: S::Context<'_>) -> Result<(), S::Err> {
345        if self.mode != Mode::View {
346            return Ok(());
347        }
348        {
349            let value = &self.editor_data.borrow()[row];
350            self.editor.set_value(value.clone(), ctx.clone())?;
351        }
352        self._start(row, Mode::Edit);
353        Ok(())
354    }
355
356    fn _start(&mut self, pos: usize, mode: Mode) {
357        if self.table.is_focused() {
358            // black magic
359            FocusBuilder::build_for(&self.editor).first();
360        }
361
362        self.mode = mode;
363        if self.mode == Mode::Insert {
364            self.table.items_added(pos, 1);
365        }
366        self.table.move_to(pos);
367        self.table.scroll_to_col(0);
368    }
369
370    /// Cancel editing.
371    ///
372    /// Updates the state to remove the edited row.
373    pub fn cancel(&mut self) {
374        if self.mode == Mode::View {
375            return;
376        }
377        let Some(row) = self.table.selected_checked() else {
378            return;
379        };
380        if self.mode == Mode::Insert {
381            self.editor_data.borrow_mut().remove(row);
382            self.table.items_removed(row, 1);
383        }
384        self._stop();
385    }
386
387    /// Commit the changes in the editor.
388    pub fn commit(&mut self, ctx: S::Context<'_>) -> Result<(), S::Err> {
389        if self.mode == Mode::View {
390            return Ok(());
391        }
392        let Some(row) = self.table.selected_checked() else {
393            return Ok(());
394        };
395        {
396            let value = self.editor.value(ctx.clone())?;
397            if let Some(value) = value {
398                self.editor_data.borrow_mut()[row] = value;
399            } else {
400                self.editor_data.borrow_mut().remove(row);
401                self.table.items_removed(row, 1);
402            }
403        }
404        self._stop();
405        Ok(())
406    }
407
408    pub fn commit_and_append(&mut self, ctx: S::Context<'_>) -> Result<(), S::Err> {
409        self.commit(ctx.clone())?;
410        if let Some(row) = self.table.selected_checked() {
411            self.edit_new(row + 1, ctx.clone())?;
412        }
413        Ok(())
414    }
415
416    pub fn commit_and_edit(&mut self, ctx: S::Context<'_>) -> Result<(), S::Err> {
417        let Some(row) = self.table.selected_checked() else {
418            return Ok(());
419        };
420
421        self.commit(ctx.clone())?;
422        self.table.select(Some(row + 1));
423        self.edit(row + 1, ctx.clone())?;
424        Ok(())
425    }
426
427    fn _stop(&mut self) {
428        self.mode = Mode::View;
429        self.table.scroll_to_col(0);
430    }
431}
432
433impl<'a, S> HandleEvent<crossterm::event::Event, S::Context<'a>, Result<Outcome, S::Err>>
434    for EditableTableVecState<S>
435where
436    S: HandleEvent<crossterm::event::Event, S::Context<'a>, Result<Outcome, S::Err>>,
437    S: TableEditorState,
438{
439    fn handle(
440        &mut self,
441        event: &crossterm::event::Event,
442        ctx: S::Context<'a>,
443    ) -> Result<Outcome, S::Err> {
444        if self.mode == Mode::Edit || self.mode == Mode::Insert {
445            try_flow!(match self.editor.handle(event, ctx.clone())? {
446                Outcome::Continue => Outcome::Continue,
447                Outcome::Unchanged => Outcome::Unchanged,
448                r => {
449                    if let Some(col) = self.editor.focused_col() {
450                        self.table.scroll_to_col(col);
451                    }
452                    r
453                }
454            });
455
456            try_flow!(match event {
457                ct_event!(keycode press Esc) => {
458                    self.cancel();
459                    Outcome::Changed
460                }
461                ct_event!(keycode press Enter) => {
462                    if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1)) {
463                        self.commit_and_edit(ctx.clone())?;
464                        Outcome::Changed
465                    } else {
466                        self.commit_and_append(ctx.clone())?;
467                        Outcome::Changed
468                    }
469                }
470                ct_event!(keycode press Up) => {
471                    self.commit(ctx.clone())?;
472                    Outcome::Changed
473                }
474                ct_event!(keycode press Down) => {
475                    self.commit(ctx.clone())?;
476                    Outcome::Changed
477                }
478                _ => Outcome::Continue,
479            });
480
481            Ok(Outcome::Continue)
482        } else {
483            try_flow!(match event {
484                ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
485                    if let Some((_col, row)) = self.table.cell_at_clicked((m.column, m.row)) {
486                        self.edit(row, ctx.clone())?;
487                        Outcome::Changed
488                    } else {
489                        Outcome::Continue
490                    }
491                }
492                _ => Outcome::Continue,
493            });
494
495            try_flow!(match event {
496                ct_event!(keycode press Insert) => {
497                    if let Some(row) = self.table.selected_checked() {
498                        self.edit_new(row, ctx.clone())?;
499                    }
500                    Outcome::Changed
501                }
502                ct_event!(keycode press Delete) => {
503                    if let Some(row) = self.table.selected_checked() {
504                        self.remove(row);
505                    }
506                    Outcome::Changed
507                }
508                ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
509                    if let Some(row) = self.table.selected_checked() {
510                        self.edit(row, ctx.clone())?;
511                        Outcome::Changed
512                    } else if self.table.rows() == 0 {
513                        self.edit_new(0, ctx.clone())?;
514                        Outcome::Changed
515                    } else {
516                        Outcome::Continue
517                    }
518                }
519                ct_event!(keycode press Down) => {
520                    if let Some(row) = self.table.selected_checked() {
521                        if row == self.table.rows().saturating_sub(1) {
522                            self.edit_new(row + 1, ctx.clone())?;
523                            Outcome::Changed
524                        } else {
525                            Outcome::Continue
526                        }
527                    } else if self.table.rows() == 0 {
528                        self.edit_new(0, ctx.clone())?;
529                        Outcome::Changed
530                    } else {
531                        Outcome::Continue
532                    }
533                }
534                _ => {
535                    Outcome::Continue
536                }
537            });
538
539            try_flow!(self.table.handle(event, Regular));
540
541            Ok(Outcome::Continue)
542        }
543    }
544}