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