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