rat_ftable/
table.rs

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