rat_ftable/
table.rs

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