rat_ftable/
table.rs

1#![allow(clippy::collapsible_if)]
2
3use crate::_private::NonExhaustive;
4use crate::event::{DoubleClick, DoubleClickOutcome};
5use crate::selection::{CellSelection, RowSelection, RowSetSelection};
6use crate::table::data::{DataRepr, DataReprIter};
7use crate::textdata::{Row, TextTableData};
8use crate::util::{fallback_select_style, revert_style, transfer_buffer};
9use crate::{TableContext, TableData, TableDataIter, TableSelection};
10use rat_event::util::MouseFlags;
11use rat_event::{ct_event, HandleEvent};
12use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
13use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
14use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
15use ratatui::buffer::Buffer;
16use ratatui::layout::{Constraint, Flex, Layout, Rect};
17use ratatui::style::Style;
18use ratatui::widgets::{Block, StatefulWidget, Widget};
19use std::cmp::{max, min};
20use std::collections::HashSet;
21use std::fmt::Debug;
22use std::marker::PhantomData;
23use std::mem;
24use std::rc::Rc;
25
26/// Table widget.
27///
28/// Can be used as a drop-in replacement for the ratatui table. But
29/// that's not the point of this widget.
30///
31/// This widget uses the [TableData](crate::TableData) trait instead
32/// of rendering all the table-cells and putting them into a Vec.
33/// This way rendering time only depends on the screen-size not on
34/// the size of your data.
35///
36/// There is a second trait [TableDataIter](crate::TableDataIter) that
37/// works better if you only have an Iterator over your data.
38///
39/// See [Table::data] and [Table::iter] for an example.
40#[derive(Debug)]
41pub struct Table<'a, Selection> {
42    data: DataRepr<'a>,
43    no_row_count: bool,
44
45    header: Option<Row<'a>>,
46    footer: Option<Row<'a>>,
47
48    widths: Vec<Constraint>,
49    flex: Flex,
50    column_spacing: u16,
51    layout_width: Option<u16>,
52    auto_layout_width: bool,
53
54    block: Option<Block<'a>>,
55    hscroll: Option<Scroll<'a>>,
56    vscroll: Option<Scroll<'a>>,
57
58    header_style: Option<Style>,
59    footer_style: Option<Style>,
60    style: Style,
61
62    auto_styles: bool,
63    select_row_style: Option<Style>,
64    show_row_focus: bool,
65    select_column_style: Option<Style>,
66    show_column_focus: bool,
67    select_cell_style: Option<Style>,
68    show_cell_focus: bool,
69    select_header_style: Option<Style>,
70    show_header_focus: bool,
71    select_footer_style: Option<Style>,
72    show_footer_focus: bool,
73
74    focus_style: Option<Style>,
75
76    debug: bool,
77
78    _phantom: PhantomData<Selection>,
79}
80
81mod data {
82    use crate::textdata::TextTableData;
83    use crate::{TableContext, TableData, TableDataIter};
84    #[cfg(debug_assertions)]
85    use log::warn;
86    use ratatui::buffer::Buffer;
87    use ratatui::layout::Rect;
88    use ratatui::style::{Style, Stylize};
89    use std::fmt::{Debug, Formatter};
90
91    #[derive(Default)]
92    pub(super) enum DataRepr<'a> {
93        #[default]
94        None,
95        Text(TextTableData<'a>),
96        Data(Box<dyn TableData<'a> + 'a>),
97        Iter(Box<dyn TableDataIter<'a> + 'a>),
98    }
99
100    impl<'a> DataRepr<'a> {
101        pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
102            match self {
103                DataRepr::None => DataReprIter::None,
104                DataRepr::Text(v) => DataReprIter::IterText(v, None),
105                DataRepr::Data(v) => DataReprIter::IterData(v, None),
106                DataRepr::Iter(v) => DataReprIter::IterIter(v),
107            }
108        }
109
110        pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
111            match self {
112                DataRepr::None => DataReprIter::None,
113                DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
114                DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
115                DataRepr::Iter(v) => {
116                    // TableDataIter might not implement a valid cloned().
117                    if let Some(v) = v.cloned() {
118                        DataReprIter::IterIter(v)
119                    } else {
120                        DataReprIter::Invalid(None)
121                    }
122                }
123            }
124        }
125    }
126
127    impl Debug for DataRepr<'_> {
128        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129            f.debug_struct("Data").finish()
130        }
131    }
132
133    #[derive(Default)]
134    pub(super) enum DataReprIter<'a, 'b> {
135        #[default]
136        None,
137        #[allow(dead_code)]
138        Invalid(Option<usize>),
139        IterText(TextTableData<'a>, Option<usize>),
140        IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
141        #[allow(dead_code)]
142        IterDataRef(&'b dyn TableData<'a>, Option<usize>),
143        IterIter(Box<dyn TableDataIter<'a> + 'a>),
144    }
145
146    impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
147        fn rows(&self) -> Option<usize> {
148            match self {
149                DataReprIter::None => Some(0),
150                DataReprIter::Invalid(_) => Some(1),
151                DataReprIter::IterText(v, _) => Some(v.rows.len()),
152                DataReprIter::IterData(v, _) => Some(v.rows()),
153                DataReprIter::IterDataRef(v, _) => Some(v.rows()),
154                DataReprIter::IterIter(v) => v.rows(),
155            }
156        }
157
158        fn nth(&mut self, n: usize) -> bool {
159            let incr = |row: &mut Option<usize>, rows: usize| match *row {
160                None => {
161                    *row = Some(n);
162                    *row < Some(rows)
163                }
164                Some(w) => {
165                    *row = Some(w.saturating_add(n).saturating_add(1));
166                    *row < Some(rows)
167                }
168            };
169
170            match self {
171                DataReprIter::None => false,
172                DataReprIter::Invalid(row) => incr(row, 1),
173                DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
174                DataReprIter::IterData(v, row) => incr(row, v.rows()),
175                DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
176                DataReprIter::IterIter(v) => v.nth(n),
177            }
178        }
179
180        /// Row height.
181        fn row_height(&self) -> u16 {
182            match self {
183                DataReprIter::None => 1,
184                DataReprIter::Invalid(_) => 1,
185                DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
186                DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
187                DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
188                DataReprIter::IterIter(v) => v.row_height(),
189            }
190        }
191
192        fn row_style(&self) -> Option<Style> {
193            match self {
194                DataReprIter::None => None,
195                DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
196                DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
197                DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
198                DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
199                DataReprIter::IterIter(v) => v.row_style(),
200            }
201        }
202
203        /// Render the cell given by column/row.
204        fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
205            match self {
206                DataReprIter::None => {}
207                DataReprIter::Invalid(_) => {
208                    if column == 0 {
209                        #[cfg(debug_assertions)]
210                        warn!("Table::render_ref - TableDataIter must implement a valid cloned() for this to work.");
211
212                        buf.set_string(
213                            area.x,
214                            area.y,
215                            "TableDataIter must implement a valid cloned() for this",
216                            Style::default(),
217                        );
218                    }
219                }
220                DataReprIter::IterText(v, n) => {
221                    v.render_cell(ctx, column, n.expect("row"), area, buf)
222                }
223                DataReprIter::IterData(v, n) => {
224                    v.render_cell(ctx, column, n.expect("row"), area, buf)
225                }
226                DataReprIter::IterDataRef(v, n) => {
227                    v.render_cell(ctx, column, n.expect("row"), area, buf)
228                }
229                DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
230            }
231        }
232    }
233}
234
235/// Combined style.
236#[derive(Debug)]
237pub struct TableStyle {
238    pub style: Style,
239    pub header: Option<Style>,
240    pub footer: Option<Style>,
241
242    pub select_row: Option<Style>,
243    pub select_column: Option<Style>,
244    pub select_cell: Option<Style>,
245    pub select_header: Option<Style>,
246    pub select_footer: Option<Style>,
247
248    pub show_row_focus: bool,
249    pub show_column_focus: bool,
250    pub show_cell_focus: bool,
251    pub show_header_focus: bool,
252    pub show_footer_focus: bool,
253
254    pub focus_style: Option<Style>,
255
256    pub block: Option<Block<'static>>,
257    pub border_style: Option<Style>,
258    pub scroll: Option<ScrollStyle>,
259
260    pub non_exhaustive: NonExhaustive,
261}
262
263/// Table state.
264#[derive(Debug)]
265pub struct TableState<Selection> {
266    /// Current focus state.
267    /// __read+write__
268    pub focus: FocusFlag,
269
270    /// Total area.
271    /// __read only__ Renewed with each render.
272    pub area: Rect,
273    /// Area inside the border and scrollbars
274    /// __read only__ Renewed with each render.
275    pub inner: Rect,
276
277    /// Total header area.
278    /// __read only__ Renewed with each render.
279    pub header_area: Rect,
280    /// Total table area.
281    /// __read only__ Renewed with each render.
282    pub table_area: Rect,
283    /// Area per visible row. The first element is at row_offset.
284    /// __read only__ Renewed with each render.
285    pub row_areas: Vec<Rect>,
286    /// Area for each column plus the following spacer if any.
287    /// Invisible columns have width 0, height is the height of the table_area.
288    /// __read only__ Renewed with each render.
289    pub column_areas: Vec<Rect>,
290    /// Layout areas for each column plus the following spacer if any.
291    /// Positions are 0-based, y and height are 0.
292    /// __read only__ Renewed with each render.
293    pub column_layout: Vec<Rect>,
294    /// Total footer area.
295    /// __read only__ Renewed with each render.
296    pub footer_area: Rect,
297
298    /// Row count.
299    /// __read+write__ Renewed with each render anyway.
300    pub rows: usize,
301    // debug info
302    pub _counted_rows: usize,
303    /// Column count.
304    /// __read only__ Renewed with each render.
305    pub columns: usize,
306
307    /// Row scrolling data.
308    /// __read+write__ max_offset set with each render.
309    pub vscroll: ScrollState,
310    /// Column scrolling data.
311    /// __read+write__ max_offset set with each render.
312    pub hscroll: ScrollState,
313
314    /// Selection data.
315    /// __read+write__ selection model. selection is not bound by rows.
316    pub selection: Selection,
317
318    /// Helper for mouse interactions.
319    pub mouse: MouseFlags,
320
321    pub non_exhaustive: NonExhaustive,
322}
323
324impl<Selection> Default for Table<'_, Selection> {
325    fn default() -> Self {
326        Self {
327            data: Default::default(),
328            no_row_count: Default::default(),
329            header: Default::default(),
330            footer: Default::default(),
331            widths: Default::default(),
332            flex: Default::default(),
333            column_spacing: Default::default(),
334            layout_width: Default::default(),
335            auto_layout_width: Default::default(),
336            block: Default::default(),
337            hscroll: Default::default(),
338            vscroll: Default::default(),
339            header_style: Default::default(),
340            footer_style: Default::default(),
341            style: Default::default(),
342            auto_styles: true,
343            select_row_style: Default::default(),
344            show_row_focus: true,
345            select_column_style: Default::default(),
346            show_column_focus: Default::default(),
347            select_cell_style: Default::default(),
348            show_cell_focus: Default::default(),
349            select_header_style: Default::default(),
350            show_header_focus: Default::default(),
351            select_footer_style: Default::default(),
352            show_footer_focus: Default::default(),
353            focus_style: Default::default(),
354            debug: Default::default(),
355            _phantom: Default::default(),
356        }
357    }
358}
359
360impl<'a, Selection> Table<'a, Selection> {
361    /// New, empty Table.
362    pub fn new() -> Self
363    where
364        Selection: Default,
365    {
366        Self::default()
367    }
368
369    /// Create a new Table with preformatted data. For compatibility
370    /// with ratatui.
371    ///
372    /// Use of [Table::data] is preferred.
373    pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
374    where
375        R: IntoIterator,
376        R::Item: Into<Row<'a>>,
377        C: IntoIterator,
378        C::Item: Into<Constraint>,
379        Selection: Default,
380    {
381        let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
382        let data = TextTableData {
383            rows: rows.into_iter().map(|v| v.into()).collect(),
384        };
385        Self {
386            data: DataRepr::Text(data),
387            widths,
388            ..Default::default()
389        }
390    }
391
392    /// Set preformatted row-data. For compatibility with ratatui.
393    ///
394    /// Use of [Table::data] is preferred.
395    pub fn rows<T>(mut self, rows: T) -> Self
396    where
397        T: IntoIterator<Item = Row<'a>>,
398    {
399        let rows = rows.into_iter().collect();
400        self.data = DataRepr::Text(TextTableData { rows });
401        self
402    }
403
404    /// Set a reference to the TableData facade to your data.
405    ///
406    /// The way to go is to define a small struct that contains just a
407    /// reference to your data. Then implement TableData for this struct.
408    ///
409    /// ```rust
410    /// use ratatui::buffer::Buffer;
411    /// use ratatui::layout::Rect;
412    /// use ratatui::style::Style;
413    /// use ratatui::text::Span;
414    /// use ratatui::widgets::{StatefulWidget, Widget};
415    /// use rat_ftable::{Table, TableContext, TableState, TableData};    ///
416    /// #
417    /// use rat_ftable::selection::RowSelection;
418    ///
419    /// struct SampleRow;
420    /// # impl Clone for SampleRow {
421    /// #     fn clone(&self) -> Self {
422    /// #        SampleRow
423    /// #     }
424    /// # }
425    /// # let area = Rect::default();
426    /// # let mut buf = Buffer::empty(area);
427    /// # let buf = &mut buf;
428    ///
429    /// struct Data1<'a>(&'a [SampleRow]);
430    ///
431    /// impl<'a> TableData<'a> for Data1<'a> {
432    ///     fn rows(&self) -> usize {
433    ///         self.0.len()
434    ///     }
435    ///
436    ///     fn row_height(&self, row: usize) -> u16 {
437    ///         // to some calculations ...
438    ///         1
439    ///     }
440    ///
441    ///     fn row_style(&self, row: usize) -> Option<Style> {
442    ///         // to some calculations ...
443    ///         None
444    ///     }
445    ///
446    ///     fn render_cell(&self, ctx: &TableContext, column: usize, row: usize, area: Rect, buf: &mut Buffer) {
447    ///         if let Some(data) = self.0.get(row) {
448    ///             let rend = match column {
449    ///                 0 => Span::from("column1"),
450    ///                 1 => Span::from("column2"),
451    ///                 2 => Span::from("column3"),
452    ///                 _ => return
453    ///             };
454    ///             rend.render(area, buf);
455    ///         }
456    ///     }
457    /// }
458    ///
459    /// // When you are creating the table widget you hand over a reference
460    /// // to the facade struct.
461    ///
462    /// let my_data_somewhere_else = vec![SampleRow;999999];
463    /// let mut table_state_somewhere_else = TableState::<RowSelection>::default();
464    ///
465    /// // ...
466    ///
467    /// let table1 = Table::default().data(Data1(&my_data_somewhere_else));
468    /// table1.render(area, buf, &mut table_state_somewhere_else);
469    /// ```
470    #[inline]
471    pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
472        self.widths = data.widths();
473        self.header = data.header();
474        self.footer = data.footer();
475        self.data = DataRepr::Data(Box::new(data));
476        self
477    }
478
479    ///
480    /// Alternative representation for the data as a kind of Iterator.
481    /// It uses interior iteration, which fits quite nice for this and
482    /// avoids handing out lifetime bound results of the actual iterator.
483    /// Which is a bit nightmarish to get right.
484    ///
485    ///
486    /// Caution: If you can't give the number of rows, the table will iterate over all
487    /// the data. See [Table::no_row_count].
488    ///
489    /// ```rust
490    /// use std::iter::{Enumerate};
491    /// use std::slice::Iter;
492    /// use format_num_pattern::NumberFormat;
493    /// use ratatui::buffer::Buffer;
494    /// use ratatui::layout::{Constraint, Rect};
495    /// use ratatui::style::Color;
496    /// use ratatui::style::{Style, Stylize};
497    /// use ratatui::text::Span;
498    /// use ratatui::widgets::{Widget, StatefulWidget};
499    /// use rat_ftable::{Table, TableContext, TableState, TableDataIter};
500    /// use rat_ftable::selection::RowSelection;
501    ///
502    /// struct Data {
503    /// #     table_data: Vec<Sample>
504    /// # }
505    /// #
506    /// # struct Sample {
507    /// #     pub text: String
508    /// # }
509    /// #
510    /// # let data = Data {
511    /// #     table_data: vec![],
512    /// # };
513    /// # let area = Rect::default();
514    /// # let mut buf = Buffer::empty(area);
515    /// # let buf = &mut buf;
516    ///
517    /// struct RowIter1<'a> {
518    ///     iter: Enumerate<Iter<'a, Sample>>,
519    ///     item: Option<(usize, &'a Sample)>,
520    /// }
521    ///
522    /// impl<'a> TableDataIter<'a> for RowIter1<'a> {
523    ///     fn rows(&self) -> Option<usize> {
524    ///         // If you can, give the length. Otherwise,
525    ///         // the table will iterate all to find out a length.
526    ///         None
527    ///         // Some(100_000)
528    ///     }
529    ///
530    ///     /// Select the nth element from the current position.
531    ///     fn nth(&mut self, n: usize) -> bool {
532    ///         self.item = self.iter.nth(n);
533    ///         self.item.is_some()
534    ///     }
535    ///
536    ///     /// Row height.
537    ///     fn row_height(&self) -> u16 {
538    ///         1
539    ///     }
540    ///
541    ///     /// Row style.
542    ///     fn row_style(&self) -> Option<Style> {
543    ///         Some(Style::default())
544    ///     }
545    ///
546    ///     /// Render one cell.
547    ///     fn render_cell(&self,
548    ///                     ctx: &TableContext,
549    ///                     column: usize,
550    ///                     area: Rect,
551    ///                     buf: &mut Buffer)
552    ///     {
553    ///         let row = self.item.expect("data");
554    ///         match column {
555    ///             0 => {
556    ///                 let row_fmt = NumberFormat::new("000000").expect("fmt");
557    ///                 let span = Span::from(row_fmt.fmt_u(row.0));
558    ///                 buf.set_style(area, Style::new().black().bg(Color::from_u32(0xe7c787)));
559    ///                 span.render(area, buf);
560    ///             }
561    ///             1 => {
562    ///                 let span = Span::from(&row.1.text);
563    ///                 span.render(area, buf);
564    ///             }
565    ///             _ => {}
566    ///         }
567    ///     }
568    /// }
569    ///
570    /// let mut rit = RowIter1 {
571    ///     iter: data.table_data.iter().enumerate(),
572    ///     item: None,
573    /// };
574    ///
575    /// let table1 = Table::default()
576    ///     .iter(rit)
577    ///     .widths([
578    ///         Constraint::Length(6),
579    ///         Constraint::Length(20)
580    ///     ]);
581    ///
582    /// let mut table_state_somewhere_else = TableState::<RowSelection>::default();
583    ///
584    /// table1.render(area, buf, &mut table_state_somewhere_else);
585    /// ```
586    ///
587    #[inline]
588    pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
589        #[cfg(debug_assertions)]
590        if data.rows().is_none() {
591            use log::warn;
592            warn!("Table::iter - rows is None, this will be slower");
593        }
594        self.header = data.header();
595        self.footer = data.footer();
596        self.widths = data.widths();
597        self.data = DataRepr::Iter(Box::new(data));
598        self
599    }
600
601    /// If you work with an TableDataIter to fill the table, and
602    /// if you don't return a count with rows(), Table will run
603    /// through all your iterator to find the actual number of rows.
604    ///
605    /// This may take its time.
606    ///
607    /// If you set no_row_count(true), this part will be skipped, and
608    /// the row count will be set to an estimate of usize::MAX.
609    /// This will destroy your ability to jump to the end of the data,
610    /// but otherwise it's fine.
611    /// You can still page-down through the data, and if you ever
612    /// reach the end, the correct row-count can be established.
613    ///
614    /// _Extra info_: This might be only useful if you have a LOT of data.
615    /// In my test it changed from 1.5ms to 150µs for about 100.000 rows.
616    /// And 1.5ms is still not that much ... so you probably want to
617    /// test without this first and then decide.
618    pub fn no_row_count(mut self, no_row_count: bool) -> Self {
619        self.no_row_count = no_row_count;
620        self
621    }
622
623    /// Set the table-header.
624    #[inline]
625    pub fn header(mut self, header: Row<'a>) -> Self {
626        self.header = Some(header);
627        self
628    }
629
630    /// Set the table-footer.
631    #[inline]
632    pub fn footer(mut self, footer: Row<'a>) -> Self {
633        self.footer = Some(footer);
634        self
635    }
636
637    /// Column widths as Constraints.
638    pub fn widths<I>(mut self, widths: I) -> Self
639    where
640        I: IntoIterator,
641        I::Item: Into<Constraint>,
642    {
643        self.widths = widths.into_iter().map(|v| v.into()).collect();
644        self
645    }
646
647    /// Flex for layout.
648    #[inline]
649    pub fn flex(mut self, flex: Flex) -> Self {
650        self.flex = flex;
651        self
652    }
653
654    /// Spacing between columns.
655    #[inline]
656    pub fn column_spacing(mut self, spacing: u16) -> Self {
657        self.column_spacing = spacing;
658        self
659    }
660
661    /// Overrides the width of the rendering area for layout purposes.
662    /// Layout uses this width, even if it means that some columns are
663    /// not visible.
664    #[inline]
665    pub fn layout_width(mut self, width: u16) -> Self {
666        self.layout_width = Some(width);
667        self
668    }
669
670    /// Calculates the width from the given column-constraints.
671    /// If a fixed layout_width() is set too, that one will win.
672    ///
673    /// Panic:
674    /// Rendering will panic, if any constraint other than Constraint::Length(),
675    /// Constraint::Min() or Constraint::Max() is used.
676    #[inline]
677    pub fn auto_layout_width(mut self) -> Self {
678        self.auto_layout_width = true;
679        self
680    }
681
682    /// Draws a block around the table widget.
683    #[inline]
684    pub fn block(mut self, block: Block<'a>) -> Self {
685        self.block = Some(block);
686        self.block = self.block.map(|v| v.style(self.style));
687        self
688    }
689
690    /// Scrollbars
691    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
692        self.hscroll = Some(scroll.clone().override_horizontal());
693        self.vscroll = Some(scroll.override_vertical());
694        self
695    }
696
697    /// Scrollbars
698    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
699        self.hscroll = Some(scroll.override_horizontal());
700        self
701    }
702
703    /// Scrollbars
704    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
705        self.vscroll = Some(scroll.override_vertical());
706        self
707    }
708
709    /// Set all styles as a bundle.
710    #[inline]
711    pub fn styles(mut self, styles: TableStyle) -> Self {
712        self.style = styles.style;
713        if styles.header.is_some() {
714            self.header_style = styles.header;
715        }
716        if styles.footer.is_some() {
717            self.footer_style = styles.footer;
718        }
719        if styles.select_row.is_some() {
720            self.select_row_style = styles.select_row;
721        }
722        self.show_row_focus = styles.show_row_focus;
723        if styles.select_column.is_some() {
724            self.select_column_style = styles.select_column;
725        }
726        self.show_column_focus = styles.show_column_focus;
727        if styles.select_cell.is_some() {
728            self.select_cell_style = styles.select_cell;
729        }
730        self.show_cell_focus = styles.show_cell_focus;
731        if styles.select_header.is_some() {
732            self.select_header_style = styles.select_header;
733        }
734        self.show_header_focus = styles.show_header_focus;
735        if styles.select_footer.is_some() {
736            self.select_footer_style = styles.select_footer;
737        }
738        self.show_footer_focus = styles.show_footer_focus;
739        if styles.focus_style.is_some() {
740            self.focus_style = styles.focus_style;
741        }
742        if let Some(border_style) = styles.border_style {
743            self.block = self.block.map(|v| v.border_style(border_style));
744        }
745        self.block = self.block.map(|v| v.style(self.style));
746        if styles.block.is_some() {
747            self.block = styles.block;
748        }
749        if let Some(styles) = styles.scroll {
750            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
751            self.vscroll = self.vscroll.map(|v| v.styles(styles));
752        }
753        self
754    }
755
756    /// Base style for the table.
757    #[inline]
758    pub fn style(mut self, style: Style) -> Self {
759        self.style = style;
760        self.block = self.block.map(|v| v.style(self.style));
761        self
762    }
763
764    /// Base style for the table.
765    #[inline]
766    pub fn header_style(mut self, style: Option<Style>) -> Self {
767        self.header_style = style;
768        self
769    }
770
771    /// Base style for the table.
772    #[inline]
773    pub fn footer_style(mut self, style: Option<Style>) -> Self {
774        self.footer_style = style;
775        self
776    }
777
778    /// Set the appropriate styles when rendering a cell.
779    /// If this is set to false, no styles will be set at all.
780    /// It's up to the TableData/TableDataIter impl to set the correct styles.
781    ///
782    /// Default is true.
783    #[inline]
784    pub fn auto_styles(mut self, auto_styles: bool) -> Self {
785        self.auto_styles = auto_styles;
786        self
787    }
788
789    /// Style for a selected row. The chosen selection must support
790    /// row-selection for this to take effect.
791    #[inline]
792    pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
793        self.select_row_style = select_style;
794        self
795    }
796
797    /// Add the focus-style to the row-style if the table is focused.
798    #[inline]
799    pub fn show_row_focus(mut self, show: bool) -> Self {
800        self.show_row_focus = show;
801        self
802    }
803
804    /// Style for a selected column. The chosen selection must support
805    /// column-selection for this to take effect.
806    #[inline]
807    pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
808        self.select_column_style = select_style;
809        self
810    }
811
812    /// Add the focus-style to the column-style if the table is focused.
813    #[inline]
814    pub fn show_column_focus(mut self, show: bool) -> Self {
815        self.show_column_focus = show;
816        self
817    }
818
819    /// Style for a selected cell. The chosen selection must support
820    /// cell-selection for this to take effect.
821    #[inline]
822    pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
823        self.select_cell_style = select_style;
824        self
825    }
826
827    /// Add the focus-style to the cell-style if the table is focused.
828    #[inline]
829    pub fn show_cell_focus(mut self, show: bool) -> Self {
830        self.show_cell_focus = show;
831        self
832    }
833
834    /// Style for a selected header cell. The chosen selection must
835    /// support column-selection for this to take effect.
836    #[inline]
837    pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
838        self.select_header_style = select_style;
839        self
840    }
841
842    /// Add the focus-style to the header-style if the table is focused.
843    #[inline]
844    pub fn show_header_focus(mut self, show: bool) -> Self {
845        self.show_header_focus = show;
846        self
847    }
848
849    /// Style for a selected footer cell. The chosen selection must
850    /// support column-selection for this to take effect.
851    #[inline]
852    pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
853        self.select_footer_style = select_style;
854        self
855    }
856
857    /// Add the footer-style to the table-style if the table is focused.
858    #[inline]
859    pub fn show_footer_focus(mut self, show: bool) -> Self {
860        self.show_footer_focus = show;
861        self
862    }
863
864    /// This style will be patched onto the selection to indicate that
865    /// the widget has the input focus.
866    ///
867    /// The selection must support some kind of selection for this to
868    /// be effective.
869    #[inline]
870    pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
871        self.focus_style = focus_style;
872        self
873    }
874
875    /// Just some utility to help with debugging. Usually does nothing.
876    pub fn debug(mut self, debug: bool) -> Self {
877        self.debug = debug;
878        self
879    }
880}
881
882impl<Selection> Table<'_, Selection> {
883    // area_width or layout_width
884    #[inline]
885    fn total_width(&self, area_width: u16) -> u16 {
886        if let Some(layout_width) = self.layout_width {
887            layout_width
888        } else if self.auto_layout_width {
889            let mut width = 0;
890            for w in &self.widths {
891                match w {
892                    Constraint::Min(v) => width += *v + self.column_spacing,
893                    Constraint::Max(v) => width += *v + self.column_spacing,
894                    Constraint::Length(v) => width += *v + self.column_spacing,
895                    _ => unimplemented!("Invalid layout constraint."),
896                }
897            }
898            width
899        } else {
900            area_width
901        }
902    }
903
904    // Do the column-layout. Fill in missing columns, if necessary.
905    #[inline]
906    fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
907        let width = self.total_width(width);
908        let area = Rect::new(0, 0, width, 0);
909
910        let (layout, spacers) = Layout::horizontal(&self.widths)
911            .flex(self.flex)
912            .spacing(self.column_spacing)
913            .split_with_spacers(area);
914
915        (width, layout, spacers)
916    }
917
918    // Layout header/table/footer
919    #[inline]
920    fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
921        let heights = vec![
922            Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
923            Constraint::Fill(1),
924            Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
925        ];
926
927        Layout::vertical(heights).split(area)
928    }
929}
930
931impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
932where
933    Selection: TableSelection,
934{
935    type State = TableState<Selection>;
936
937    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
938        let iter = (&self.data).iter();
939        self.render_iter(iter, area, buf, state);
940    }
941}
942
943impl<Selection> StatefulWidget for Table<'_, Selection>
944where
945    Selection: TableSelection,
946{
947    type State = TableState<Selection>;
948
949    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
950        let iter = mem::take(&mut self.data).into_iter();
951        self.render_iter(iter, area, buf, state);
952    }
953}
954
955impl<'a, Selection> Table<'a, Selection>
956where
957    Selection: TableSelection,
958{
959    /// Render an Iterator over TableRowData.
960    ///
961    /// rows: If the row number is known, this can help.
962    ///
963    fn render_iter<'b>(
964        &self,
965        mut data: DataReprIter<'a, 'b>,
966        area: Rect,
967        buf: &mut Buffer,
968        state: &mut TableState<Selection>,
969    ) {
970        if let Some(rows) = data.rows() {
971            state.rows = rows;
972        }
973        state.columns = self.widths.len();
974        state.area = area;
975
976        let sa = ScrollArea::new()
977            .style(self.style)
978            .block(self.block.as_ref())
979            .h_scroll(self.hscroll.as_ref())
980            .v_scroll(self.vscroll.as_ref());
981        state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
982
983        let l_rows = self.layout_areas(state.inner);
984        state.header_area = l_rows[0];
985        state.table_area = l_rows[1];
986        state.footer_area = l_rows[2];
987
988        // horizontal layout
989        let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
990        self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
991
992        // render block+scroll
993        sa.render(
994            area,
995            buf,
996            &mut ScrollAreaState::new()
997                .h_scroll(&mut state.hscroll)
998                .v_scroll(&mut state.vscroll),
999        );
1000
1001        // render header & footer
1002        self.render_header(
1003            state.columns,
1004            width,
1005            l_columns.as_ref(),
1006            l_spacers.as_ref(),
1007            state.header_area,
1008            buf,
1009            state,
1010        );
1011        self.render_footer(
1012            state.columns,
1013            width,
1014            l_columns.as_ref(),
1015            l_spacers.as_ref(),
1016            state.footer_area,
1017            buf,
1018            state,
1019        );
1020
1021        // render table
1022        state.row_areas.clear();
1023        state.vscroll.set_page_len(0);
1024        state.hscroll.set_page_len(area.width as usize);
1025
1026        let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1027        let mut row = None;
1028        let mut row_y = state.table_area.y;
1029        let mut row_heights = Vec::new();
1030        #[cfg(debug_assertions)]
1031        let mut insane_offset = false;
1032
1033        let mut ctx = TableContext {
1034            focus: state.focus.get(),
1035            selected_cell: false,
1036            selected_row: false,
1037            selected_column: false,
1038            style: self.style,
1039            row_style: None,
1040            select_style: None,
1041            space_area: Default::default(),
1042            row_area: Default::default(),
1043            non_exhaustive: NonExhaustive,
1044        };
1045
1046        if data.nth(state.vscroll.offset()) {
1047            row = Some(state.vscroll.offset());
1048            loop {
1049                ctx.row_style = data.row_style();
1050                // We render each row to a temporary buffer.
1051                // For ease of use we start each row at 0,0.
1052                // We still only render at least partially visible cells.
1053                let render_row_area = Rect::new(0, 0, width, data.row_height());
1054                ctx.row_area = render_row_area;
1055                row_buf.resize(render_row_area);
1056                if self.auto_styles {
1057                    if let Some(row_style) = ctx.row_style {
1058                        row_buf.set_style(render_row_area, row_style);
1059                    } else {
1060                        row_buf.set_style(render_row_area, self.style);
1061                    }
1062                }
1063                row_heights.push(render_row_area.height);
1064
1065                // Target area for the finished row.
1066                let visible_row_area = Rect::new(
1067                    state.table_area.x,
1068                    row_y,
1069                    state.table_area.width,
1070                    render_row_area.height,
1071                )
1072                .intersection(state.table_area);
1073                state.row_areas.push(visible_row_area);
1074                // only count fully visible rows.
1075                if render_row_area.height == visible_row_area.height {
1076                    state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1077                }
1078
1079                // can skip this entirely
1080                if render_row_area.height > 0 {
1081                    let mut col = 0;
1082                    loop {
1083                        if col >= state.columns {
1084                            break;
1085                        }
1086
1087                        let render_cell_area = Rect::new(
1088                            l_columns[col].x,
1089                            0,
1090                            l_columns[col].width,
1091                            render_row_area.height,
1092                        );
1093                        ctx.space_area = Rect::new(
1094                            l_spacers[col + 1].x,
1095                            0,
1096                            l_spacers[col + 1].width,
1097                            render_row_area.height,
1098                        );
1099
1100                        if state.selection.is_selected_cell(col, row.expect("row")) {
1101                            ctx.selected_cell = true;
1102                            ctx.selected_row = false;
1103                            ctx.selected_column = false;
1104                            ctx.select_style = self.patch_select(
1105                                self.select_cell_style,
1106                                state.focus.get(),
1107                                self.show_cell_focus,
1108                            );
1109                        } else if state.selection.is_selected_row(row.expect("row")) {
1110                            ctx.selected_cell = false;
1111                            ctx.selected_row = true;
1112                            ctx.selected_column = false;
1113                            // use a fallback if no row-selected style is set.
1114                            ctx.select_style = if self.select_row_style.is_some() {
1115                                self.patch_select(
1116                                    self.select_row_style,
1117                                    state.focus.get(),
1118                                    self.show_row_focus,
1119                                )
1120                            } else {
1121                                self.patch_select(
1122                                    Some(self.style),
1123                                    state.focus.get(),
1124                                    self.show_row_focus,
1125                                )
1126                            };
1127                        } else if state.selection.is_selected_column(col) {
1128                            ctx.selected_cell = false;
1129                            ctx.selected_row = false;
1130                            ctx.selected_column = true;
1131                            ctx.select_style = self.patch_select(
1132                                self.select_column_style,
1133                                state.focus.get(),
1134                                self.show_column_focus,
1135                            );
1136                        } else {
1137                            ctx.selected_cell = false;
1138                            ctx.selected_row = false;
1139                            ctx.selected_column = false;
1140                            ctx.select_style = None;
1141                        }
1142
1143                        // partially visible?
1144                        if render_cell_area.right() > state.hscroll.offset as u16
1145                            || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1146                        {
1147                            if self.auto_styles {
1148                                if let Some(select_style) = ctx.select_style {
1149                                    row_buf.set_style(render_cell_area, select_style);
1150                                    row_buf.set_style(ctx.space_area, select_style);
1151                                }
1152                            }
1153                            data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1154                        }
1155
1156                        col += 1;
1157                    }
1158
1159                    // render shifted and clipped row.
1160                    transfer_buffer(
1161                        &mut row_buf,
1162                        state.hscroll.offset() as u16,
1163                        visible_row_area,
1164                        buf,
1165                    );
1166                }
1167
1168                if visible_row_area.bottom() >= state.table_area.bottom() {
1169                    break;
1170                }
1171                if !data.nth(0) {
1172                    break;
1173                }
1174                row = Some(row.expect("row").saturating_add(1));
1175                row_y += render_row_area.height;
1176            }
1177        } else {
1178            // can only guess whether the skip failed completely or partially.
1179            // so don't alter row here.
1180
1181            // if this first skip fails all bets are off.
1182            if data.rows().is_none() || data.rows() == Some(0) {
1183                // this is ok
1184            } else {
1185                #[cfg(debug_assertions)]
1186                {
1187                    insane_offset = true;
1188                }
1189            }
1190        }
1191
1192        // maximum offsets
1193        #[allow(unused_variables)]
1194        let algorithm;
1195        #[allow(unused_assignments)]
1196        {
1197            if let Some(rows) = data.rows() {
1198                algorithm = 0;
1199                // skip to a guess for the last page.
1200                // the guess uses row-height is 1, which may read a few more lines than
1201                // absolutely necessary.
1202                let skip_rows = rows
1203                    .saturating_sub(row.map_or(0, |v| v + 1))
1204                    .saturating_sub(state.table_area.height as usize);
1205                // if we can still skip some rows, then the data so far is useless.
1206                if skip_rows > 0 {
1207                    row_heights.clear();
1208                }
1209                let nth_row = skip_rows;
1210                // collect the remaining row-heights.
1211                if data.nth(nth_row) {
1212                    let mut sum_height = row_heights.iter().sum::<u16>();
1213                    row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1214                    loop {
1215                        let row_height = data.row_height();
1216                        row_heights.push(row_height);
1217
1218                        // Keep a rolling sum of the heights and drop unnecessary info.
1219                        // We don't need more info, and there will be a lot more otherwise.
1220                        sum_height += row_height;
1221                        if sum_height
1222                            .saturating_sub(row_heights.first().copied().unwrap_or_default())
1223                            > state.table_area.height
1224                        {
1225                            let lost_height = row_heights.remove(0);
1226                            sum_height -= lost_height;
1227                        }
1228
1229                        if !data.nth(0) {
1230                            break;
1231                        }
1232
1233                        row = Some(row.expect("row") + 1);
1234                        // if the given number of rows is too small, we would overshoot here.
1235                        if row.expect("row") > rows {
1236                            break;
1237                        }
1238                    }
1239                    // we break before to have an accurate last page.
1240                    // but we still want to report an error, if the count is off.
1241                    while data.nth(0) {
1242                        row = Some(row.expect("row") + 1);
1243                    }
1244                } else {
1245                    // skip failed, maybe again?
1246                    // leave everything as is and report later.
1247                }
1248
1249                state.rows = rows;
1250                state._counted_rows = row.map_or(0, |v| v + 1);
1251
1252                // have we got a page worth of data?
1253                if let Some(last_page) = state.calc_last_page(row_heights) {
1254                    state.vscroll.set_max_offset(state.rows - last_page);
1255                } else {
1256                    // we don't have enough data to establish the last page.
1257                    // either there are not enough rows or the given row-count
1258                    // was off. make a guess.
1259                    state.vscroll.set_max_offset(
1260                        state.rows.saturating_sub(state.table_area.height as usize),
1261                    );
1262                }
1263            } else if self.no_row_count {
1264                algorithm = 1;
1265
1266                // We need to feel out a bit beyond the page, otherwise
1267                // we can't really stabilize the row count and the
1268                // display starts flickering.
1269                if row.is_some() {
1270                    if data.nth(0) {
1271                        // try one past page
1272                        row = Some(row.expect("row").saturating_add(1));
1273                        if data.nth(0) {
1274                            // have an unknown number of rows left.
1275                            row = Some(usize::MAX - 1);
1276                        }
1277                    }
1278                }
1279
1280                state.rows = row.map_or(0, |v| v + 1);
1281                state._counted_rows = row.map_or(0, |v| v + 1);
1282                // rough estimate
1283                state.vscroll.set_max_offset(usize::MAX - 1);
1284                if state.vscroll.page_len() == 0 {
1285                    state.vscroll.set_page_len(state.table_area.height as usize);
1286                }
1287            } else {
1288                algorithm = 2;
1289
1290                // Read all the rest to establish the exact row-count.
1291                let mut sum_height = row_heights.iter().sum::<u16>();
1292                while data.nth(0) {
1293                    let row_height = data.row_height();
1294                    row_heights.push(row_height);
1295
1296                    // Keep a rolling sum of the heights and drop unnecessary info.
1297                    // We don't need more info, and there will be a lot more otherwise.
1298                    sum_height += row_height;
1299                    if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1300                        > state.table_area.height
1301                    {
1302                        let lost_height = row_heights.remove(0);
1303                        sum_height -= lost_height;
1304                    }
1305                    row = Some(row.map_or(0, |v| v + 1));
1306                }
1307
1308                state.rows = row.map_or(0, |v| v + 1);
1309                state._counted_rows = row.map_or(0, |v| v + 1);
1310
1311                // have we got a page worth of data?
1312                if let Some(last_page) = state.calc_last_page(row_heights) {
1313                    state.vscroll.set_max_offset(state.rows - last_page);
1314                } else {
1315                    state.vscroll.set_max_offset(0);
1316                }
1317            }
1318        }
1319        {
1320            state
1321                .hscroll
1322                .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1323        }
1324
1325        #[cfg(debug_assertions)]
1326        {
1327            use std::fmt::Write;
1328            let mut msg = String::new();
1329            if insane_offset {
1330                _= write!(msg,
1331                          "Table::render:\n        offset {}\n        rows {}\n        iter-rows {}max\n    don't match up\nCode X{}X\n",
1332                          state.vscroll.offset(), state.rows, state._counted_rows, algorithm
1333                );
1334            }
1335            if state.rows != state._counted_rows {
1336                _ = write!(
1337                    msg,
1338                    "Table::render:\n    rows {} don't match\n    iterated rows {}\nCode X{}X\n",
1339                    state.rows, state._counted_rows, algorithm
1340                );
1341            }
1342            if !msg.is_empty() {
1343                use log::warn;
1344                use ratatui::style::Stylize;
1345                use ratatui::text::Text;
1346
1347                warn!("{}", &msg);
1348                Text::from(msg)
1349                    .white()
1350                    .on_red()
1351                    .render(state.table_area, buf);
1352            }
1353        }
1354    }
1355
1356    #[allow(clippy::too_many_arguments)]
1357    fn render_footer(
1358        &self,
1359        columns: usize,
1360        width: u16,
1361        l_columns: &[Rect],
1362        l_spacers: &[Rect],
1363        area: Rect,
1364        buf: &mut Buffer,
1365        state: &mut TableState<Selection>,
1366    ) {
1367        if let Some(footer) = &self.footer {
1368            let render_row_area = Rect::new(0, 0, width, footer.height);
1369            let mut row_buf = Buffer::empty(render_row_area);
1370
1371            row_buf.set_style(render_row_area, self.style);
1372            if let Some(footer_style) = footer.style {
1373                row_buf.set_style(render_row_area, footer_style);
1374            } else if let Some(footer_style) = self.footer_style {
1375                row_buf.set_style(render_row_area, footer_style);
1376            }
1377
1378            let mut col = 0;
1379            loop {
1380                if col >= columns {
1381                    break;
1382                }
1383
1384                let render_cell_area =
1385                    Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1386                let render_space_area = Rect::new(
1387                    l_spacers[col + 1].x,
1388                    0,
1389                    l_spacers[col + 1].width,
1390                    area.height,
1391                );
1392
1393                if state.selection.is_selected_column(col) {
1394                    if let Some(selected_style) = self.patch_select(
1395                        self.select_footer_style,
1396                        state.focus.get(),
1397                        self.show_footer_focus,
1398                    ) {
1399                        row_buf.set_style(render_cell_area, selected_style);
1400                        row_buf.set_style(render_space_area, selected_style);
1401                    }
1402                };
1403
1404                // partially visible?
1405                if render_cell_area.right() > state.hscroll.offset as u16
1406                    || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1407                {
1408                    if let Some(cell) = footer.cells.get(col) {
1409                        if let Some(cell_style) = cell.style {
1410                            row_buf.set_style(render_cell_area, cell_style);
1411                        }
1412                        cell.content.clone().render(render_cell_area, &mut row_buf);
1413                    }
1414                }
1415
1416                col += 1;
1417            }
1418
1419            // render shifted and clipped row.
1420            transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1421        }
1422    }
1423
1424    #[allow(clippy::too_many_arguments)]
1425    fn render_header(
1426        &self,
1427        columns: usize,
1428        width: u16,
1429        l_columns: &[Rect],
1430        l_spacers: &[Rect],
1431        area: Rect,
1432        buf: &mut Buffer,
1433        state: &mut TableState<Selection>,
1434    ) {
1435        if let Some(header) = &self.header {
1436            let render_row_area = Rect::new(0, 0, width, header.height);
1437            let mut row_buf = Buffer::empty(render_row_area);
1438
1439            row_buf.set_style(render_row_area, self.style);
1440            if let Some(header_style) = header.style {
1441                row_buf.set_style(render_row_area, header_style);
1442            } else if let Some(header_style) = self.header_style {
1443                row_buf.set_style(render_row_area, header_style);
1444            }
1445
1446            let mut col = 0;
1447            loop {
1448                if col >= columns {
1449                    break;
1450                }
1451
1452                let render_cell_area =
1453                    Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1454                let render_space_area = Rect::new(
1455                    l_spacers[col + 1].x,
1456                    0,
1457                    l_spacers[col + 1].width,
1458                    area.height,
1459                );
1460
1461                if state.selection.is_selected_column(col) {
1462                    if let Some(selected_style) = self.patch_select(
1463                        self.select_header_style,
1464                        state.focus.get(),
1465                        self.show_header_focus,
1466                    ) {
1467                        row_buf.set_style(render_cell_area, selected_style);
1468                        row_buf.set_style(render_space_area, selected_style);
1469                    }
1470                };
1471
1472                // partially visible?
1473                if render_cell_area.right() > state.hscroll.offset as u16
1474                    || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1475                {
1476                    if let Some(cell) = header.cells.get(col) {
1477                        if let Some(cell_style) = cell.style {
1478                            row_buf.set_style(render_cell_area, cell_style);
1479                        }
1480                        cell.content.clone().render(render_cell_area, &mut row_buf);
1481                    }
1482                }
1483
1484                col += 1;
1485            }
1486
1487            // render shifted and clipped row.
1488            transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1489        }
1490    }
1491
1492    fn calculate_column_areas(
1493        &self,
1494        columns: usize,
1495        l_columns: &[Rect],
1496        l_spacers: &[Rect],
1497        state: &mut TableState<Selection>,
1498    ) {
1499        state.column_areas.clear();
1500        state.column_layout.clear();
1501
1502        let mut col = 0;
1503        let shift = state.hscroll.offset() as isize;
1504        loop {
1505            if col >= columns {
1506                break;
1507            }
1508
1509            state.column_layout.push(Rect::new(
1510                l_columns[col].x,
1511                0,
1512                l_columns[col].width + l_spacers[col + 1].width,
1513                0,
1514            ));
1515
1516            let cell_x1 = l_columns[col].x as isize;
1517            let cell_x2 =
1518                (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1519
1520            let squish_x1 = cell_x1.saturating_sub(shift);
1521            let squish_x2 = cell_x2.saturating_sub(shift);
1522
1523            let abs_x1 = max(0, squish_x1) as u16;
1524            let abs_x2 = max(0, squish_x2) as u16;
1525
1526            let v_area = Rect::new(
1527                state.table_area.x + abs_x1,
1528                state.table_area.y,
1529                abs_x2 - abs_x1,
1530                state.table_area.height,
1531            );
1532            state
1533                .column_areas
1534                .push(v_area.intersection(state.table_area));
1535
1536            col += 1;
1537        }
1538    }
1539
1540    #[expect(clippy::collapsible_else_if)]
1541    fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1542        if let Some(style) = style {
1543            if let Some(focus_style) = self.focus_style {
1544                if focus && show {
1545                    Some(style.patch(focus_style))
1546                } else {
1547                    Some(fallback_select_style(style))
1548                }
1549            } else {
1550                if focus && show {
1551                    Some(revert_style(style))
1552                } else {
1553                    Some(fallback_select_style(style))
1554                }
1555            }
1556        } else {
1557            None
1558        }
1559    }
1560}
1561
1562impl Default for TableStyle {
1563    fn default() -> Self {
1564        Self {
1565            style: Default::default(),
1566            header: None,
1567            footer: None,
1568            select_row: None,
1569            select_column: None,
1570            select_cell: None,
1571            select_header: None,
1572            select_footer: None,
1573            show_row_focus: true, // non standard
1574            show_column_focus: false,
1575            show_cell_focus: false,
1576            show_header_focus: false,
1577            show_footer_focus: false,
1578            focus_style: None,
1579            block: None,
1580            border_style: None,
1581            scroll: None,
1582            non_exhaustive: NonExhaustive,
1583        }
1584    }
1585}
1586
1587impl<Selection: Clone> Clone for TableState<Selection> {
1588    fn clone(&self) -> Self {
1589        Self {
1590            focus: FocusFlag::named(self.focus.name()),
1591            area: self.area,
1592            inner: self.inner,
1593            header_area: self.header_area,
1594            table_area: self.table_area,
1595            row_areas: self.row_areas.clone(),
1596            column_areas: self.column_areas.clone(),
1597            column_layout: self.column_layout.clone(),
1598            footer_area: self.footer_area,
1599            rows: self.rows,
1600            _counted_rows: self._counted_rows,
1601            columns: self.columns,
1602            vscroll: self.vscroll.clone(),
1603            hscroll: self.hscroll.clone(),
1604            selection: self.selection.clone(),
1605            mouse: Default::default(),
1606            non_exhaustive: NonExhaustive,
1607        }
1608    }
1609}
1610
1611impl<Selection: Default> Default for TableState<Selection> {
1612    fn default() -> Self {
1613        Self {
1614            focus: Default::default(),
1615            area: Default::default(),
1616            inner: Default::default(),
1617            header_area: Default::default(),
1618            table_area: Default::default(),
1619            row_areas: Default::default(),
1620            column_areas: Default::default(),
1621            column_layout: Default::default(),
1622            footer_area: Default::default(),
1623            rows: Default::default(),
1624            _counted_rows: Default::default(),
1625            columns: Default::default(),
1626            vscroll: Default::default(),
1627            hscroll: Default::default(),
1628            selection: Default::default(),
1629            mouse: Default::default(),
1630            non_exhaustive: NonExhaustive,
1631        }
1632    }
1633}
1634
1635impl<Selection> HasFocus for TableState<Selection> {
1636    fn build(&self, builder: &mut FocusBuilder) {
1637        builder.leaf_widget(self);
1638    }
1639
1640    #[inline]
1641    fn focus(&self) -> FocusFlag {
1642        self.focus.clone()
1643    }
1644
1645    #[inline]
1646    fn area(&self) -> Rect {
1647        self.area
1648    }
1649}
1650
1651impl<Selection> RelocatableState for TableState<Selection> {
1652    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1653        self.area = relocate_area(self.area, shift, clip);
1654        self.inner = relocate_area(self.inner, shift, clip);
1655        self.table_area = relocate_area(self.table_area, shift, clip);
1656        self.footer_area = relocate_area(self.footer_area, shift, clip);
1657        self.header_area = relocate_area(self.header_area, shift, clip);
1658
1659        relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1660        relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1661        relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1662
1663        self.hscroll.relocate(shift, clip);
1664        self.vscroll.relocate(shift, clip);
1665    }
1666}
1667
1668impl<Selection> TableState<Selection> {
1669    fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1670        let mut sum_heights = 0;
1671        let mut n_rows = 0;
1672        while let Some(h) = row_heights.pop() {
1673            sum_heights += h;
1674            n_rows += 1;
1675            if sum_heights >= self.table_area.height {
1676                break;
1677            }
1678        }
1679
1680        if sum_heights < self.table_area.height {
1681            None
1682        } else {
1683            Some(n_rows)
1684        }
1685    }
1686}
1687
1688// Baseline
1689impl<Selection> TableState<Selection>
1690where
1691    Selection: Default,
1692{
1693    pub fn new() -> Self {
1694        Self::default()
1695    }
1696
1697    pub fn named(name: &str) -> Self {
1698        Self {
1699            focus: FocusFlag::named(name),
1700            ..TableState::default()
1701        }
1702    }
1703}
1704
1705// Baseline
1706impl<Selection> TableState<Selection> {
1707    /// Number of rows.
1708    #[inline]
1709    pub fn rows(&self) -> usize {
1710        self.rows
1711    }
1712
1713    /// Number of columns.
1714    #[inline]
1715    pub fn columns(&self) -> usize {
1716        self.columns
1717    }
1718}
1719
1720// Table areas
1721impl<Selection> TableState<Selection> {
1722    /// Returns the whole row-area and the cell-areas for the
1723    /// given row, if it is visible.
1724    ///
1725    /// Attention: These areas might be 0-length if the column is scrolled
1726    /// beyond the table-area.
1727    pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1728        if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1729            return None;
1730        }
1731
1732        let mut areas = Vec::new();
1733
1734        let r = self.row_areas[row - self.vscroll.offset()];
1735        for c in &self.column_areas {
1736            areas.push(Rect::new(c.x, r.y, c.width, r.height));
1737        }
1738
1739        Some((r, areas))
1740    }
1741
1742    /// Cell at given position.
1743    pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1744        let col = self.column_at_clicked(pos);
1745        let row = self.row_at_clicked(pos);
1746
1747        match (col, row) {
1748            (Some(col), Some(row)) => Some((col, row)),
1749            _ => None,
1750        }
1751    }
1752
1753    /// Column at given position.
1754    pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1755        self.mouse.column_at(&self.column_areas, pos.0)
1756    }
1757
1758    /// Row at given position.
1759    pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1760        self.mouse
1761            .row_at(&self.row_areas, pos.1)
1762            .map(|v| self.vscroll.offset() + v)
1763    }
1764
1765    /// Cell when dragging. Position can be outside the table area.
1766    /// See [row_at_drag](TableState::row_at_drag), [col_at_drag](TableState::column_at_drag)
1767    pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1768        let col = self.column_at_drag(pos);
1769        let row = self.row_at_drag(pos);
1770
1771        (col, row)
1772    }
1773
1774    /// Row when dragging. Position can be outside the table area.
1775    /// If the position is above the table-area this returns offset - #rows.
1776    /// If the position is below the table-area this returns offset + page_len + #rows.
1777    ///
1778    /// This doesn't account for the row-height of the actual rows outside
1779    /// the table area, just assumes '1'.
1780    pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1781        match self
1782            .mouse
1783            .row_at_drag(self.table_area, &self.row_areas, pos.1)
1784        {
1785            Ok(v) => self.vscroll.offset() + v,
1786            Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1787            Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1788        }
1789    }
1790
1791    /// Column when dragging. Position can be outside the table area.
1792    /// If the position is left of the table area this returns offset - 1.
1793    /// If the position is right of the table area this returns offset + page_width + 1.
1794    pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1795        match self
1796            .mouse
1797            .column_at_drag(self.table_area, &self.column_areas, pos.0)
1798        {
1799            Ok(v) => v,
1800            Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1801            Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1802        }
1803    }
1804}
1805
1806// Offset related.
1807impl<Selection: TableSelection> TableState<Selection> {
1808    /// Sets both offsets to 0.
1809    pub fn clear_offset(&mut self) {
1810        self.vscroll.set_offset(0);
1811        self.hscroll.set_offset(0);
1812    }
1813
1814    /// Maximum offset that is accessible with scrolling.
1815    ///
1816    /// This is shorter than the length by whatever fills the last page.
1817    /// This is the base for the scrollbar content_length.
1818    pub fn row_max_offset(&self) -> usize {
1819        self.vscroll.max_offset()
1820    }
1821
1822    /// Current vertical offset.
1823    pub fn row_offset(&self) -> usize {
1824        self.vscroll.offset()
1825    }
1826
1827    /// Change the vertical offset.
1828    ///
1829    /// Due to overscroll it's possible that this is an invalid offset for the widget.
1830    /// The widget must deal with this situation.
1831    ///
1832    /// The widget returns true if the offset changed at all.
1833    pub fn set_row_offset(&mut self, offset: usize) -> bool {
1834        self.vscroll.set_offset(offset)
1835    }
1836
1837    /// Vertical page-size at the current offset.
1838    pub fn page_len(&self) -> usize {
1839        self.vscroll.page_len()
1840    }
1841
1842    /// Suggested scroll per scroll-event.
1843    pub fn row_scroll_by(&self) -> usize {
1844        self.vscroll.scroll_by()
1845    }
1846
1847    /// Maximum offset that is accessible with scrolling.
1848    ///
1849    /// This is shorter than the length of the content by whatever fills the last page.
1850    /// This is the base for the scrollbar content_length.
1851    pub fn x_max_offset(&self) -> usize {
1852        self.hscroll.max_offset()
1853    }
1854
1855    /// Current horizontal offset.
1856    pub fn x_offset(&self) -> usize {
1857        self.hscroll.offset()
1858    }
1859
1860    /// Change the horizontal offset.
1861    ///
1862    /// Due to overscroll it's possible that this is an invalid offset for the widget.
1863    /// The widget must deal with this situation.
1864    ///
1865    /// The widget returns true if the offset changed at all.
1866    pub fn set_x_offset(&mut self, offset: usize) -> bool {
1867        self.hscroll.set_offset(offset)
1868    }
1869
1870    /// Horizontal page-size at the current offset.
1871    pub fn page_width(&self) -> usize {
1872        self.hscroll.page_len()
1873    }
1874
1875    /// Suggested scroll per scroll-event.
1876    pub fn x_scroll_by(&self) -> usize {
1877        self.hscroll.scroll_by()
1878    }
1879
1880    /// Ensures that the selected item is visible.
1881    /// Caveat: This doesn't work nicely if you have varying row-heights.
1882    pub fn scroll_to_selected(&mut self) -> bool {
1883        if let Some(selected) = self.selection.lead_selection() {
1884            let c = self.scroll_to_col(selected.0);
1885            let r = self.scroll_to_row(selected.1);
1886            r || c
1887        } else {
1888            false
1889        }
1890    }
1891
1892    /// Ensures that the given row is visible.
1893    /// Caveat: This doesn't work nicely if you have varying row-heights.
1894    pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1895        if pos >= self.rows {
1896            false
1897        } else if pos == self.row_offset().saturating_add(self.page_len()) {
1898            // the page might not fill the full area.
1899            let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1900            if heights < self.table_area.height {
1901                false
1902            } else {
1903                self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1904            }
1905        } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1906            self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1907        } else if pos < self.row_offset() {
1908            self.set_row_offset(pos)
1909        } else {
1910            false
1911        }
1912    }
1913
1914    /// Ensures that the given column is completely visible.
1915    pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1916        if let Some(col) = self.column_layout.get(pos) {
1917            if (col.left() as usize) < self.x_offset() {
1918                self.set_x_offset(col.x as usize)
1919            } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1920                self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
1921            } else {
1922                false
1923            }
1924        } else {
1925            false
1926        }
1927    }
1928
1929    /// Ensures that the given position is visible.
1930    pub fn scroll_to_x(&mut self, pos: usize) -> bool {
1931        if pos >= self.x_offset().saturating_add(self.page_width()) {
1932            self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
1933        } else if pos < self.x_offset() {
1934            self.set_x_offset(pos)
1935        } else {
1936            false
1937        }
1938    }
1939
1940    /// Reduce the row-offset by n.
1941    pub fn scroll_up(&mut self, n: usize) -> bool {
1942        self.vscroll.scroll_up(n)
1943    }
1944
1945    /// Increase the row-offset by n.
1946    pub fn scroll_down(&mut self, n: usize) -> bool {
1947        self.vscroll.scroll_down(n)
1948    }
1949
1950    /// Reduce the col-offset by n.
1951    pub fn scroll_left(&mut self, n: usize) -> bool {
1952        self.hscroll.scroll_left(n)
1953    }
1954
1955    /// Increase the col-offset by n.
1956    pub fn scroll_right(&mut self, n: usize) -> bool {
1957        self.hscroll.scroll_right(n)
1958    }
1959}
1960
1961impl TableState<RowSelection> {
1962    /// Update the state to match adding items.
1963    /// This corrects the number of rows, offset and selection.
1964    pub fn items_added(&mut self, pos: usize, n: usize) {
1965        self.vscroll.items_added(pos, n);
1966        self.selection.items_added(pos, n);
1967        self.rows += n;
1968    }
1969
1970    /// Update the state to match removing items.
1971    /// This corrects the number of rows, offset and selection.
1972    pub fn items_removed(&mut self, pos: usize, n: usize) {
1973        self.vscroll.items_removed(pos, n);
1974        self.selection
1975            .items_removed(pos, n, self.rows.saturating_sub(1));
1976        self.rows -= n;
1977    }
1978
1979    /// When scrolling the table, change the selection instead of the offset.
1980    #[inline]
1981    pub fn set_scroll_selection(&mut self, scroll: bool) {
1982        self.selection.set_scroll_selected(scroll);
1983    }
1984
1985    /// Clear the selection.
1986    #[inline]
1987    pub fn clear_selection(&mut self) {
1988        self.selection.clear();
1989    }
1990
1991    /// Anything selected?
1992    #[inline]
1993    pub fn has_selection(&mut self) -> bool {
1994        self.selection.has_selection()
1995    }
1996
1997    /// Selected row.
1998    /// The selected row is not constrained by the row-count.
1999    #[inline]
2000    pub fn selected(&self) -> Option<usize> {
2001        self.selection.selected()
2002    }
2003
2004    /// Return the selected row and ensure it is in the
2005    /// range 0..rows.
2006    #[inline]
2007    #[allow(clippy::manual_filter)]
2008    pub fn selected_checked(&self) -> Option<usize> {
2009        if let Some(selected) = self.selection.selected() {
2010            if selected < self.rows {
2011                Some(selected)
2012            } else {
2013                None
2014            }
2015        } else {
2016            None
2017        }
2018    }
2019
2020    /// Select the row.
2021    /// The selection is not constrained by the row-count.
2022    #[inline]
2023    pub fn select(&mut self, row: Option<usize>) -> bool {
2024        self.selection.select(row)
2025    }
2026
2027    /// Scroll delivers a value between 0 and max_offset as offset.
2028    /// This remaps the ratio to the selection with a range 0..row_len.
2029    ///
2030    pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2031        if self.vscroll.max_offset() > 0 {
2032            (self.rows * offset) / self.vscroll.max_offset()
2033        } else {
2034            0 // ???
2035        }
2036    }
2037
2038    /// Move the selection to the given row.
2039    /// Ensures the row is visible afterward.
2040    #[inline]
2041    pub fn move_to(&mut self, row: usize) -> bool {
2042        let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2043        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2044        r || s
2045    }
2046
2047    /// Move the selection up n rows.
2048    /// Ensures the row is visible afterward.
2049    #[inline]
2050    pub fn move_up(&mut self, n: usize) -> bool {
2051        let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2052        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2053        r || s
2054    }
2055
2056    /// Move the selection down n rows.
2057    /// Ensures the row is visible afterward.
2058    #[inline]
2059    pub fn move_down(&mut self, n: usize) -> bool {
2060        let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2061        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2062        r || s
2063    }
2064}
2065
2066impl TableState<RowSetSelection> {
2067    /// Clear the selection.
2068    #[inline]
2069    pub fn clear_selection(&mut self) {
2070        self.selection.clear();
2071    }
2072
2073    /// Anything selected?
2074    #[inline]
2075    pub fn has_selection(&mut self) -> bool {
2076        self.selection.has_selection()
2077    }
2078
2079    /// Selected rows.
2080    #[inline]
2081    pub fn selected(&self) -> HashSet<usize> {
2082        self.selection.selected()
2083    }
2084
2085    /// Change the lead-selection. Limits the value to the number of rows.
2086    /// If extend is false the current selection is cleared and both lead and
2087    /// anchor are set to the given value.
2088    /// If extend is true, the anchor is kept where it is and lead is changed.
2089    /// Everything in the range `anchor..lead` is selected. It doesn't matter
2090    /// if anchor < lead.
2091    #[inline]
2092    pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2093        self.selection.set_lead(row, extend)
2094    }
2095
2096    /// Current lead.
2097    #[inline]
2098    pub fn lead(&self) -> Option<usize> {
2099        self.selection.lead()
2100    }
2101
2102    /// Current anchor.
2103    #[inline]
2104    pub fn anchor(&self) -> Option<usize> {
2105        self.selection.anchor()
2106    }
2107
2108    /// Retire the current anchor/lead selection to the set of selected rows.
2109    /// Resets lead and anchor and starts a new selection round.
2110    #[inline]
2111    pub fn retire_selection(&mut self) {
2112        self.selection.retire_selection();
2113    }
2114
2115    /// Add to selection. Only works for retired selections, not for the
2116    /// active anchor-lead range.
2117    #[inline]
2118    pub fn add_selected(&mut self, idx: usize) {
2119        self.selection.add(idx);
2120    }
2121
2122    /// Remove from selection. Only works for retired selections, not for the
2123    /// active anchor-lead range.
2124    #[inline]
2125    pub fn remove_selected(&mut self, idx: usize) {
2126        self.selection.remove(idx);
2127    }
2128
2129    /// Move the selection to the given row.
2130    /// Ensures the row is visible afterwards.
2131    #[inline]
2132    pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2133        let r = self
2134            .selection
2135            .move_to(row, self.rows.saturating_sub(1), extend);
2136        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2137        r || s
2138    }
2139
2140    /// Move the selection up n rows.
2141    /// Ensures the row is visible afterwards.
2142    #[inline]
2143    pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2144        let r = self
2145            .selection
2146            .move_up(n, self.rows.saturating_sub(1), extend);
2147        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2148        r || s
2149    }
2150
2151    /// Move the selection down n rows.
2152    /// Ensures the row is visible afterwards.
2153    #[inline]
2154    pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2155        let r = self
2156            .selection
2157            .move_down(n, self.rows.saturating_sub(1), extend);
2158        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2159        r || s
2160    }
2161}
2162
2163impl TableState<CellSelection> {
2164    #[inline]
2165    pub fn clear_selection(&mut self) {
2166        self.selection.clear();
2167    }
2168
2169    #[inline]
2170    pub fn has_selection(&mut self) -> bool {
2171        self.selection.has_selection()
2172    }
2173
2174    /// Selected cell.
2175    #[inline]
2176    pub fn selected(&self) -> Option<(usize, usize)> {
2177        self.selection.selected()
2178    }
2179
2180    /// Select a cell.
2181    #[inline]
2182    pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2183        self.selection.select_cell(select)
2184    }
2185
2186    /// Select a row. Column stays the same.
2187    #[inline]
2188    pub fn select_row(&mut self, row: Option<usize>) -> bool {
2189        if let Some(row) = row {
2190            self.selection
2191                .select_row(Some(min(row, self.rows.saturating_sub(1))))
2192        } else {
2193            self.selection.select_row(None)
2194        }
2195    }
2196
2197    /// Select a column, row stays the same.
2198    #[inline]
2199    pub fn select_column(&mut self, column: Option<usize>) -> bool {
2200        if let Some(column) = column {
2201            self.selection
2202                .select_column(Some(min(column, self.columns.saturating_sub(1))))
2203        } else {
2204            self.selection.select_column(None)
2205        }
2206    }
2207
2208    /// Select a cell, limit to maximum.
2209    #[inline]
2210    pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2211        let r = self.selection.move_to(
2212            select,
2213            (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2214        );
2215        let s = self.scroll_to_selected();
2216        r || s
2217    }
2218
2219    /// Select a row, limit to maximum.
2220    #[inline]
2221    pub fn move_to_row(&mut self, row: usize) -> bool {
2222        let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2223        let s = self.scroll_to_selected();
2224        r || s
2225    }
2226
2227    /// Select a cell, clamp between 0 and maximum.
2228    #[inline]
2229    pub fn move_to_col(&mut self, col: usize) -> bool {
2230        let r = self
2231            .selection
2232            .move_to_col(col, self.columns.saturating_sub(1));
2233        let s = self.scroll_to_selected();
2234        r || s
2235    }
2236
2237    /// Move the selection up n rows.
2238    /// Ensures the row is visible afterwards.
2239    #[inline]
2240    pub fn move_up(&mut self, n: usize) -> bool {
2241        let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2242        let s = self.scroll_to_selected();
2243        r || s
2244    }
2245
2246    /// Move the selection down n rows.
2247    /// Ensures the row is visible afterwards.
2248    #[inline]
2249    pub fn move_down(&mut self, n: usize) -> bool {
2250        let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2251        let s = self.scroll_to_selected();
2252        r || s
2253    }
2254
2255    /// Move the selection left n columns.
2256    /// Ensures the row is visible afterwards.
2257    #[inline]
2258    pub fn move_left(&mut self, n: usize) -> bool {
2259        let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2260        let s = self.scroll_to_selected();
2261        r || s
2262    }
2263
2264    /// Move the selection right n columns.
2265    /// Ensures the row is visible afterwards.
2266    #[inline]
2267    pub fn move_right(&mut self, n: usize) -> bool {
2268        let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2269        let s = self.scroll_to_selected();
2270        r || s
2271    }
2272}
2273
2274impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2275    for TableState<Selection>
2276{
2277    /// Handles double-click events on the table.
2278    fn handle(
2279        &mut self,
2280        event: &crossterm::event::Event,
2281        _keymap: DoubleClick,
2282    ) -> DoubleClickOutcome {
2283        match event {
2284            ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2285                if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2286                    DoubleClickOutcome::ClickClick(col, row)
2287                } else {
2288                    DoubleClickOutcome::Continue
2289                }
2290            }
2291            _ => DoubleClickOutcome::Continue,
2292        }
2293    }
2294}
2295
2296/// Handle all events for recognizing double-clicks.
2297pub fn handle_doubleclick_events<Selection: TableSelection>(
2298    state: &mut TableState<Selection>,
2299    event: &crossterm::event::Event,
2300) -> DoubleClickOutcome {
2301    state.handle(event, DoubleClick)
2302}