Skip to main content

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