Skip to main content

tui_lipan/widgets/table/
mod.rs

1//! Table widget.
2
3use std::sync::Arc;
4
5use crate::callback::{Callback, KeyHandler};
6use crate::core::element::{Element, ElementKind};
7use crate::core::event::{KeyEvent, MouseEvent};
8use crate::style::{BorderStyle, Length, Padding, ScrollbarConfig, Style, StyleSlot};
9use crate::utils::gradient::{ColorGradient, GradientRange};
10use crate::widgets::scroll::{ScrollKeymap, scroll_action_from_key};
11
12/// A table selection event.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct TableEvent {
15    /// Selected row index.
16    pub index: usize,
17}
18
19/// Semantic role of a table row.
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
21pub enum TableRowRole {
22    /// Regular data row.
23    #[default]
24    Normal,
25    /// Section header row, usually spanning key/value groups.
26    Section,
27    /// Visual separator row.
28    Separator,
29}
30
31/// Disclosure marker state for inspector-like rows.
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum TableDisclosureState {
34    /// Collapsed branch marker.
35    Collapsed,
36    /// Expanded branch marker.
37    Expanded,
38}
39
40/// A cell in a table.
41#[derive(Clone, Debug, Default)]
42pub struct TableCell {
43    pub(crate) content: Arc<str>,
44    pub(crate) style: Style,
45}
46
47impl TableCell {
48    /// Create a new table cell.
49    pub fn new(content: impl Into<Arc<str>>) -> Self {
50        Self {
51            content: content.into(),
52            style: Style::default(),
53        }
54    }
55
56    /// Set cell style.
57    pub fn style(mut self, style: Style) -> Self {
58        self.style = style;
59        self
60    }
61
62    /// Map a numeric value to foreground color using a gradient.
63    pub fn heat_fg(
64        mut self,
65        value: u64,
66        gradient: ColorGradient,
67        range: impl Into<GradientRange>,
68    ) -> Self {
69        let color = gradient.color_for(value, range);
70        self.style = self.style.patch(Style::new().fg(color));
71        self
72    }
73
74    /// Map a numeric value to background color using a gradient.
75    pub fn heat_bg(
76        mut self,
77        value: u64,
78        gradient: ColorGradient,
79        range: impl Into<GradientRange>,
80    ) -> Self {
81        let color = gradient.color_for(value, range);
82        self.style = self.style.patch(Style::new().bg(color));
83        self
84    }
85}
86
87impl<T: Into<Arc<str>>> From<T> for TableCell {
88    fn from(value: T) -> Self {
89        Self::new(value)
90    }
91}
92
93/// A row in a table.
94#[derive(Clone, Debug, Default)]
95pub struct TableRow {
96    pub(crate) cells: Vec<TableCell>,
97    pub(crate) style: Style,
98    pub(crate) height: u16,
99    pub(crate) bottom_margin: u16,
100    pub(crate) role: TableRowRole,
101    pub(crate) depth: u16,
102    pub(crate) disclosure: Option<TableDisclosureState>,
103}
104
105impl TableRow {
106    /// Create a new table row.
107    pub fn new(cells: impl IntoIterator<Item = impl Into<TableCell>>) -> Self {
108        Self {
109            cells: cells.into_iter().map(Into::into).collect(),
110            style: Style::default(),
111            height: 1,
112            bottom_margin: 0,
113            role: TableRowRole::Normal,
114            depth: 0,
115            disclosure: None,
116        }
117    }
118
119    /// Create a key/value row optimized for inspector-style tables.
120    pub fn key_value(key: impl Into<TableCell>, value: impl Into<TableCell>) -> Self {
121        Self::new([key.into(), value.into()])
122    }
123
124    /// Create a section row.
125    pub fn section(title: impl Into<TableCell>) -> Self {
126        Self::new([title.into()]).role(TableRowRole::Section)
127    }
128
129    /// Create a separator row.
130    pub fn separator() -> Self {
131        Self::new(std::iter::empty::<TableCell>()).role(TableRowRole::Separator)
132    }
133
134    /// Set row style.
135    pub fn style(mut self, style: Style) -> Self {
136        self.style = style;
137        self
138    }
139
140    /// Set row height.
141    pub fn height(mut self, height: u16) -> Self {
142        self.height = height;
143        self
144    }
145
146    /// Automatically size the row height based on content line count.
147    pub fn auto_height(mut self) -> Self {
148        self.height = 0;
149        self
150    }
151
152    /// Set bottom margin.
153    pub fn bottom_margin(mut self, margin: u16) -> Self {
154        self.bottom_margin = margin;
155        self
156    }
157
158    /// Set semantic row role.
159    pub fn role(mut self, role: TableRowRole) -> Self {
160        self.role = role;
161        self
162    }
163
164    /// Set indentation depth for inspector rendering.
165    pub fn depth(mut self, depth: u16) -> Self {
166        self.depth = depth;
167        self
168    }
169
170    /// Set disclosure marker state for inspector rendering.
171    pub fn disclosure(mut self, disclosure: TableDisclosureState) -> Self {
172        self.disclosure = Some(disclosure);
173        self
174    }
175}
176
177pub(crate) fn resolved_row_height(row: &TableRow) -> u16 {
178    if row.height > 0 {
179        return row.height;
180    }
181    let mut max_lines = 1u16;
182    for cell in &row.cells {
183        let lines = cell.content.as_ref().lines().count().max(1) as u16;
184        max_lines = max_lines.max(lines);
185    }
186    max_lines
187}
188
189pub(crate) fn resolved_row_total_height(row: &TableRow) -> u16 {
190    resolved_row_height(row).saturating_add(row.bottom_margin)
191}
192
193pub(crate) fn table_header_reserved_height(
194    header: Option<&TableRow>,
195    rows_len: usize,
196    row_gap: u16,
197) -> u16 {
198    header
199        .map(resolved_row_total_height)
200        .unwrap_or(0)
201        .saturating_add(if header.is_some() && rows_len > 0 {
202            row_gap
203        } else {
204            0
205        })
206}
207
208pub(crate) fn row_index_at_visual_offset(
209    rows: &[TableRow],
210    offset: usize,
211    visual_y: u16,
212    row_gap: u16,
213) -> Option<usize> {
214    if rows.is_empty() || offset >= rows.len() {
215        return None;
216    }
217
218    let mut remaining = visual_y;
219    for (index, row) in rows.iter().enumerate().skip(offset) {
220        let row_h = resolved_row_total_height(row).max(1);
221        if remaining < row_h {
222            return Some(index);
223        }
224        remaining = remaining.saturating_sub(row_h);
225
226        if index + 1 < rows.len() {
227            if remaining < row_gap {
228                return None;
229            }
230            remaining = remaining.saturating_sub(row_gap);
231        }
232    }
233
234    None
235}
236
237pub(crate) fn visible_rows_for_height(
238    rows: &[TableRow],
239    offset: usize,
240    available_height: u16,
241    row_gap: u16,
242) -> usize {
243    if available_height == 0 || rows.is_empty() || offset >= rows.len() {
244        return 0;
245    }
246
247    let mut used = 0u16;
248    let mut count = 0usize;
249    for (idx, row) in rows.iter().enumerate().skip(offset) {
250        let row_h = resolved_row_total_height(row).max(1);
251        let gap_before = if count > 0 && idx > offset {
252            row_gap
253        } else {
254            0
255        };
256        let needed = gap_before.saturating_add(row_h);
257        if used.saturating_add(needed) > available_height {
258            break;
259        }
260        used = used.saturating_add(needed);
261        count = count.saturating_add(1);
262    }
263
264    if count == 0 { 1 } else { count }
265}
266
267impl<I, C> From<I> for TableRow
268where
269    I: IntoIterator<Item = C>,
270    C: Into<TableCell>,
271{
272    fn from(iter: I) -> Self {
273        Self::new(iter)
274    }
275}
276
277/// Column width constraint.
278#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
279pub enum ColumnWidth {
280    /// Fixed width in cells.
281    Fixed(u16),
282    /// Percentage of total width.
283    Percent(u16),
284    /// Minimum width (Auto).
285    Min(u16),
286    /// Maximum width.
287    Max(u16),
288    /// Proportional fill.
289    Fill(u16),
290}
291
292/// A table widget.
293#[derive(Clone)]
294pub struct Table {
295    pub(crate) rows: Arc<[TableRow]>,
296    pub(crate) header: Option<TableRow>,
297    pub(crate) widths: Vec<ColumnWidth>,
298    pub(crate) column_styles: Vec<Style>,
299    pub(crate) row_styles: Vec<Style>,
300    pub(crate) selected: Option<usize>,
301    pub(crate) column_spacing: u16,
302    pub(crate) row_gap: u16,
303    pub(crate) style: Style,
304    pub(crate) hover_style: StyleSlot,
305    pub(crate) item_hover_style: StyleSlot,
306    pub(crate) alternating_row_style: Option<Style>,
307    pub(crate) row_style_full_width: bool,
308    pub(crate) selection_style: StyleSlot,
309    pub(crate) selection_symbol: Option<Arc<str>>,
310    pub(crate) selection_symbol_style: Option<Style>,
311    pub(crate) unselected_symbol: Option<Arc<str>>,
312    pub(crate) border: bool,
313    pub(crate) border_style: BorderStyle,
314    pub(crate) padding: Padding,
315
316    // Scrolling support
317    pub(crate) scrollbar: bool,
318    pub(crate) scrollbar_config: ScrollbarConfig,
319    pub(crate) scroll_keys: ScrollKeymap,
320    pub(crate) scroll_wheel: bool,
321
322    // Layout
323    pub(crate) width: Length,
324    pub(crate) height: Length,
325
326    // Events
327    pub(crate) on_select: Option<Callback<TableEvent>>,
328    pub(crate) on_activate: Option<Callback<TableEvent>>,
329    pub(crate) on_click: Option<Callback<MouseEvent>>,
330    pub(crate) on_scroll_to: Option<Callback<usize>>,
331    pub(crate) on_key: Option<KeyHandler>,
332
333    pub(crate) disabled: bool,
334    pub(crate) disabled_style: Style,
335    pub(crate) focusable: bool,
336    pub(crate) tab_stop: bool,
337    pub(crate) on_focus: Option<Callback<()>>,
338    pub(crate) on_blur: Option<Callback<()>>,
339    pub(crate) show_scroll_indicators: bool,
340    pub(crate) scroll_indicator_style: Style,
341
342    // Inspector-style configuration.
343    pub(crate) inspector: bool,
344    pub(crate) inspector_key_style: Style,
345    pub(crate) inspector_value_style: Style,
346    pub(crate) inspector_section_style: Style,
347    pub(crate) inspector_separator_style: Style,
348    pub(crate) inspector_indent_size: u16,
349    pub(crate) inspector_collapsed_symbol: Arc<str>,
350    pub(crate) inspector_expanded_symbol: Arc<str>,
351    pub(crate) inspector_separator_char: char,
352}
353
354impl Default for Table {
355    fn default() -> Self {
356        Self {
357            rows: Arc::new([]),
358            header: None,
359            widths: Vec::new(),
360            column_styles: Vec::new(),
361            row_styles: Vec::new(),
362            selected: Some(0),
363            column_spacing: 1,
364            row_gap: 0,
365            style: Style::default(),
366            hover_style: StyleSlot::Inherit,
367            item_hover_style: StyleSlot::Inherit,
368            alternating_row_style: None,
369            row_style_full_width: false,
370            selection_style: StyleSlot::Inherit,
371            selection_symbol: None,
372            selection_symbol_style: None,
373            unselected_symbol: None,
374            border: false,
375            border_style: BorderStyle::Plain,
376            padding: Padding::default(),
377            scrollbar: false,
378            scrollbar_config: ScrollbarConfig::default(),
379            scroll_keys: ScrollKeymap::default(),
380            scroll_wheel: true,
381            width: Length::Flex(1),
382            height: Length::Flex(1),
383            on_select: None,
384            on_activate: None,
385            on_click: None,
386            on_scroll_to: None,
387            on_key: None,
388            disabled: false,
389            disabled_style: Style::default(),
390            focusable: true,
391            tab_stop: true,
392            on_focus: None,
393            on_blur: None,
394            show_scroll_indicators: false,
395            scroll_indicator_style: Style::default(),
396            inspector: false,
397            inspector_key_style: Style::default(),
398            inspector_value_style: Style::default(),
399            inspector_section_style: Style::default(),
400            inspector_separator_style: Style::default(),
401            inspector_indent_size: 2,
402            inspector_collapsed_symbol: Arc::from("▸"),
403            inspector_expanded_symbol: Arc::from("▾"),
404            inspector_separator_char: '─',
405        }
406    }
407}
408
409impl Table {
410    /// Create a new table.
411    pub fn new() -> Self {
412        Self::default()
413    }
414
415    /// Set table rows.
416    pub fn rows(mut self, rows: impl IntoIterator<Item = impl Into<TableRow>>) -> Self {
417        self.rows = rows.into_iter().map(Into::into).collect::<Vec<_>>().into();
418        self
419    }
420
421    /// Set rows from a shared slice.
422    pub fn rows_arc(mut self, rows: Arc<[TableRow]>) -> Self {
423        self.rows = rows;
424        self
425    }
426
427    /// Add a row.
428    pub fn row(mut self, row: impl Into<TableRow>) -> Self {
429        let mut rows = self.rows.to_vec();
430        rows.push(row.into());
431        self.rows = rows.into();
432        self
433    }
434
435    /// Set header row.
436    pub fn header(mut self, header: impl Into<TableRow>) -> Self {
437        self.header = Some(header.into());
438        self
439    }
440
441    /// Set header row style.
442    pub fn header_style(mut self, style: Style) -> Self {
443        if let Some(header) = &mut self.header {
444            header.style = header.style.patch(style);
445        }
446        self
447    }
448
449    /// Set base style for all rows.
450    pub fn row_style(mut self, style: Style) -> Self {
451        let mut rows = self.rows.to_vec();
452        for row in &mut rows {
453            row.style = row.style.patch(style);
454        }
455        self.rows = rows.into();
456        self
457    }
458
459    /// Patch the style for one zero-based column.
460    ///
461    /// Missing entries are filled with `Style::default()`. The supplied style is patched over any
462    /// existing style at `index` and applies to header and data cells in that column.
463    pub fn column_style(mut self, index: usize, style: Style) -> Self {
464        if self.column_styles.len() <= index {
465            self.column_styles
466                .resize(index.saturating_add(1), Style::default());
467        }
468        self.column_styles[index] = self.column_styles[index].patch(style);
469        self
470    }
471
472    /// Replace the positional column style list.
473    ///
474    /// Styles are matched by zero-based column index and apply to header and data cells.
475    pub fn column_styles(mut self, styles: impl IntoIterator<Item = Style>) -> Self {
476        self.column_styles = styles.into_iter().collect();
477        self
478    }
479
480    /// Patch the style for one zero-based data row by absolute row index.
481    ///
482    /// Missing entries are filled with `Style::default()`. The supplied style is patched over any
483    /// existing style at `index`; header rows are not affected.
484    pub fn row_style_at(mut self, index: usize, style: Style) -> Self {
485        if self.row_styles.len() <= index {
486            self.row_styles
487                .resize(index.saturating_add(1), Style::default());
488        }
489        self.row_styles[index] = self.row_styles[index].patch(style);
490        self
491    }
492
493    /// Replace the positional data-row style list.
494    ///
495    /// Styles are matched by zero-based absolute row index and do not affect the header row.
496    pub fn row_styles(mut self, styles: impl IntoIterator<Item = Style>) -> Self {
497        self.row_styles = styles.into_iter().collect();
498        self
499    }
500
501    /// Set column widths.
502    pub fn widths(mut self, widths: impl IntoIterator<Item = ColumnWidth>) -> Self {
503        self.widths = widths.into_iter().collect();
504        self
505    }
506
507    /// Set selected row index.
508    ///
509    /// Pass `None` for no current row (no selection highlight). Bare integers
510    /// still work via `From<T> for Option<T>` (`table.selected(0)`).
511    pub fn selected(mut self, selected: impl Into<Option<usize>>) -> Self {
512        self.selected = selected.into();
513        self
514    }
515
516    /// Set column spacing.
517    pub fn column_spacing(mut self, spacing: u16) -> Self {
518        self.column_spacing = spacing;
519        self
520    }
521
522    /// Set blank terminal rows inserted between rendered table rows.
523    ///
524    /// The gap is additive with `TableRow::bottom_margin`, applies between the
525    /// header and first data row when both are present, and is not added after
526    /// the final data row or after a header-only table.
527    pub fn row_gap(mut self, gap: u16) -> Self {
528        self.row_gap = gap;
529        self
530    }
531
532    /// Set base style.
533    pub fn style(mut self, style: Style) -> Self {
534        self.style = style;
535        self
536    }
537
538    /// Set style when table is hovered.
539    pub fn hover_style(mut self, style: Style) -> Self {
540        self.hover_style = StyleSlot::Replace(style);
541        self
542    }
543
544    /// Extend the active theme's hover style with additional fields.
545    pub fn extend_hover_style(mut self, style: Style) -> Self {
546        self.hover_style = StyleSlot::Extend(style);
547        self
548    }
549
550    /// Inherit hover style from the active theme.
551    pub fn inherit_hover_style(mut self) -> Self {
552        self.hover_style = StyleSlot::Inherit;
553        self
554    }
555
556    /// Set style for hovered rows.
557    pub fn item_hover_style(mut self, style: Style) -> Self {
558        self.item_hover_style = StyleSlot::Replace(style);
559        self
560    }
561
562    /// Extend the active theme's item hover style with additional fields.
563    pub fn extend_item_hover_style(mut self, style: Style) -> Self {
564        self.item_hover_style = StyleSlot::Extend(style);
565        self
566    }
567
568    /// Inherit item hover style from the active theme.
569    pub fn inherit_item_hover_style(mut self) -> Self {
570        self.item_hover_style = StyleSlot::Inherit;
571        self
572    }
573
574    /// Set alternating style for odd data rows.
575    pub fn alternating_row_style(mut self, style: Style) -> Self {
576        self.alternating_row_style = Some(style);
577        self
578    }
579
580    /// Set whether row-level styles span the full content width.
581    ///
582    /// When enabled, alternating row style, hover style, and selected-row style
583    /// are rendered across the entire row width, not just table cell content.
584    pub fn row_style_full_width(mut self, full_width: bool) -> Self {
585        self.row_style_full_width = full_width;
586        self
587    }
588
589    /// Set highlight style.
590    pub fn selection_style(mut self, style: Style) -> Self {
591        self.selection_style = StyleSlot::Replace(style);
592        self
593    }
594
595    /// Extend the active theme's selection style with additional fields.
596    pub fn extend_selection_style(mut self, style: Style) -> Self {
597        self.selection_style = StyleSlot::Extend(style);
598        self
599    }
600
601    /// Inherit selection style from the active theme.
602    pub fn inherit_selection_style(mut self) -> Self {
603        self.selection_style = StyleSlot::Inherit;
604        self
605    }
606
607    /// Set highlight symbol.
608    pub fn selection_symbol(mut self, symbol: Option<impl Into<Arc<str>>>) -> Self {
609        self.selection_symbol = symbol.map(Into::into);
610        self
611    }
612
613    /// Set style for the highlight symbol.
614    pub fn selection_symbol_style(mut self, style: Style) -> Self {
615        self.selection_symbol_style = Some(style);
616        self
617    }
618
619    /// Set symbol for unselected rows.
620    pub fn unselected_symbol(mut self, symbol: Option<impl Into<Arc<str>>>) -> Self {
621        self.unselected_symbol = symbol.map(Into::into);
622        self
623    }
624
625    /// Enable border.
626    pub fn border(mut self, border: bool) -> Self {
627        self.border = border;
628        self
629    }
630
631    /// Set border style.
632    pub fn border_style(mut self, style: BorderStyle) -> Self {
633        self.border_style = style;
634        self
635    }
636
637    /// Set padding.
638    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
639        self.padding = padding.into();
640        self
641    }
642
643    /// Enable scrollbar.
644    pub fn scrollbar(mut self, scrollbar: bool) -> Self {
645        self.scrollbar = scrollbar;
646        self
647    }
648
649    /// Set scrollbar configuration.
650    pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
651        self.scrollbar_config = config;
652        self
653    }
654
655    /// Set scroll keys.
656    pub fn scroll_keys(mut self, keys: ScrollKeymap) -> Self {
657        self.scroll_keys = keys;
658        self
659    }
660
661    /// Set width.
662    pub fn width(mut self, width: Length) -> Self {
663        self.width = width;
664        self
665    }
666
667    /// Set height.
668    pub fn height(mut self, height: Length) -> Self {
669        self.height = height;
670        self
671    }
672
673    /// Set on-select callback.
674    pub fn on_select(mut self, cb: Callback<TableEvent>) -> Self {
675        self.on_select = Some(cb);
676        self
677    }
678
679    /// Set on-activate callback.
680    pub fn on_activate(mut self, cb: Callback<TableEvent>) -> Self {
681        self.on_activate = Some(cb);
682        self
683    }
684
685    /// Set on-click callback.
686    pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
687        self.on_click = Some(cb);
688        self
689    }
690
691    /// Set on-scroll-to callback.
692    pub fn on_scroll_to(mut self, cb: Callback<usize>) -> Self {
693        self.on_scroll_to = Some(cb);
694        self
695    }
696
697    /// Set on-key handler.
698    pub fn on_key(mut self, handler: KeyHandler) -> Self {
699        self.on_key = Some(handler);
700        self
701    }
702
703    /// Set disabled.
704    pub fn disabled(mut self, disabled: bool) -> Self {
705        self.disabled = disabled;
706        self
707    }
708
709    /// Set focusable.
710    pub fn focusable(mut self, focusable: bool) -> Self {
711        self.focusable = focusable;
712        self
713    }
714
715    /// Control whether the table participates in sequential focus navigation.
716    pub fn tab_stop(mut self, tab_stop: bool) -> Self {
717        self.tab_stop = tab_stop;
718        self
719    }
720
721    /// Set the callback fired when the table receives focus.
722    pub fn on_focus(mut self, cb: Callback<()>) -> Self {
723        self.on_focus = Some(cb);
724        self
725    }
726
727    /// Set the callback fired when the table loses focus.
728    pub fn on_blur(mut self, cb: Callback<()>) -> Self {
729        self.on_blur = Some(cb);
730        self
731    }
732
733    /// Enable scroll indicators when rows are hidden.
734    pub fn show_scroll_indicators(mut self, show: bool) -> Self {
735        self.show_scroll_indicators = show;
736        self
737    }
738
739    /// Set style for scroll indicators.
740    pub fn scroll_indicator_style(mut self, style: Style) -> Self {
741        self.scroll_indicator_style = style;
742        self
743    }
744
745    /// Enable inspector-style row rendering conventions.
746    pub fn inspector(mut self, enabled: bool) -> Self {
747        self.inspector = enabled;
748        self
749    }
750
751    /// Apply inspector defaults and key/value-friendly column widths.
752    pub fn inspector_preset(mut self) -> Self {
753        self.inspector = true;
754        if self.widths.is_empty() {
755            self.widths = vec![ColumnWidth::Min(24), ColumnWidth::Fill(1)];
756        }
757        self
758    }
759
760    /// Style for the key column when inspector mode is enabled.
761    pub fn inspector_key_style(mut self, style: Style) -> Self {
762        self.inspector_key_style = style;
763        self
764    }
765
766    /// Style for value columns when inspector mode is enabled.
767    pub fn inspector_value_style(mut self, style: Style) -> Self {
768        self.inspector_value_style = style;
769        self
770    }
771
772    /// Style for section rows when inspector mode is enabled.
773    pub fn inspector_section_style(mut self, style: Style) -> Self {
774        self.inspector_section_style = style;
775        self
776    }
777
778    /// Style for separator rows when inspector mode is enabled.
779    pub fn inspector_separator_style(mut self, style: Style) -> Self {
780        self.inspector_separator_style = style;
781        self
782    }
783
784    /// Set indentation width (in cells) for inspector mode.
785    pub fn inspector_indent_size(mut self, size: u16) -> Self {
786        self.inspector_indent_size = size.max(1);
787        self
788    }
789
790    /// Set disclosure symbols used by inspector rows with disclosure metadata.
791    pub fn inspector_disclosure_symbols(
792        mut self,
793        collapsed: impl Into<Arc<str>>,
794        expanded: impl Into<Arc<str>>,
795    ) -> Self {
796        self.inspector_collapsed_symbol = collapsed.into();
797        self.inspector_expanded_symbol = expanded.into();
798        self
799    }
800
801    /// Set separator character used by inspector separator rows.
802    pub fn inspector_separator_char(mut self, separator_char: char) -> Self {
803        self.inspector_separator_char = separator_char;
804        self
805    }
806
807    pub(crate) fn next_selection(
808        selected: usize,
809        len: usize,
810        key: &KeyEvent,
811        scroll_keys: ScrollKeymap,
812    ) -> Option<usize> {
813        let action = scroll_action_from_key(key, scroll_keys)?;
814        crate::widgets::list::List::selection_for_action_in_len(selected, len, action)
815    }
816}
817
818impl From<Table> for Element {
819    fn from(value: Table) -> Self {
820        Element::new(ElementKind::Table(Box::new(value)))
821    }
822}
823
824impl crate::layout::hash::LayoutHash for Table {
825    fn layout_hash(
826        &self,
827        hasher: &mut impl std::hash::Hasher,
828        _recurse: &dyn Fn(&Element) -> Option<u64>,
829    ) -> Option<()> {
830        use std::hash::Hash;
831        self.width.hash(hasher);
832        self.height.hash(hasher);
833        self.border.hash(hasher);
834        self.border_style.hash(hasher);
835        self.padding.hash(hasher);
836        self.row_gap.hash(hasher);
837
838        let needs_content = matches!(self.height, Length::Auto);
839        if needs_content {
840            self.rows.len().hash(hasher);
841            if let Some(header) = &self.header {
842                header.height.hash(hasher);
843                header.bottom_margin.hash(hasher);
844            }
845            for row in self.rows.iter() {
846                row.height.hash(hasher);
847                row.bottom_margin.hash(hasher);
848            }
849        }
850
851        self.header.is_some().hash(hasher);
852        self.column_spacing.hash(hasher);
853        self.scrollbar.hash(hasher);
854        self.scrollbar_config.gap.hash(hasher);
855        self.show_scroll_indicators.hash(hasher);
856        self.widths.hash(hasher);
857        self.selected.hash(hasher);
858        Some(())
859    }
860}
861
862mod layout;
863mod node;
864mod reconcile;
865mod shared;
866
867pub(crate) use layout::measure_table;
868pub(crate) use node::TableNode;
869pub(crate) use reconcile::reconcile_table;
870pub(crate) use shared::{
871    TableBorderLineKind, distribute_extra_width, shrink_widths_to_fit, table_border_glyphs,
872    table_border_line, table_fixed_chars, table_render_width,
873};
874
875#[cfg(test)]
876mod arc_setter_tests {
877    use super::{Table, TableRow};
878    use std::sync::Arc;
879
880    #[test]
881    fn rows_arc_preserves_shared_slice() {
882        let rows: Arc<[TableRow]> = Arc::from([TableRow::new(vec!["a", "b"])]);
883        let table = Table::new().rows_arc(Arc::clone(&rows));
884        assert!(Arc::ptr_eq(&table.rows, &rows));
885    }
886}