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