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_cursor::HasScreenCursor;
11use rat_event::util::MouseFlags;
12use rat_event::{HandleEvent, ct_event};
13use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
14use rat_reloc::{RelocatableState, relocate_areas};
15use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
16use ratatui::buffer::Buffer;
17use ratatui::layout::{Constraint, Flex, Layout, Rect};
18use ratatui::style::Style;
19use ratatui::widgets::{Block, StatefulWidget, Widget};
20use std::cmp::{max, min};
21use std::collections::HashSet;
22use std::fmt::Debug;
23use std::marker::PhantomData;
24use std::mem;
25use std::rc::Rc;
26
27/// Table widget.
28///
29/// Can be used as a drop-in replacement for the ratatui table. But
30/// that's not the point of this widget.
31///
32/// This widget uses the [TableData](crate::TableData) trait instead
33/// of rendering all the table-cells and putting them into a Vec.
34/// This way rendering time only depends on the screen-size not on
35/// the size of your data.
36///
37/// There is a second trait [TableDataIter](crate::TableDataIter) that
38/// works better if you only have an Iterator over your data.
39///
40/// See [Table::data] and [Table::iter] for an example.
41#[derive(Debug)]
42pub struct Table<'a, Selection = RowSelection> {
43    data: DataRepr<'a>,
44    no_row_count: bool,
45
46    header: Option<Row<'a>>,
47    footer: Option<Row<'a>>,
48
49    widths: Vec<Constraint>,
50    flex: Flex,
51    column_spacing: u16,
52    layout_width: Option<u16>,
53    layout_column_widths: bool,
54
55    style: Style,
56    block: Option<Block<'a>>,
57    hscroll: Option<Scroll<'a>>,
58    vscroll: Option<Scroll<'a>>,
59    header_style: Option<Style>,
60    footer_style: Option<Style>,
61    focus_style: Option<Style>,
62
63    auto_styles: bool,
64    select_row_style: Option<Style>,
65    show_row_focus: bool,
66    select_column_style: Option<Style>,
67    show_column_focus: bool,
68    select_cell_style: Option<Style>,
69    show_cell_focus: bool,
70    select_header_style: Option<Style>,
71    show_header_focus: bool,
72    select_footer_style: Option<Style>,
73    show_footer_focus: bool,
74
75    _phantom: PhantomData<Selection>,
76}
77
78mod data {
79    use crate::textdata::TextTableData;
80    use crate::{TableContext, TableData, TableDataIter};
81    #[cfg(feature = "perf_warnings")]
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(feature = "perf_warnings")]
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, Clone)]
236pub struct TableStyle {
237    pub style: Style,
238    pub block: Option<Block<'static>>,
239    pub border_style: Option<Style>,
240    pub title_style: Option<Style>,
241    pub scroll: Option<ScrollStyle>,
242    pub header: Option<Style>,
243    pub footer: Option<Style>,
244    pub focus_style: Option<Style>,
245
246    pub select_row: Option<Style>,
247    pub select_column: Option<Style>,
248    pub select_cell: Option<Style>,
249    pub select_header: Option<Style>,
250    pub select_footer: Option<Style>,
251
252    pub show_row_focus: bool,
253    pub show_column_focus: bool,
254    pub show_cell_focus: bool,
255    pub show_header_focus: bool,
256    pub show_footer_focus: bool,
257
258    pub non_exhaustive: NonExhaustive,
259}
260
261/// Table state.
262#[derive(Debug)]
263pub struct TableState<Selection = RowSelection> {
264    /// Current focus state.
265    /// __read+write__
266    pub focus: FocusFlag,
267
268    /// Total area.
269    /// __read only__ Renewed with each render.
270    pub area: Rect,
271    /// Area inside the border and scrollbars
272    /// __read only__ Renewed with each render.
273    pub inner: Rect,
274
275    /// Total header area.
276    /// __read only__ Renewed with each render.
277    pub header_area: Rect,
278    /// Total table area.
279    /// __read only__ Renewed with each render.
280    pub table_area: Rect,
281    /// Area per visible row. The first element is at row_offset.
282    /// __read only__ Renewed with each render.
283    pub row_areas: Vec<Rect>,
284    /// Area for each column plus the following spacer if any.
285    /// Invisible columns have width 0, height is the height of the table_area.
286    /// __read only__ Renewed with each render.
287    pub column_areas: Vec<Rect>,
288    /// Layout areas for each column plus the following spacer if any.
289    /// Positions are 0-based, y and height are 0.
290    /// __read only__ Renewed with each render.
291    pub column_layout: Vec<Rect>,
292    /// Total footer area.
293    /// __read only__ Renewed with each render.
294    pub footer_area: Rect,
295
296    /// Row count.
297    /// __read+write__ Renewed with each render anyway.
298    pub rows: usize,
299    // debug info
300    pub _counted_rows: usize,
301    /// Column count.
302    /// __read only__ Renewed with each render.
303    pub columns: usize,
304
305    /// Row scrolling data.
306    /// __read+write__ max_offset set with each render.
307    pub vscroll: ScrollState,
308    /// Column scrolling data.
309    /// __read+write__ max_offset set with each render.
310    pub hscroll: ScrollState,
311
312    /// Selection data.
313    /// __read+write__ selection model. selection is not bound by rows.
314    pub selection: Selection,
315
316    /// Helper for mouse interactions.
317    pub mouse: MouseFlags,
318
319    pub non_exhaustive: NonExhaustive,
320}
321
322impl<Selection> Default for Table<'_, Selection> {
323    fn default() -> Self {
324        Self {
325            data: Default::default(),
326            no_row_count: Default::default(),
327            header: Default::default(),
328            footer: Default::default(),
329            widths: Default::default(),
330            flex: Default::default(),
331            column_spacing: Default::default(),
332            layout_width: Default::default(),
333            layout_column_widths: true,
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(feature = "perf_warnings")]
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    /// Use the sum of all column-lengths as the layout width.
667    ///
668    /// If a [layout_width] is set too, that one will win.
669    ///
670    /// __Panic__
671    ///
672    /// Rendering will panic, if any constraint other than Constraint::Length(),
673    /// Constraint::Min() or Constraint::Max() is used.
674    #[inline]
675    pub fn layout_column_widths(mut self) -> Self {
676        self.layout_column_widths = true;
677        self
678    }
679
680    /// Calculates the width from the given column-constraints.
681    /// If a fixed [layout_width](Table::layout_width) is set too, that one will win.
682    ///
683    /// Panic:
684    /// Rendering will panic, if any constraint other than Constraint::Length(),
685    /// Constraint::Min() or Constraint::Max() is used.
686    #[deprecated(since = "1.1.1", note = "no longer supported")]
687    #[inline]
688    pub fn auto_layout_width(self) -> Self {
689        self
690    }
691
692    /// Draws a block around the table widget.
693    #[inline]
694    pub fn block(mut self, block: Block<'a>) -> Self {
695        self.block = Some(block);
696        self.block = self.block.map(|v| v.style(self.style));
697        self
698    }
699
700    /// Sets the border-style for the Block, if any.
701    pub fn border_style(mut self, style: Style) -> Self {
702        self.block = self.block.map(|v| v.border_style(style));
703        self
704    }
705
706    /// Sets the title-style for the Block, if any.
707    pub fn title_style(mut self, style: Style) -> Self {
708        self.block = self.block.map(|v| v.title_style(style));
709        self
710    }
711
712    /// Scrollbars
713    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
714        self.hscroll = Some(scroll.clone().override_horizontal());
715        self.vscroll = Some(scroll.override_vertical());
716        self
717    }
718
719    /// Scrollbars
720    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
721        self.hscroll = Some(scroll.override_horizontal());
722        self
723    }
724
725    /// Scrollbars
726    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
727        self.vscroll = Some(scroll.override_vertical());
728        self
729    }
730
731    /// Set all styles as a bundle.
732    #[inline]
733    pub fn styles(mut self, styles: TableStyle) -> Self {
734        self.style = styles.style;
735        if styles.block.is_some() {
736            self.block = styles.block;
737        }
738        if let Some(border_style) = styles.border_style {
739            self.block = self.block.map(|v| v.border_style(border_style));
740        }
741        if let Some(title_style) = styles.title_style {
742            self.block = self.block.map(|v| v.title_style(title_style));
743        }
744        self.block = self.block.map(|v| v.style(self.style));
745
746        if let Some(styles) = styles.scroll {
747            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
748            self.vscroll = self.vscroll.map(|v| v.styles(styles));
749        }
750        if styles.header.is_some() {
751            self.header_style = styles.header;
752        }
753        if styles.footer.is_some() {
754            self.footer_style = styles.footer;
755        }
756        if styles.select_row.is_some() {
757            self.select_row_style = styles.select_row;
758        }
759        self.show_row_focus = styles.show_row_focus;
760        if styles.select_column.is_some() {
761            self.select_column_style = styles.select_column;
762        }
763        self.show_column_focus = styles.show_column_focus;
764        if styles.select_cell.is_some() {
765            self.select_cell_style = styles.select_cell;
766        }
767        self.show_cell_focus = styles.show_cell_focus;
768        if styles.select_header.is_some() {
769            self.select_header_style = styles.select_header;
770        }
771        self.show_header_focus = styles.show_header_focus;
772        if styles.select_footer.is_some() {
773            self.select_footer_style = styles.select_footer;
774        }
775        self.show_footer_focus = styles.show_footer_focus;
776        if styles.focus_style.is_some() {
777            self.focus_style = styles.focus_style;
778        }
779        self
780    }
781
782    /// Base style for the table.
783    #[inline]
784    pub fn style(mut self, style: Style) -> Self {
785        self.style = style;
786        self.block = self.block.map(|v| v.style(self.style));
787        self
788    }
789
790    /// Base style for the table.
791    #[inline]
792    pub fn header_style(mut self, style: Option<Style>) -> Self {
793        self.header_style = style;
794        self
795    }
796
797    /// Base style for the table.
798    #[inline]
799    pub fn footer_style(mut self, style: Option<Style>) -> Self {
800        self.footer_style = style;
801        self
802    }
803
804    /// Set the appropriate styles when rendering a cell.
805    /// If this is set to false, no styles will be set at all.
806    /// It's up to the TableData/TableDataIter impl to set the correct styles.
807    ///
808    /// Default is true.
809    #[inline]
810    pub fn auto_styles(mut self, auto_styles: bool) -> Self {
811        self.auto_styles = auto_styles;
812        self
813    }
814
815    /// Style for a selected row. The chosen selection must support
816    /// row-selection for this to take effect.
817    #[inline]
818    pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
819        self.select_row_style = select_style;
820        self
821    }
822
823    /// Add the focus-style to the row-style if the table is focused.
824    #[inline]
825    pub fn show_row_focus(mut self, show: bool) -> Self {
826        self.show_row_focus = show;
827        self
828    }
829
830    /// Style for a selected column. The chosen selection must support
831    /// column-selection for this to take effect.
832    #[inline]
833    pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
834        self.select_column_style = select_style;
835        self
836    }
837
838    /// Add the focus-style to the column-style if the table is focused.
839    #[inline]
840    pub fn show_column_focus(mut self, show: bool) -> Self {
841        self.show_column_focus = show;
842        self
843    }
844
845    /// Style for a selected cell. The chosen selection must support
846    /// cell-selection for this to take effect.
847    #[inline]
848    pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
849        self.select_cell_style = select_style;
850        self
851    }
852
853    /// Add the focus-style to the cell-style if the table is focused.
854    #[inline]
855    pub fn show_cell_focus(mut self, show: bool) -> Self {
856        self.show_cell_focus = show;
857        self
858    }
859
860    /// Style for a selected header cell. The chosen selection must
861    /// support column-selection for this to take effect.
862    #[inline]
863    pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
864        self.select_header_style = select_style;
865        self
866    }
867
868    /// Add the focus-style to the header-style if the table is focused.
869    #[inline]
870    pub fn show_header_focus(mut self, show: bool) -> Self {
871        self.show_header_focus = show;
872        self
873    }
874
875    /// Style for a selected footer cell. The chosen selection must
876    /// support column-selection for this to take effect.
877    #[inline]
878    pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
879        self.select_footer_style = select_style;
880        self
881    }
882
883    /// Add the footer-style to the table-style if the table is focused.
884    #[inline]
885    pub fn show_footer_focus(mut self, show: bool) -> Self {
886        self.show_footer_focus = show;
887        self
888    }
889
890    /// This style will be patched onto the selection to indicate that
891    /// the widget has the input focus.
892    ///
893    /// The selection must support some kind of selection for this to
894    /// be effective.
895    #[inline]
896    pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
897        self.focus_style = focus_style;
898        self
899    }
900
901    /// Inherent width.
902    pub fn width(&self) -> u16 {
903        let sa = ScrollArea::new()
904            .style(self.style)
905            .block(self.block.as_ref())
906            .h_scroll(self.hscroll.as_ref())
907            .v_scroll(self.vscroll.as_ref());
908        let padding = sa.padding();
909        let width = self.total_width(0);
910
911        width + padding.left + padding.right
912    }
913
914    /// Inherent height.
915    pub fn heigth(&self) -> u16 {
916        let sa = ScrollArea::new()
917            .style(self.style)
918            .block(self.block.as_ref())
919            .h_scroll(self.hscroll.as_ref())
920            .v_scroll(self.vscroll.as_ref());
921        let padding = sa.padding();
922        let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
923        let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
924
925        1 + header + footer + padding.top + padding.bottom
926    }
927
928    #[deprecated(since = "1.1.1", note = "not in use")]
929    pub fn debug(self, _: bool) -> Self {
930        self
931    }
932}
933
934impl<Selection> Table<'_, Selection> {
935    // area_width or layout_width
936    #[inline]
937    fn total_width(&self, area_width: u16) -> u16 {
938        if let Some(layout_width) = self.layout_width {
939            layout_width
940        } else if self.layout_column_widths {
941            let mut width = 0;
942            for w in self.widths.iter().copied() {
943                match w {
944                    Constraint::Min(v) => width += v + self.column_spacing,
945                    Constraint::Max(v) => width += v + self.column_spacing,
946                    Constraint::Length(v) => width += v + self.column_spacing,
947                    Constraint::Percentage(p) => {
948                        width += (((area_width as u32) * (p as u32)) / 100) as u16;
949                    }
950                    Constraint::Ratio(n, d) => {
951                        width += (((area_width as u32) * n) / d) as u16;
952                    }
953                    Constraint::Fill(_) => {
954                        /* unsupported, use dummy */
955                        width += 10;
956                    }
957                }
958            }
959            max(width, area_width)
960        } else {
961            area_width
962        }
963    }
964
965    // Do the column-layout. Fill in missing columns, if necessary.
966    #[inline]
967    fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
968        let width = self.total_width(width);
969        let area = Rect::new(0, 0, width, 0);
970
971        let (layout, spacers) = Layout::horizontal(&self.widths)
972            .flex(self.flex)
973            .spacing(self.column_spacing)
974            .split_with_spacers(area);
975
976        (width, layout, spacers)
977    }
978
979    // Layout header/table/footer
980    #[inline]
981    fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
982        let heights = vec![
983            Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
984            Constraint::Fill(1),
985            Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
986        ];
987
988        Layout::vertical(heights).split(area)
989    }
990}
991
992impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
993where
994    Selection: TableSelection,
995{
996    type State = TableState<Selection>;
997
998    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
999        let iter = self.data.iter();
1000        self.render_iter(iter, area, buf, state);
1001    }
1002}
1003
1004impl<Selection> StatefulWidget for Table<'_, Selection>
1005where
1006    Selection: TableSelection,
1007{
1008    type State = TableState<Selection>;
1009
1010    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1011        let iter = mem::take(&mut self.data).into_iter();
1012        self.render_iter(iter, area, buf, state);
1013    }
1014}
1015
1016impl<'a, Selection> Table<'a, Selection>
1017where
1018    Selection: TableSelection,
1019{
1020    /// Render an Iterator over TableRowData.
1021    ///
1022    /// rows: If the row number is known, this can help.
1023    ///
1024    fn render_iter<'b>(
1025        &self,
1026        mut data: DataReprIter<'a, 'b>,
1027        area: Rect,
1028        buf: &mut Buffer,
1029        state: &mut TableState<Selection>,
1030    ) {
1031        if let Some(rows) = data.rows() {
1032            state.rows = rows;
1033        }
1034        state.columns = self.widths.len();
1035        state.area = area;
1036
1037        let sa = ScrollArea::new()
1038            .style(self.style)
1039            .block(self.block.as_ref())
1040            .h_scroll(self.hscroll.as_ref())
1041            .v_scroll(self.vscroll.as_ref());
1042        state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1043
1044        let l_rows = self.layout_areas(state.inner);
1045        state.header_area = l_rows[0];
1046        state.table_area = l_rows[1];
1047        state.footer_area = l_rows[2];
1048
1049        // horizontal layout
1050        let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1051        self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1052
1053        // render block only
1054        sa.render_block(area, buf);
1055
1056        // render header & footer
1057        self.render_header(
1058            state.columns,
1059            width,
1060            l_columns.as_ref(),
1061            l_spacers.as_ref(),
1062            state.header_area,
1063            buf,
1064            state,
1065        );
1066        self.render_footer(
1067            state.columns,
1068            width,
1069            l_columns.as_ref(),
1070            l_spacers.as_ref(),
1071            state.footer_area,
1072            buf,
1073            state,
1074        );
1075
1076        // render table
1077        state.row_areas.clear();
1078        state.vscroll.set_page_len(0);
1079        state.hscroll.set_page_len(area.width as usize);
1080
1081        let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1082        let mut row = None;
1083        let mut row_y = state.table_area.y;
1084        let mut row_heights = Vec::new();
1085        #[cfg(feature = "perf_warnings")]
1086        let mut insane_offset = false;
1087
1088        let mut ctx = TableContext {
1089            focus: state.focus.get(),
1090            selected_cell: false,
1091            selected_row: false,
1092            selected_column: false,
1093            style: self.style,
1094            row_style: None,
1095            select_style: None,
1096            space_area: Default::default(),
1097            row_area: Default::default(),
1098            non_exhaustive: NonExhaustive,
1099        };
1100
1101        if data.nth(state.vscroll.offset()) {
1102            row = Some(state.vscroll.offset());
1103            loop {
1104                ctx.row_style = data.row_style();
1105                // We render each row to a temporary buffer.
1106                // For ease of use we start each row at 0,0.
1107                // We still only render at least partially visible cells.
1108                let render_row_area = Rect::new(0, 0, width, data.row_height());
1109                ctx.row_area = render_row_area;
1110                row_buf.resize(render_row_area);
1111                if self.auto_styles {
1112                    if let Some(row_style) = ctx.row_style {
1113                        row_buf.set_style(render_row_area, row_style);
1114                    } else {
1115                        row_buf.set_style(render_row_area, self.style);
1116                    }
1117                }
1118                row_heights.push(render_row_area.height);
1119
1120                // Target area for the finished row.
1121                let visible_row_area = Rect::new(
1122                    state.table_area.x,
1123                    row_y,
1124                    state.table_area.width,
1125                    render_row_area.height,
1126                )
1127                .intersection(state.table_area);
1128                state.row_areas.push(visible_row_area);
1129                // only count fully visible rows.
1130                if render_row_area.height == visible_row_area.height {
1131                    state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1132                }
1133
1134                // can skip this entirely
1135                if render_row_area.height > 0 {
1136                    let mut col = 0;
1137                    loop {
1138                        if col >= state.columns {
1139                            break;
1140                        }
1141
1142                        let render_cell_area = Rect::new(
1143                            l_columns[col].x,
1144                            0,
1145                            l_columns[col].width,
1146                            render_row_area.height,
1147                        );
1148                        ctx.space_area = Rect::new(
1149                            l_spacers[col + 1].x,
1150                            0,
1151                            l_spacers[col + 1].width,
1152                            render_row_area.height,
1153                        );
1154
1155                        if state.selection.is_selected_cell(col, row.expect("row")) {
1156                            ctx.selected_cell = true;
1157                            ctx.selected_row = false;
1158                            ctx.selected_column = false;
1159                            ctx.select_style = self.patch_select(
1160                                self.select_cell_style,
1161                                state.focus.get(),
1162                                self.show_cell_focus,
1163                            );
1164                        } else if state.selection.is_selected_row(row.expect("row")) {
1165                            ctx.selected_cell = false;
1166                            ctx.selected_row = true;
1167                            ctx.selected_column = false;
1168                            // use a fallback if no row-selected style is set.
1169                            ctx.select_style = if self.select_row_style.is_some() {
1170                                self.patch_select(
1171                                    self.select_row_style,
1172                                    state.focus.get(),
1173                                    self.show_row_focus,
1174                                )
1175                            } else {
1176                                self.patch_select(
1177                                    Some(self.style),
1178                                    state.focus.get(),
1179                                    self.show_row_focus,
1180                                )
1181                            };
1182                        } else if state.selection.is_selected_column(col) {
1183                            ctx.selected_cell = false;
1184                            ctx.selected_row = false;
1185                            ctx.selected_column = true;
1186                            ctx.select_style = self.patch_select(
1187                                self.select_column_style,
1188                                state.focus.get(),
1189                                self.show_column_focus,
1190                            );
1191                        } else {
1192                            ctx.selected_cell = false;
1193                            ctx.selected_row = false;
1194                            ctx.selected_column = false;
1195                            ctx.select_style = None;
1196                        }
1197
1198                        // partially visible?
1199                        if render_cell_area.right() > state.hscroll.offset as u16
1200                            || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1201                        {
1202                            if self.auto_styles {
1203                                if let Some(select_style) = ctx.select_style {
1204                                    row_buf.set_style(render_cell_area, select_style);
1205                                    row_buf.set_style(ctx.space_area, select_style);
1206                                }
1207                            }
1208                            data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1209                        }
1210
1211                        col += 1;
1212                    }
1213
1214                    // render shifted and clipped row.
1215                    transfer_buffer(
1216                        &mut row_buf,
1217                        state.hscroll.offset() as u16,
1218                        visible_row_area,
1219                        buf,
1220                    );
1221                }
1222
1223                if visible_row_area.bottom() >= state.table_area.bottom() {
1224                    break;
1225                }
1226                if !data.nth(0) {
1227                    break;
1228                }
1229                row = Some(row.expect("row").saturating_add(1));
1230                row_y += render_row_area.height;
1231            }
1232        } else {
1233            // can only guess whether the skip failed completely or partially.
1234            // so don't alter row here.
1235
1236            // if this first skip fails all bets are off.
1237            if data.rows().is_none() || data.rows() == Some(0) {
1238                // this is ok
1239            } else {
1240                #[cfg(feature = "perf_warnings")]
1241                {
1242                    insane_offset = true;
1243                }
1244            }
1245        }
1246
1247        // maximum offsets
1248        #[allow(unused_variables)]
1249        let algorithm;
1250        #[allow(unused_assignments)]
1251        {
1252            if let Some(rows) = data.rows() {
1253                algorithm = 0;
1254                // skip to a guess for the last page.
1255                // the guess uses row-height is 1, which may read a few more lines than
1256                // absolutely necessary.
1257                let skip_rows = rows
1258                    .saturating_sub(row.map_or(0, |v| v + 1))
1259                    .saturating_sub(state.table_area.height as usize);
1260                // if we can still skip some rows, then the data so far is useless.
1261                if skip_rows > 0 {
1262                    row_heights.clear();
1263                }
1264                let nth_row = skip_rows;
1265                // collect the remaining row-heights.
1266                if data.nth(nth_row) {
1267                    let mut sum_height = row_heights.iter().sum::<u16>();
1268                    row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1269                    loop {
1270                        let row_height = data.row_height();
1271                        row_heights.push(row_height);
1272
1273                        // Keep a rolling sum of the heights and drop unnecessary info.
1274                        // We don't need more info, and there will be a lot more otherwise.
1275                        sum_height += row_height;
1276                        if sum_height
1277                            .saturating_sub(row_heights.first().copied().unwrap_or_default())
1278                            > state.table_area.height
1279                        {
1280                            let lost_height = row_heights.remove(0);
1281                            sum_height -= lost_height;
1282                        }
1283
1284                        if !data.nth(0) {
1285                            break;
1286                        }
1287
1288                        row = Some(row.expect("row") + 1);
1289                        // if the given number of rows is too small, we would overshoot here.
1290                        if row.expect("row") > rows {
1291                            break;
1292                        }
1293                    }
1294                    // we break before to have an accurate last page.
1295                    // but we still want to report an error, if the count is off.
1296                    while data.nth(0) {
1297                        row = Some(row.expect("row") + 1);
1298                    }
1299                } else {
1300                    // skip failed, maybe again?
1301                    // leave everything as is and report later.
1302                }
1303
1304                state.rows = rows;
1305                state._counted_rows = row.map_or(0, |v| v + 1);
1306
1307                // have we got a page worth of data?
1308                if let Some(last_page) = state.calc_last_page(row_heights) {
1309                    state.vscroll.set_max_offset(state.rows - last_page);
1310                } else {
1311                    // we don't have enough data to establish the last page.
1312                    // either there are not enough rows or the given row-count
1313                    // was off. make a guess.
1314                    state.vscroll.set_max_offset(
1315                        state.rows.saturating_sub(state.table_area.height as usize),
1316                    );
1317                }
1318            } else if self.no_row_count {
1319                algorithm = 1;
1320
1321                // We need to feel out a bit beyond the page, otherwise
1322                // we can't really stabilize the row count and the
1323                // display starts flickering.
1324                if row.is_some() {
1325                    if data.nth(0) {
1326                        // try one past page
1327                        row = Some(row.expect("row").saturating_add(1));
1328                        if data.nth(0) {
1329                            // have an unknown number of rows left.
1330                            row = Some(usize::MAX - 1);
1331                        }
1332                    }
1333                }
1334
1335                state.rows = row.map_or(0, |v| v + 1);
1336                state._counted_rows = row.map_or(0, |v| v + 1);
1337                // rough estimate
1338                state.vscroll.set_max_offset(usize::MAX - 1);
1339                if state.vscroll.page_len() == 0 {
1340                    state.vscroll.set_page_len(state.table_area.height as usize);
1341                }
1342            } else {
1343                algorithm = 2;
1344
1345                // Read all the rest to establish the exact row-count.
1346                let mut sum_height = row_heights.iter().sum::<u16>();
1347                while data.nth(0) {
1348                    let row_height = data.row_height();
1349                    row_heights.push(row_height);
1350
1351                    // Keep a rolling sum of the heights and drop unnecessary info.
1352                    // We don't need more info, and there will be a lot more otherwise.
1353                    sum_height += row_height;
1354                    if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1355                        > state.table_area.height
1356                    {
1357                        let lost_height = row_heights.remove(0);
1358                        sum_height -= lost_height;
1359                    }
1360                    row = Some(row.map_or(0, |v| v + 1));
1361                }
1362
1363                state.rows = row.map_or(0, |v| v + 1);
1364                state._counted_rows = row.map_or(0, |v| v + 1);
1365
1366                // have we got a page worth of data?
1367                if let Some(last_page) = state.calc_last_page(row_heights) {
1368                    state.vscroll.set_max_offset(state.rows - last_page);
1369                } else {
1370                    state.vscroll.set_max_offset(0);
1371                }
1372            }
1373        }
1374        {
1375            state
1376                .hscroll
1377                .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1378        }
1379
1380        // render only the scrollbars.
1381        ScrollArea::new()
1382            .style(self.style)
1383            .block(self.block.as_ref())
1384            .h_scroll(self.hscroll.as_ref())
1385            .v_scroll(self.vscroll.as_ref())
1386            .render_scrollbars(
1387                area,
1388                buf,
1389                &mut ScrollAreaState::new()
1390                    .h_scroll(&mut state.hscroll)
1391                    .v_scroll(&mut state.vscroll),
1392            );
1393
1394        #[cfg(feature = "perf_warnings")]
1395        {
1396            use std::fmt::Write;
1397            let mut msg = String::new();
1398            if insane_offset {
1399                _ = write!(
1400                    msg,
1401                    "Table::render:\n        offset {}\n        rows {}\n        iter-rows {}max\n    don't match up\nCode X{}X\n",
1402                    state.vscroll.offset(),
1403                    state.rows,
1404                    state._counted_rows,
1405                    algorithm
1406                );
1407            }
1408            if state.rows != state._counted_rows {
1409                _ = write!(
1410                    msg,
1411                    "Table::render:\n    rows {} don't match\n    iterated rows {}\nCode X{}X\n",
1412                    state.rows, state._counted_rows, algorithm
1413                );
1414            }
1415            if !msg.is_empty() {
1416                use log::warn;
1417                use ratatui::style::Stylize;
1418                use ratatui::text::Text;
1419
1420                warn!("{}", &msg);
1421                Text::from(msg)
1422                    .white()
1423                    .on_red()
1424                    .render(state.table_area, buf);
1425            }
1426        }
1427    }
1428
1429    #[allow(clippy::too_many_arguments)]
1430    fn render_footer(
1431        &self,
1432        columns: usize,
1433        width: u16,
1434        l_columns: &[Rect],
1435        l_spacers: &[Rect],
1436        area: Rect,
1437        buf: &mut Buffer,
1438        state: &mut TableState<Selection>,
1439    ) {
1440        if let Some(footer) = &self.footer {
1441            let render_row_area = Rect::new(0, 0, width, footer.height);
1442            let mut row_buf = Buffer::empty(render_row_area);
1443
1444            row_buf.set_style(render_row_area, self.style);
1445            if let Some(footer_style) = footer.style {
1446                row_buf.set_style(render_row_area, footer_style);
1447            } else if let Some(footer_style) = self.footer_style {
1448                row_buf.set_style(render_row_area, footer_style);
1449            }
1450
1451            let mut col = 0;
1452            loop {
1453                if col >= columns {
1454                    break;
1455                }
1456
1457                let render_cell_area =
1458                    Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1459                let render_space_area = Rect::new(
1460                    l_spacers[col + 1].x,
1461                    0,
1462                    l_spacers[col + 1].width,
1463                    area.height,
1464                );
1465
1466                if state.selection.is_selected_column(col) {
1467                    if let Some(selected_style) = self.patch_select(
1468                        self.select_footer_style,
1469                        state.focus.get(),
1470                        self.show_footer_focus,
1471                    ) {
1472                        row_buf.set_style(render_cell_area, selected_style);
1473                        row_buf.set_style(render_space_area, selected_style);
1474                    }
1475                };
1476
1477                // partially visible?
1478                if render_cell_area.right() > state.hscroll.offset as u16
1479                    || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1480                {
1481                    if let Some(cell) = footer.cells.get(col) {
1482                        if let Some(cell_style) = cell.style {
1483                            row_buf.set_style(render_cell_area, cell_style);
1484                        }
1485                        cell.content.clone().render(render_cell_area, &mut row_buf);
1486                    }
1487                }
1488
1489                col += 1;
1490            }
1491
1492            // render shifted and clipped row.
1493            transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1494        }
1495    }
1496
1497    #[allow(clippy::too_many_arguments)]
1498    fn render_header(
1499        &self,
1500        columns: usize,
1501        width: u16,
1502        l_columns: &[Rect],
1503        l_spacers: &[Rect],
1504        area: Rect,
1505        buf: &mut Buffer,
1506        state: &mut TableState<Selection>,
1507    ) {
1508        if let Some(header) = &self.header {
1509            let render_row_area = Rect::new(0, 0, width, header.height);
1510            let mut row_buf = Buffer::empty(render_row_area);
1511
1512            row_buf.set_style(render_row_area, self.style);
1513            if let Some(header_style) = header.style {
1514                row_buf.set_style(render_row_area, header_style);
1515            } else if let Some(header_style) = self.header_style {
1516                row_buf.set_style(render_row_area, header_style);
1517            }
1518
1519            let mut col = 0;
1520            loop {
1521                if col >= columns {
1522                    break;
1523                }
1524
1525                let render_cell_area =
1526                    Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1527                let render_space_area = Rect::new(
1528                    l_spacers[col + 1].x,
1529                    0,
1530                    l_spacers[col + 1].width,
1531                    area.height,
1532                );
1533
1534                if state.selection.is_selected_column(col) {
1535                    if let Some(selected_style) = self.patch_select(
1536                        self.select_header_style,
1537                        state.focus.get(),
1538                        self.show_header_focus,
1539                    ) {
1540                        row_buf.set_style(render_cell_area, selected_style);
1541                        row_buf.set_style(render_space_area, selected_style);
1542                    }
1543                };
1544
1545                // partially visible?
1546                if render_cell_area.right() > state.hscroll.offset as u16
1547                    || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1548                {
1549                    if let Some(cell) = header.cells.get(col) {
1550                        if let Some(cell_style) = cell.style {
1551                            row_buf.set_style(render_cell_area, cell_style);
1552                        }
1553                        cell.content.clone().render(render_cell_area, &mut row_buf);
1554                    }
1555                }
1556
1557                col += 1;
1558            }
1559
1560            // render shifted and clipped row.
1561            transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1562        }
1563    }
1564
1565    fn calculate_column_areas(
1566        &self,
1567        columns: usize,
1568        l_columns: &[Rect],
1569        l_spacers: &[Rect],
1570        state: &mut TableState<Selection>,
1571    ) {
1572        state.column_areas.clear();
1573        state.column_layout.clear();
1574
1575        let mut col = 0;
1576        let shift = state.hscroll.offset() as isize;
1577        loop {
1578            if col >= columns {
1579                break;
1580            }
1581
1582            state.column_layout.push(Rect::new(
1583                l_columns[col].x,
1584                0,
1585                l_columns[col].width + l_spacers[col + 1].width,
1586                0,
1587            ));
1588
1589            let cell_x1 = l_columns[col].x as isize;
1590            let cell_x2 =
1591                (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1592
1593            let squish_x1 = cell_x1.saturating_sub(shift);
1594            let squish_x2 = cell_x2.saturating_sub(shift);
1595
1596            let abs_x1 = max(0, squish_x1) as u16;
1597            let abs_x2 = max(0, squish_x2) as u16;
1598
1599            let v_area = Rect::new(
1600                state.table_area.x + abs_x1,
1601                state.table_area.y,
1602                abs_x2 - abs_x1,
1603                state.table_area.height,
1604            );
1605            state
1606                .column_areas
1607                .push(v_area.intersection(state.table_area));
1608
1609            col += 1;
1610        }
1611    }
1612
1613    #[expect(clippy::collapsible_else_if)]
1614    fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1615        if let Some(style) = style {
1616            if let Some(focus_style) = self.focus_style {
1617                if focus && show {
1618                    Some(style.patch(focus_style))
1619                } else {
1620                    Some(fallback_select_style(style))
1621                }
1622            } else {
1623                if focus && show {
1624                    Some(revert_style(style))
1625                } else {
1626                    Some(fallback_select_style(style))
1627                }
1628            }
1629        } else {
1630            None
1631        }
1632    }
1633}
1634
1635impl Default for TableStyle {
1636    fn default() -> Self {
1637        Self {
1638            style: Default::default(),
1639            header: None,
1640            footer: None,
1641            select_row: None,
1642            select_column: None,
1643            select_cell: None,
1644            select_header: None,
1645            select_footer: None,
1646            show_row_focus: true, // non standard
1647            show_column_focus: false,
1648            show_cell_focus: false,
1649            show_header_focus: false,
1650            show_footer_focus: false,
1651            focus_style: None,
1652            block: None,
1653            border_style: None,
1654            title_style: None,
1655            scroll: None,
1656            non_exhaustive: NonExhaustive,
1657        }
1658    }
1659}
1660
1661impl<Selection: Clone> Clone for TableState<Selection> {
1662    fn clone(&self) -> Self {
1663        Self {
1664            focus: self.focus.new_instance(),
1665            area: self.area,
1666            inner: self.inner,
1667            header_area: self.header_area,
1668            table_area: self.table_area,
1669            row_areas: self.row_areas.clone(),
1670            column_areas: self.column_areas.clone(),
1671            column_layout: self.column_layout.clone(),
1672            footer_area: self.footer_area,
1673            rows: self.rows,
1674            _counted_rows: self._counted_rows,
1675            columns: self.columns,
1676            vscroll: self.vscroll.clone(),
1677            hscroll: self.hscroll.clone(),
1678            selection: self.selection.clone(),
1679            mouse: Default::default(),
1680            non_exhaustive: NonExhaustive,
1681        }
1682    }
1683}
1684
1685impl<Selection: Default> Default for TableState<Selection> {
1686    fn default() -> Self {
1687        Self {
1688            focus: Default::default(),
1689            area: Default::default(),
1690            inner: Default::default(),
1691            header_area: Default::default(),
1692            table_area: Default::default(),
1693            row_areas: Default::default(),
1694            column_areas: Default::default(),
1695            column_layout: Default::default(),
1696            footer_area: Default::default(),
1697            rows: Default::default(),
1698            _counted_rows: Default::default(),
1699            columns: Default::default(),
1700            vscroll: Default::default(),
1701            hscroll: Default::default(),
1702            selection: Default::default(),
1703            mouse: Default::default(),
1704            non_exhaustive: NonExhaustive,
1705        }
1706    }
1707}
1708
1709impl<Selection> HasFocus for TableState<Selection> {
1710    fn build(&self, builder: &mut FocusBuilder) {
1711        builder.leaf_widget(self);
1712    }
1713
1714    #[inline]
1715    fn focus(&self) -> FocusFlag {
1716        self.focus.clone()
1717    }
1718
1719    #[inline]
1720    fn area(&self) -> Rect {
1721        self.area
1722    }
1723}
1724
1725impl<Selection> HasScreenCursor for TableState<Selection> {
1726    fn screen_cursor(&self) -> Option<(u16, u16)> {
1727        None
1728    }
1729}
1730
1731impl<Selection> RelocatableState for TableState<Selection> {
1732    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1733        self.area.relocate(shift, clip);
1734        self.inner.relocate(shift, clip);
1735        self.header_area.relocate(shift, clip);
1736        self.table_area.relocate(shift, clip);
1737        relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1738        relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1739        relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1740        self.footer_area.relocate(shift, clip);
1741        self.hscroll.relocate(shift, clip);
1742        self.vscroll.relocate(shift, clip);
1743    }
1744}
1745
1746impl<Selection> TableState<Selection> {
1747    fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1748        let mut sum_heights = 0;
1749        let mut n_rows = 0;
1750        while let Some(h) = row_heights.pop() {
1751            sum_heights += h;
1752            n_rows += 1;
1753            if sum_heights >= self.table_area.height {
1754                break;
1755            }
1756        }
1757
1758        if sum_heights < self.table_area.height {
1759            None
1760        } else {
1761            Some(n_rows)
1762        }
1763    }
1764}
1765
1766// Baseline
1767impl<Selection> TableState<Selection>
1768where
1769    Selection: Default,
1770{
1771    pub fn new() -> Self {
1772        Self::default()
1773    }
1774
1775    pub fn named(name: &str) -> Self {
1776        Self {
1777            focus: FocusFlag::new().with_name(name),
1778            ..TableState::default()
1779        }
1780    }
1781}
1782
1783// Baseline
1784impl<Selection> TableState<Selection> {
1785    /// Number of rows.
1786    #[inline]
1787    pub fn rows(&self) -> usize {
1788        self.rows
1789    }
1790
1791    /// Update the number of rows.
1792    /// This corrects the number of rows *during* event-handling.
1793    /// A number of functions depend on the number of rows,
1794    /// but this value is only updated during render.
1795    ///
1796    /// If you encounter such a case, manually changing the number of rows
1797    /// will fix it.
1798    ///
1799    /// This will *not* change any selection. If you know which items
1800    /// have changed you can use [items_added](TableState::items_added) or
1801    /// [items_removed](TableState::items_removed).
1802    pub fn rows_changed(&mut self, rows: usize) {
1803        self.rows = rows;
1804        self.vscroll
1805            .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1806    }
1807
1808    /// Number of columns.
1809    #[inline]
1810    pub fn columns(&self) -> usize {
1811        self.columns
1812    }
1813}
1814
1815// Table areas
1816impl<Selection> TableState<Selection> {
1817    /// Returns the whole row-area and the cell-areas for the
1818    /// given row, if it is visible.
1819    ///
1820    /// Attention: These areas might be 0-length if the column is scrolled
1821    /// beyond the table-area.
1822    pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1823        if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1824            return None;
1825        }
1826
1827        let mut areas = Vec::new();
1828
1829        let r = self.row_areas[row - self.vscroll.offset()];
1830        for c in &self.column_areas {
1831            areas.push(Rect::new(c.x, r.y, c.width, r.height));
1832        }
1833
1834        Some((r, areas))
1835    }
1836
1837    /// Cell at given position.
1838    pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1839        let col = self.column_at_clicked(pos);
1840        let row = self.row_at_clicked(pos);
1841
1842        match (col, row) {
1843            (Some(col), Some(row)) => Some((col, row)),
1844            _ => None,
1845        }
1846    }
1847
1848    /// Column at given position.
1849    pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1850        self.mouse.column_at(&self.column_areas, pos.0)
1851    }
1852
1853    /// Row at given position.
1854    pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1855        self.mouse
1856            .row_at(&self.row_areas, pos.1)
1857            .map(|v| self.vscroll.offset() + v)
1858    }
1859
1860    /// Cell when dragging. Position can be outside the table area.
1861    /// See [row_at_drag](TableState::row_at_drag), [col_at_drag](TableState::column_at_drag)
1862    pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1863        let col = self.column_at_drag(pos);
1864        let row = self.row_at_drag(pos);
1865
1866        (col, row)
1867    }
1868
1869    /// Row when dragging. Position can be outside the table area.
1870    /// If the position is above the table-area this returns offset - #rows.
1871    /// If the position is below the table-area this returns offset + page_len + #rows.
1872    ///
1873    /// This doesn't account for the row-height of the actual rows outside
1874    /// the table area, just assumes '1'.
1875    pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1876        match self
1877            .mouse
1878            .row_at_drag(self.table_area, &self.row_areas, pos.1)
1879        {
1880            Ok(v) => self.vscroll.offset() + v,
1881            Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1882            Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1883        }
1884    }
1885
1886    /// Column when dragging. Position can be outside the table area.
1887    /// If the position is left of the table area this returns offset - 1.
1888    /// If the position is right of the table area this returns offset + page_width + 1.
1889    pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1890        match self
1891            .mouse
1892            .column_at_drag(self.table_area, &self.column_areas, pos.0)
1893        {
1894            Ok(v) => v,
1895            Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1896            Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1897        }
1898    }
1899}
1900
1901// Offset related.
1902impl<Selection: TableSelection> TableState<Selection> {
1903    /// Sets both offsets to 0.
1904    pub fn clear_offset(&mut self) {
1905        self.vscroll.set_offset(0);
1906        self.hscroll.set_offset(0);
1907    }
1908
1909    /// Maximum offset that is accessible with scrolling.
1910    ///
1911    /// This is shorter than the length by whatever fills the last page.
1912    /// This is the base for the scrollbar content_length.
1913    pub fn row_max_offset(&self) -> usize {
1914        self.vscroll.max_offset()
1915    }
1916
1917    /// Current vertical offset.
1918    pub fn row_offset(&self) -> usize {
1919        self.vscroll.offset()
1920    }
1921
1922    /// Change the vertical offset.
1923    ///
1924    /// Due to overscroll it's possible that this is an invalid offset for the widget.
1925    /// The widget must deal with this situation.
1926    ///
1927    /// The widget returns true if the offset changed at all.
1928    pub fn set_row_offset(&mut self, offset: usize) -> bool {
1929        self.vscroll.set_offset(offset)
1930    }
1931
1932    /// Vertical page-size at the current offset.
1933    pub fn page_len(&self) -> usize {
1934        self.vscroll.page_len()
1935    }
1936
1937    /// Suggested scroll per scroll-event.
1938    pub fn row_scroll_by(&self) -> usize {
1939        self.vscroll.scroll_by()
1940    }
1941
1942    /// Maximum offset that is accessible with scrolling.
1943    ///
1944    /// This is shorter than the length of the content by whatever fills the last page.
1945    /// This is the base for the scrollbar content_length.
1946    pub fn x_max_offset(&self) -> usize {
1947        self.hscroll.max_offset()
1948    }
1949
1950    /// Current horizontal offset.
1951    pub fn x_offset(&self) -> usize {
1952        self.hscroll.offset()
1953    }
1954
1955    /// Change the horizontal offset.
1956    ///
1957    /// Due to overscroll it's possible that this is an invalid offset for the widget.
1958    /// The widget must deal with this situation.
1959    ///
1960    /// The widget returns true if the offset changed at all.
1961    pub fn set_x_offset(&mut self, offset: usize) -> bool {
1962        self.hscroll.set_offset(offset)
1963    }
1964
1965    /// Horizontal page-size at the current offset.
1966    pub fn page_width(&self) -> usize {
1967        self.hscroll.page_len()
1968    }
1969
1970    /// Suggested scroll per scroll-event.
1971    pub fn x_scroll_by(&self) -> usize {
1972        self.hscroll.scroll_by()
1973    }
1974
1975    /// Ensures that the selected item is visible.
1976    /// Caveat: This doesn't work nicely if you have varying row-heights.
1977    /// Caveat: Number of rows needs to be correct.
1978    pub fn scroll_to_selected(&mut self) -> bool {
1979        if let Some(selected) = self.selection.lead_selection() {
1980            let c = self.scroll_to_col(selected.0);
1981            let r = self.scroll_to_row(selected.1);
1982            r || c
1983        } else {
1984            false
1985        }
1986    }
1987
1988    /// Ensures that the given row is visible.
1989    /// Caveat: This doesn't work nicely if you have varying row-heights.
1990    /// Caveat: Number of rows needs to be correct.
1991    // todo: fix for varying heights
1992    pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1993        if pos >= self.rows {
1994            false
1995        } else if pos == self.row_offset().saturating_add(self.page_len()) {
1996            // the page might not fill the full area.
1997            let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1998            if heights < self.table_area.height {
1999                false
2000            } else {
2001                self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2002            }
2003        } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2004            self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2005        } else if pos < self.row_offset() {
2006            self.set_row_offset(pos)
2007        } else {
2008            false
2009        }
2010    }
2011
2012    /// Ensures that the given column is completely visible.
2013    pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2014        if let Some(col) = self.column_layout.get(pos) {
2015            if (col.left() as usize) < self.x_offset() {
2016                self.set_x_offset(col.x as usize)
2017            } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2018                self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2019            } else {
2020                false
2021            }
2022        } else {
2023            false
2024        }
2025    }
2026
2027    /// Ensures that the given position is visible.
2028    pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2029        if pos >= self.x_offset().saturating_add(self.page_width()) {
2030            self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2031        } else if pos < self.x_offset() {
2032            self.set_x_offset(pos)
2033        } else {
2034            false
2035        }
2036    }
2037
2038    /// Reduce the row-offset by n.
2039    pub fn scroll_up(&mut self, n: usize) -> bool {
2040        self.vscroll.scroll_up(n)
2041    }
2042
2043    /// Increase the row-offset by n.
2044    pub fn scroll_down(&mut self, n: usize) -> bool {
2045        self.vscroll.scroll_down(n)
2046    }
2047
2048    /// Reduce the col-offset by n.
2049    pub fn scroll_left(&mut self, n: usize) -> bool {
2050        self.hscroll.scroll_left(n)
2051    }
2052
2053    /// Increase the col-offset by n.
2054    pub fn scroll_right(&mut self, n: usize) -> bool {
2055        self.hscroll.scroll_right(n)
2056    }
2057}
2058
2059impl TableState<RowSelection> {
2060    /// Update the state to match adding items.
2061    /// This corrects the number of rows, offset and selection.
2062    // todo: add for other selection
2063    pub fn items_added(&mut self, pos: usize, n: usize) {
2064        self.vscroll.items_added(pos, n);
2065        self.selection.items_added(pos, n);
2066        self.rows += n;
2067    }
2068
2069    /// Update the state to match removing items.
2070    /// This corrects the number of rows, offset and selection.
2071    // todo: add for other selection
2072    pub fn items_removed(&mut self, pos: usize, n: usize) {
2073        self.vscroll.items_removed(pos, n);
2074        self.selection
2075            .items_removed(pos, n, self.rows.saturating_sub(1));
2076        self.rows -= n;
2077    }
2078
2079    /// When scrolling the table, change the selection instead of the offset.
2080    // todo: add for other selection
2081    #[inline]
2082    pub fn set_scroll_selection(&mut self, scroll: bool) {
2083        self.selection.set_scroll_selected(scroll);
2084    }
2085
2086    /// Clear the selection.
2087    #[inline]
2088    pub fn clear_selection(&mut self) {
2089        self.selection.clear();
2090    }
2091
2092    /// Anything selected?
2093    #[inline]
2094    pub fn has_selection(&mut self) -> bool {
2095        self.selection.has_selection()
2096    }
2097
2098    /// Selected row.
2099    /// The selected row is not constrained by the row-count.
2100    #[inline]
2101    pub fn selected(&self) -> Option<usize> {
2102        self.selection.selected()
2103    }
2104
2105    /// Return the selected row and ensure it is in the
2106    /// range `0..rows`.
2107    #[inline]
2108    #[allow(clippy::manual_filter)]
2109    pub fn selected_checked(&self) -> Option<usize> {
2110        if let Some(selected) = self.selection.selected() {
2111            if selected < self.rows {
2112                Some(selected)
2113            } else {
2114                None
2115            }
2116        } else {
2117            None
2118        }
2119    }
2120
2121    /// Select the row.
2122    /// The selection is not constrained by the row-count.
2123    #[inline]
2124    pub fn select(&mut self, row: Option<usize>) -> bool {
2125        self.selection.select(row)
2126    }
2127
2128    /// Scroll delivers a value between 0 and max_offset as offset.
2129    /// This remaps the ratio to the selection with a range 0..row_len.
2130    /// Info: This is used when scroll_selected is active.
2131    pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2132        if self.vscroll.max_offset() > 0 {
2133            (self.rows * offset) / self.vscroll.max_offset()
2134        } else {
2135            0 // todo: what does this mean?
2136        }
2137    }
2138
2139    /// Set the selection to None and set the offset to 0
2140    #[inline]
2141    pub fn move_deselect(&mut self) -> bool {
2142        let r = self.selection.select(None);
2143        let s = self.set_row_offset(0);
2144        r || s
2145    }
2146
2147    /// Move the selection to the given row.
2148    /// Ensures the row is visible afterward.
2149    #[inline]
2150    pub fn move_to(&mut self, row: usize) -> bool {
2151        let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2152        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2153        r || s
2154    }
2155
2156    /// Move the selection up n rows.
2157    /// Ensures the row is visible afterward.
2158    #[inline]
2159    pub fn move_up(&mut self, n: usize) -> bool {
2160        let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2161        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2162        r || s
2163    }
2164
2165    /// Move the selection down n rows.
2166    /// Ensures the row is visible afterward.
2167    #[inline]
2168    pub fn move_down(&mut self, n: usize) -> bool {
2169        let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2170        let s = self.scroll_to_row(self.selection.selected().expect("row"));
2171        r || s
2172    }
2173}
2174
2175impl TableState<RowSetSelection> {
2176    /// Clear the selection.
2177    #[inline]
2178    pub fn clear_selection(&mut self) {
2179        self.selection.clear();
2180    }
2181
2182    /// Anything selected?
2183    #[inline]
2184    pub fn has_selection(&mut self) -> bool {
2185        self.selection.has_selection()
2186    }
2187
2188    /// Selected rows.
2189    #[inline]
2190    pub fn selected(&self) -> HashSet<usize> {
2191        self.selection.selected()
2192    }
2193
2194    /// Change the lead-selection. Limits the value to the number of rows.
2195    /// If extend is false the current selection is cleared and both lead and
2196    /// anchor are set to the given value.
2197    /// If extend is true, the anchor is kept where it is and lead is changed.
2198    /// Everything in the range `anchor..lead` is selected. It doesn't matter
2199    /// if anchor < lead.
2200    #[inline]
2201    pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2202        self.selection.set_lead(row, extend)
2203    }
2204
2205    /// Current lead.
2206    #[inline]
2207    pub fn lead(&self) -> Option<usize> {
2208        self.selection.lead()
2209    }
2210
2211    /// Current anchor.
2212    #[inline]
2213    pub fn anchor(&self) -> Option<usize> {
2214        self.selection.anchor()
2215    }
2216
2217    /// Retire the current anchor/lead selection to the set of selected rows.
2218    /// Resets lead and anchor and starts a new selection round.
2219    #[inline]
2220    pub fn retire_selection(&mut self) {
2221        self.selection.retire_selection();
2222    }
2223
2224    /// Add to selection. Only works for retired selections, not for the
2225    /// active anchor-lead range.
2226    ///
2227    /// To be sure call [retire_selection] first.
2228    #[inline]
2229    pub fn add_selected(&mut self, idx: usize) {
2230        self.selection.add(idx);
2231    }
2232
2233    /// Remove from selection. Only works for retired selections, not for the
2234    /// active anchor-lead range.
2235    ///
2236    /// To be sure call [retire_selection] first.
2237    #[inline]
2238    pub fn remove_selected(&mut self, idx: usize) {
2239        self.selection.remove(idx);
2240    }
2241
2242    /// Move the selection to the given row.
2243    /// Ensures the row is visible afterward.
2244    #[inline]
2245    pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2246        let r = self
2247            .selection
2248            .move_to(row, self.rows.saturating_sub(1), extend);
2249        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2250        r || s
2251    }
2252
2253    /// Move the selection up n rows.
2254    /// Ensures the row is visible afterward.
2255    #[inline]
2256    pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2257        let r = self
2258            .selection
2259            .move_up(n, self.rows.saturating_sub(1), extend);
2260        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2261        r || s
2262    }
2263
2264    /// Move the selection down n rows.
2265    /// Ensures the row is visible afterwards.
2266    #[inline]
2267    pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2268        let r = self
2269            .selection
2270            .move_down(n, self.rows.saturating_sub(1), extend);
2271        let s = self.scroll_to_row(self.selection.lead().expect("row"));
2272        r || s
2273    }
2274}
2275
2276impl TableState<CellSelection> {
2277    #[inline]
2278    pub fn clear_selection(&mut self) {
2279        self.selection.clear();
2280    }
2281
2282    #[inline]
2283    pub fn has_selection(&mut self) -> bool {
2284        self.selection.has_selection()
2285    }
2286
2287    /// Selected cell.
2288    #[inline]
2289    pub fn selected(&self) -> Option<(usize, usize)> {
2290        self.selection.selected()
2291    }
2292
2293    /// Select a cell.
2294    #[inline]
2295    pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2296        self.selection.select_cell(select)
2297    }
2298
2299    /// Select a row. Column stays the same.
2300    #[inline]
2301    pub fn select_row(&mut self, row: Option<usize>) -> bool {
2302        if let Some(row) = row {
2303            self.selection
2304                .select_row(Some(min(row, self.rows.saturating_sub(1))))
2305        } else {
2306            self.selection.select_row(None)
2307        }
2308    }
2309
2310    /// Select a column, row stays the same.
2311    #[inline]
2312    pub fn select_column(&mut self, column: Option<usize>) -> bool {
2313        if let Some(column) = column {
2314            self.selection
2315                .select_column(Some(min(column, self.columns.saturating_sub(1))))
2316        } else {
2317            self.selection.select_column(None)
2318        }
2319    }
2320
2321    /// Select a cell, limit to maximum.
2322    #[inline]
2323    pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2324        let r = self.selection.move_to(
2325            select,
2326            (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2327        );
2328        let s = self.scroll_to_selected();
2329        r || s
2330    }
2331
2332    /// Select a row, limit to maximum.
2333    #[inline]
2334    pub fn move_to_row(&mut self, row: usize) -> bool {
2335        let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2336        let s = self.scroll_to_selected();
2337        r || s
2338    }
2339
2340    /// Select a cell, clamp between 0 and maximum.
2341    #[inline]
2342    pub fn move_to_col(&mut self, col: usize) -> bool {
2343        let r = self
2344            .selection
2345            .move_to_col(col, self.columns.saturating_sub(1));
2346        let s = self.scroll_to_selected();
2347        r || s
2348    }
2349
2350    /// Move the selection up n rows.
2351    /// Ensures the row is visible afterwards.
2352    #[inline]
2353    pub fn move_up(&mut self, n: usize) -> bool {
2354        let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2355        let s = self.scroll_to_selected();
2356        r || s
2357    }
2358
2359    /// Move the selection down n rows.
2360    /// Ensures the row is visible afterwards.
2361    #[inline]
2362    pub fn move_down(&mut self, n: usize) -> bool {
2363        let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2364        let s = self.scroll_to_selected();
2365        r || s
2366    }
2367
2368    /// Move the selection left n columns.
2369    /// Ensures the row is visible afterwards.
2370    #[inline]
2371    pub fn move_left(&mut self, n: usize) -> bool {
2372        let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2373        let s = self.scroll_to_selected();
2374        r || s
2375    }
2376
2377    /// Move the selection right n columns.
2378    /// Ensures the row is visible afterwards.
2379    #[inline]
2380    pub fn move_right(&mut self, n: usize) -> bool {
2381        let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2382        let s = self.scroll_to_selected();
2383        r || s
2384    }
2385}
2386
2387impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2388    for TableState<Selection>
2389{
2390    /// Handles double-click events on the table.
2391    fn handle(
2392        &mut self,
2393        event: &crossterm::event::Event,
2394        _keymap: DoubleClick,
2395    ) -> DoubleClickOutcome {
2396        match event {
2397            ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2398                if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2399                    DoubleClickOutcome::ClickClick(col, row)
2400                } else {
2401                    DoubleClickOutcome::Continue
2402                }
2403            }
2404            _ => DoubleClickOutcome::Continue,
2405        }
2406    }
2407}
2408
2409/// Handle all events for recognizing double-clicks.
2410pub fn handle_doubleclick_events<Selection: TableSelection>(
2411    state: &mut TableState<Selection>,
2412    event: &crossterm::event::Event,
2413) -> DoubleClickOutcome {
2414    state.handle(event, DoubleClick)
2415}