1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct TableEvent {
15 pub index: usize,
17}
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
21pub enum TableRowRole {
22 #[default]
24 Normal,
25 Section,
27 Separator,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub enum TableDisclosureState {
34 Collapsed,
36 Expanded,
38}
39
40#[derive(Clone, Debug, Default)]
42pub struct TableCell {
43 pub(crate) content: Arc<str>,
44 pub(crate) style: Style,
45}
46
47impl TableCell {
48 pub fn new(content: impl Into<Arc<str>>) -> Self {
50 Self {
51 content: content.into(),
52 style: Style::default(),
53 }
54 }
55
56 pub fn style(mut self, style: Style) -> Self {
58 self.style = style;
59 self
60 }
61
62 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 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#[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 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 pub fn key_value(key: impl Into<TableCell>, value: impl Into<TableCell>) -> Self {
121 Self::new([key.into(), value.into()])
122 }
123
124 pub fn section(title: impl Into<TableCell>) -> Self {
126 Self::new([title.into()]).role(TableRowRole::Section)
127 }
128
129 pub fn separator() -> Self {
131 Self::new(std::iter::empty::<TableCell>()).role(TableRowRole::Separator)
132 }
133
134 pub fn style(mut self, style: Style) -> Self {
136 self.style = style;
137 self
138 }
139
140 pub fn height(mut self, height: u16) -> Self {
142 self.height = height;
143 self
144 }
145
146 pub fn auto_height(mut self) -> Self {
148 self.height = 0;
149 self
150 }
151
152 pub fn bottom_margin(mut self, margin: u16) -> Self {
154 self.bottom_margin = margin;
155 self
156 }
157
158 pub fn role(mut self, role: TableRowRole) -> Self {
160 self.role = role;
161 self
162 }
163
164 pub fn depth(mut self, depth: u16) -> Self {
166 self.depth = depth;
167 self
168 }
169
170 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
279pub enum ColumnWidth {
280 Fixed(u16),
282 Percent(u16),
284 Min(u16),
286 Max(u16),
288 Fill(u16),
290}
291
292#[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 pub(crate) scrollbar: bool,
318 pub(crate) scrollbar_config: ScrollbarConfig,
319 pub(crate) scroll_keys: ScrollKeymap,
320 pub(crate) scroll_wheel: bool,
321
322 pub(crate) width: Length,
324 pub(crate) height: Length,
325
326 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 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 pub fn new() -> Self {
412 Self::default()
413 }
414
415 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 pub fn rows_arc(mut self, rows: Arc<[TableRow]>) -> Self {
423 self.rows = rows;
424 self
425 }
426
427 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 pub fn header(mut self, header: impl Into<TableRow>) -> Self {
437 self.header = Some(header.into());
438 self
439 }
440
441 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 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 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 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 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 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 pub fn widths(mut self, widths: impl IntoIterator<Item = ColumnWidth>) -> Self {
503 self.widths = widths.into_iter().collect();
504 self
505 }
506
507 pub fn selected(mut self, selected: impl Into<Option<usize>>) -> Self {
512 self.selected = selected.into();
513 self
514 }
515
516 pub fn column_spacing(mut self, spacing: u16) -> Self {
518 self.column_spacing = spacing;
519 self
520 }
521
522 pub fn row_gap(mut self, gap: u16) -> Self {
528 self.row_gap = gap;
529 self
530 }
531
532 pub fn style(mut self, style: Style) -> Self {
534 self.style = style;
535 self
536 }
537
538 pub fn hover_style(mut self, style: Style) -> Self {
540 self.hover_style = StyleSlot::Replace(style);
541 self
542 }
543
544 pub fn extend_hover_style(mut self, style: Style) -> Self {
546 self.hover_style = StyleSlot::Extend(style);
547 self
548 }
549
550 pub fn inherit_hover_style(mut self) -> Self {
552 self.hover_style = StyleSlot::Inherit;
553 self
554 }
555
556 pub fn item_hover_style(mut self, style: Style) -> Self {
558 self.item_hover_style = StyleSlot::Replace(style);
559 self
560 }
561
562 pub fn extend_item_hover_style(mut self, style: Style) -> Self {
564 self.item_hover_style = StyleSlot::Extend(style);
565 self
566 }
567
568 pub fn inherit_item_hover_style(mut self) -> Self {
570 self.item_hover_style = StyleSlot::Inherit;
571 self
572 }
573
574 pub fn alternating_row_style(mut self, style: Style) -> Self {
576 self.alternating_row_style = Some(style);
577 self
578 }
579
580 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 pub fn selection_style(mut self, style: Style) -> Self {
591 self.selection_style = StyleSlot::Replace(style);
592 self
593 }
594
595 pub fn extend_selection_style(mut self, style: Style) -> Self {
597 self.selection_style = StyleSlot::Extend(style);
598 self
599 }
600
601 pub fn inherit_selection_style(mut self) -> Self {
603 self.selection_style = StyleSlot::Inherit;
604 self
605 }
606
607 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 pub fn selection_symbol_style(mut self, style: Style) -> Self {
615 self.selection_symbol_style = Some(style);
616 self
617 }
618
619 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 pub fn border(mut self, border: bool) -> Self {
627 self.border = border;
628 self
629 }
630
631 pub fn border_style(mut self, style: BorderStyle) -> Self {
633 self.border_style = style;
634 self
635 }
636
637 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
639 self.padding = padding.into();
640 self
641 }
642
643 pub fn scrollbar(mut self, scrollbar: bool) -> Self {
645 self.scrollbar = scrollbar;
646 self
647 }
648
649 pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
651 self.scrollbar_config = config;
652 self
653 }
654
655 pub fn scroll_keys(mut self, keys: ScrollKeymap) -> Self {
657 self.scroll_keys = keys;
658 self
659 }
660
661 pub fn width(mut self, width: Length) -> Self {
663 self.width = width;
664 self
665 }
666
667 pub fn height(mut self, height: Length) -> Self {
669 self.height = height;
670 self
671 }
672
673 pub fn on_select(mut self, cb: Callback<TableEvent>) -> Self {
675 self.on_select = Some(cb);
676 self
677 }
678
679 pub fn on_activate(mut self, cb: Callback<TableEvent>) -> Self {
681 self.on_activate = Some(cb);
682 self
683 }
684
685 pub fn on_click(mut self, cb: Callback<MouseEvent>) -> Self {
687 self.on_click = Some(cb);
688 self
689 }
690
691 pub fn on_scroll_to(mut self, cb: Callback<usize>) -> Self {
693 self.on_scroll_to = Some(cb);
694 self
695 }
696
697 pub fn on_key(mut self, handler: KeyHandler) -> Self {
699 self.on_key = Some(handler);
700 self
701 }
702
703 pub fn disabled(mut self, disabled: bool) -> Self {
705 self.disabled = disabled;
706 self
707 }
708
709 pub fn focusable(mut self, focusable: bool) -> Self {
711 self.focusable = focusable;
712 self
713 }
714
715 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
717 self.tab_stop = tab_stop;
718 self
719 }
720
721 pub fn on_focus(mut self, cb: Callback<()>) -> Self {
723 self.on_focus = Some(cb);
724 self
725 }
726
727 pub fn on_blur(mut self, cb: Callback<()>) -> Self {
729 self.on_blur = Some(cb);
730 self
731 }
732
733 pub fn show_scroll_indicators(mut self, show: bool) -> Self {
735 self.show_scroll_indicators = show;
736 self
737 }
738
739 pub fn scroll_indicator_style(mut self, style: Style) -> Self {
741 self.scroll_indicator_style = style;
742 self
743 }
744
745 pub fn inspector(mut self, enabled: bool) -> Self {
747 self.inspector = enabled;
748 self
749 }
750
751 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 pub fn inspector_key_style(mut self, style: Style) -> Self {
762 self.inspector_key_style = style;
763 self
764 }
765
766 pub fn inspector_value_style(mut self, style: Style) -> Self {
768 self.inspector_value_style = style;
769 self
770 }
771
772 pub fn inspector_section_style(mut self, style: Style) -> Self {
774 self.inspector_section_style = style;
775 self
776 }
777
778 pub fn inspector_separator_style(mut self, style: Style) -> Self {
780 self.inspector_separator_style = style;
781 self
782 }
783
784 pub fn inspector_indent_size(mut self, size: u16) -> Self {
786 self.inspector_indent_size = size.max(1);
787 self
788 }
789
790 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 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}