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