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