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