Skip to main content

rat_ftable/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3mod cellselection;
4pub mod edit;
5mod noselection;
6mod rowselection;
7mod rowsetselection;
8mod table;
9pub mod textdata;
10mod util;
11
12use crate::textdata::Row;
13use ratatui_core::buffer::Buffer;
14use ratatui_core::layout::{Constraint, Rect};
15use ratatui_core::style::Style;
16
17/// Render-context for rendering a table-cell.
18#[derive(Debug)]
19pub struct TableContext {
20    /// Focus flag is set.
21    pub focus: bool,
22
23    /// Cell is selected.
24    pub selected_cell: bool,
25    /// Row of the cell is selected.
26    pub selected_row: bool,
27    /// Column of the cell is selected.
28    pub selected_column: bool,
29
30    /// Base style
31    pub style: Style,
32    /// Row style if any.
33    pub row_style: Option<Style>,
34    /// Selection style if any.
35    pub select_style: Option<Style>,
36
37    /// Spacing after the cell. It's guaranteed that this
38    /// is writeable in the buffer given to render_cell.
39    pub space_area: Rect,
40    /// Total area for the current row.
41    pub row_area: Rect,
42
43    /// Construct with `..Default::default()`
44    pub non_exhaustive: NonExhaustive,
45}
46
47///
48/// Trait for accessing the table-data by the Table.
49///
50/// This trait is suitable if the underlying data is some sort
51/// of vec/slice.
52pub trait TableData<'a> {
53    /// Size of the data.
54    fn rows(&self) -> usize;
55
56    /// Header can be obtained from here.
57    /// Alternative to setting on Table.
58    fn header(&self) -> Option<Row<'a>> {
59        None
60    }
61
62    /// Footer can be obtained from here.
63    /// Alternative to setting on Table.
64    fn footer(&self) -> Option<Row<'a>> {
65        None
66    }
67
68    /// Row height.
69    #[allow(unused_variables)]
70    fn row_height(&self, row: usize) -> u16 {
71        1
72    }
73
74    /// Row style.
75    #[allow(unused_variables)]
76    fn row_style(&self, row: usize) -> Option<Style> {
77        None
78    }
79
80    /// Column constraints.
81    fn widths(&self) -> Vec<Constraint> {
82        Vec::default()
83    }
84
85    /// Render the cell given by column/row.
86    /// * ctx - a lot of context data.
87    fn render_cell(
88        &self,
89        ctx: &TableContext,
90        column: usize,
91        row: usize,
92        area: Rect,
93        buf: &mut Buffer,
94    );
95}
96
97impl<'a> TableData<'a> for Box<dyn TableData<'a> + 'a> {
98    fn rows(&self) -> usize {
99        (**self).rows()
100    }
101
102    fn header(&self) -> Option<Row<'a>> {
103        (**self).header()
104    }
105
106    fn footer(&self) -> Option<Row<'a>> {
107        (**self).footer()
108    }
109
110    fn row_height(&self, row: usize) -> u16 {
111        (**self).row_height(row)
112    }
113
114    fn row_style(&self, row: usize) -> Option<Style> {
115        (**self).row_style(row)
116    }
117
118    fn widths(&self) -> Vec<Constraint> {
119        (**self).widths()
120    }
121
122    fn render_cell(
123        &self,
124        ctx: &TableContext,
125        column: usize,
126        row: usize,
127        area: Rect,
128        buf: &mut Buffer,
129    ) {
130        (**self).render_cell(ctx, column, row, area, buf)
131    }
132}
133
134/// Trait for accessing the table-data by the Table.
135///
136/// This trait is suitable if the underlying data is an iterator.
137pub trait TableDataIter<'a> {
138    /// StatefulWidgetRef needs a clone of the iterator for every render.
139    /// For StatefulWidget this is not needed at all. So this defaults to
140    /// None and warns at runtime.
141    fn cloned(&self) -> Option<Box<dyn TableDataIter<'a> + 'a>> {
142        None
143    }
144
145    /// Returns the number of rows, if known.
146    ///
147    /// If they are not known, all items will be iterated to
148    /// calculate things as the length of the table. Which will
149    /// be slower if you have many items.
150    ///
151    /// See [Table::no_row_count]
152    fn rows(&self) -> Option<usize>;
153
154    /// Header can be obtained from here.
155    /// Alternative to setting on Table.
156    fn header(&self) -> Option<Row<'a>> {
157        None
158    }
159
160    /// Footer can be obtained from here.
161    /// Alternative to setting on Table.
162    fn footer(&self) -> Option<Row<'a>> {
163        None
164    }
165
166    /// Skips to the nth item, returns true if such an item exists.
167    /// nth(0) == next()
168    fn nth(&mut self, n: usize) -> bool;
169
170    /// Row height for the current item.
171    fn row_height(&self) -> u16 {
172        1
173    }
174
175    /// Row style for the current line.
176    fn row_style(&self) -> Option<Style> {
177        None
178    }
179
180    /// Column constraints.
181    fn widths(&self) -> Vec<Constraint> {
182        Vec::default()
183    }
184
185    /// Render the cell for the current line.
186    /// * ctx - a lot of context data.
187    fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer);
188}
189
190/// Trait for the different selection models used by Table.
191pub trait TableSelection {
192    /// Number of rows selected.
193    fn count(&self) -> usize;
194
195    /// Clear the selection.
196    fn clear(&mut self) {}
197
198    /// Row is selected. This can be separate from `is_selected_cell`.
199    fn is_selected_row(&self, row: usize) -> bool;
200
201    /// Column is selected. This can be separate from `is_selected_cell`.
202    fn is_selected_column(&self, column: usize) -> bool;
203
204    /// Specific cell is selected.
205    fn is_selected_cell(&self, column: usize, row: usize) -> bool;
206
207    /// Selection lead, or the sole selected index.
208    fn lead_selection(&self) -> Option<(usize, usize)>;
209
210    /// Validate the selected row against the number of rows.
211    #[allow(unused_variables)]
212    fn validate_rows(&mut self, rows: usize) {}
213
214    /// Validate the selected column against the number of columns.
215    #[allow(unused_variables)]
216    fn validate_cols(&mut self, cols: usize) {}
217
218    /// Correct the selection for added items.
219    #[allow(unused_variables)]
220    fn items_added(&mut self, pos: usize, n: usize) {}
221
222    /// Correct the selection for removed items.
223    #[allow(unused_variables)]
224    fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {}
225}
226
227use crate::_private::NonExhaustive;
228
229pub use table::{Table, TableState, TableStyle, handle_doubleclick_events};
230
231/// Different selection models for Table.
232pub mod selection {
233    pub use crate::cellselection::CellSelection;
234    pub mod cellselection {
235        pub use crate::cellselection::{handle_events, handle_mouse_events};
236    }
237    pub use crate::noselection::NoSelection;
238    pub mod noselection {
239        pub use crate::noselection::{handle_events, handle_mouse_events};
240    }
241    pub use crate::rowselection::RowSelection;
242    pub mod rowselection {
243        pub use crate::rowselection::{handle_events, handle_mouse_events};
244    }
245    pub use crate::rowsetselection::RowSetSelection;
246    pub mod rowsetselection {
247        pub use crate::rowsetselection::{handle_events, handle_mouse_events};
248    }
249}
250
251/// Eventhandling.
252pub mod event {
253    pub use rat_event::*;
254
255    /// Result value for event-handling.
256    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
257    #[non_exhaustive]
258    pub enum TableOutcome {
259        /// The given event was not handled at all.
260        Continue,
261        /// The event was handled, no repaint necessary.
262        Unchanged,
263        /// Header column clicked.
264        Header(usize),
265        /// Footer column clicked.
266        Footer(usize),
267        /// The event was handled, repaint necessary.
268        Changed,
269        /// The selection has changed.
270        Selected,
271    }
272
273    impl ConsumedEvent for TableOutcome {
274        fn is_consumed(&self) -> bool {
275            *self != TableOutcome::Continue
276        }
277    }
278
279    impl From<TableOutcome> for Outcome {
280        fn from(value: TableOutcome) -> Self {
281            match value {
282                TableOutcome::Continue => Outcome::Continue,
283                TableOutcome::Unchanged => Outcome::Unchanged,
284                TableOutcome::Header(_) => Outcome::Unchanged,
285                TableOutcome::Footer(_) => Outcome::Unchanged,
286                TableOutcome::Changed => Outcome::Changed,
287                TableOutcome::Selected => Outcome::Changed,
288            }
289        }
290    }
291
292    /// Result type for double-click event-handling.
293    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
294    pub enum DoubleClickOutcome {
295        /// The given event has not been used at all.
296        Continue,
297        /// The event has been recognized, but the result was nil.
298        /// Further processing for this event may stop.
299        Unchanged,
300        /// The event has been recognized and there is some change
301        /// due to it.
302        /// Further processing for this event may stop.
303        /// Rendering the ui is advised.
304        Changed,
305        /// Double click occurred. Contains (column, row)
306        ClickClick(usize, usize),
307    }
308
309    impl From<DoubleClickOutcome> for Outcome {
310        fn from(value: DoubleClickOutcome) -> Self {
311            match value {
312                DoubleClickOutcome::Continue => Outcome::Continue,
313                DoubleClickOutcome::Unchanged => Outcome::Unchanged,
314                DoubleClickOutcome::Changed => Outcome::Changed,
315                DoubleClickOutcome::ClickClick(_, _) => Outcome::Changed,
316            }
317        }
318    }
319
320    impl From<Outcome> for DoubleClickOutcome {
321        fn from(value: Outcome) -> Self {
322            match value {
323                Outcome::Continue => DoubleClickOutcome::Continue,
324                Outcome::Unchanged => DoubleClickOutcome::Unchanged,
325                Outcome::Changed => DoubleClickOutcome::Changed,
326            }
327        }
328    }
329
330    impl ConsumedEvent for DoubleClickOutcome {
331        fn is_consumed(&self) -> bool {
332            !matches!(self, DoubleClickOutcome::Continue)
333        }
334    }
335
336    /// Result type for the [edit](crate::edit) widgets.
337    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
338    pub enum EditOutcome {
339        /// The given event has not been used at all.
340        Continue,
341        /// The event has been recognized, but the result was nil.
342        /// Further processing for this event may stop.
343        Unchanged,
344        /// The event has been recognized and there is some change
345        /// due to it.
346        /// Further processing for this event may stop.
347        /// Rendering the ui is advised.
348        Changed,
349        /// Cancel an ongoing edit.
350        Cancel,
351        /// Commit the current edit.
352        Commit,
353        /// Commit the edit, append a new line.
354        CommitAndAppend,
355        /// Commit the edit, edit next line.
356        CommitAndEdit,
357        /// Insert an item at the selection.
358        Insert,
359        /// Remove the item at the selection.
360        Remove,
361        /// Edit the item at the selection.
362        Edit,
363        /// Append an item after last row.
364        /// Might want to start the edit too.
365        Append,
366    }
367
368    impl From<Outcome> for EditOutcome {
369        fn from(value: Outcome) -> Self {
370            match value {
371                Outcome::Continue => EditOutcome::Continue,
372                Outcome::Unchanged => EditOutcome::Unchanged,
373                Outcome::Changed => EditOutcome::Changed,
374            }
375        }
376    }
377
378    impl From<EditOutcome> for Outcome {
379        fn from(value: EditOutcome) -> Self {
380            match value {
381                EditOutcome::Continue => Outcome::Continue,
382                EditOutcome::Unchanged => Outcome::Unchanged,
383                EditOutcome::Changed => Outcome::Changed,
384                EditOutcome::Insert => Outcome::Unchanged,
385                EditOutcome::Remove => Outcome::Unchanged,
386                EditOutcome::Edit => Outcome::Unchanged,
387                EditOutcome::Append => Outcome::Unchanged,
388                EditOutcome::Cancel => Outcome::Unchanged,
389                EditOutcome::Commit => Outcome::Unchanged,
390                EditOutcome::CommitAndAppend => Outcome::Unchanged,
391                EditOutcome::CommitAndEdit => Outcome::Unchanged,
392            }
393        }
394    }
395
396    impl ConsumedEvent for EditOutcome {
397        fn is_consumed(&self) -> bool {
398            !matches!(self, EditOutcome::Continue)
399        }
400    }
401}
402
403mod _private {
404    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
405    pub struct NonExhaustive;
406}