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