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::buffer::Buffer;
17use ratatui::layout::{Constraint, Flex, Layout, Rect};
18use ratatui::style::Style;
19use ratatui::text::Span;
20use ratatui::widgets::{Block, StatefulWidget, Widget};
21use std::borrow::Cow;
22use std::cmp::{max, min};
23use std::collections::HashSet;
24use std::fmt::Debug;
25use std::marker::PhantomData;
26use std::mem;
27use std::rc::Rc;
28
29#[derive(Debug)]
44pub struct Table<'a, Selection = RowSelection> {
45 data: DataRepr<'a>,
46 no_row_count: bool,
47
48 header: Option<Row<'a>>,
49 footer: Option<Row<'a>>,
50
51 widths: Vec<Constraint>,
52 flex: Flex,
53 column_spacing: u16,
54 layout_width: Option<u16>,
55 layout_column_widths: bool,
56
57 style: Style,
58 block: Option<Block<'a>>,
59 hscroll: Option<Scroll<'a>>,
60 vscroll: Option<Scroll<'a>>,
61 header_style: Option<Style>,
62 footer_style: Option<Style>,
63 focus_style: Option<Style>,
64
65 auto_styles: bool,
66 select_row_style: Option<Style>,
67 show_row_focus: bool,
68 select_column_style: Option<Style>,
69 show_column_focus: bool,
70 select_cell_style: Option<Style>,
71 show_cell_focus: bool,
72 select_header_style: Option<Style>,
73 show_header_focus: bool,
74 select_footer_style: Option<Style>,
75 show_footer_focus: bool,
76
77 show_empty: bool,
78 empty_str: Cow<'a, str>,
79
80 _phantom: PhantomData<Selection>,
81}
82
83mod data {
84 use crate::textdata::TextTableData;
85 use crate::{TableContext, TableData, TableDataIter};
86 #[cfg(feature = "perf_warnings")]
87 use log::warn;
88 use ratatui::buffer::Buffer;
89 use ratatui::layout::Rect;
90 use ratatui::style::{Style, Stylize};
91 use std::fmt::{Debug, Formatter};
92
93 #[derive(Default)]
94 pub(super) enum DataRepr<'a> {
95 #[default]
96 None,
97 Text(TextTableData<'a>),
98 Data(Box<dyn TableData<'a> + 'a>),
99 Iter(Box<dyn TableDataIter<'a> + 'a>),
100 }
101
102 impl<'a> DataRepr<'a> {
103 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
104 match self {
105 DataRepr::None => DataReprIter::None,
106 DataRepr::Text(v) => DataReprIter::IterText(v, None),
107 DataRepr::Data(v) => DataReprIter::IterData(v, None),
108 DataRepr::Iter(v) => DataReprIter::IterIter(v),
109 }
110 }
111
112 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
113 match self {
114 DataRepr::None => DataReprIter::None,
115 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
116 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
117 DataRepr::Iter(v) => {
118 if let Some(v) = v.cloned() {
120 DataReprIter::IterIter(v)
121 } else {
122 DataReprIter::Invalid(None)
123 }
124 }
125 }
126 }
127 }
128
129 impl Debug for DataRepr<'_> {
130 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("Data").finish()
132 }
133 }
134
135 #[derive(Default)]
136 pub(super) enum DataReprIter<'a, 'b> {
137 #[default]
138 None,
139 #[allow(dead_code)]
140 Invalid(Option<usize>),
141 IterText(TextTableData<'a>, Option<usize>),
142 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
143 #[allow(dead_code)]
144 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
145 IterIter(Box<dyn TableDataIter<'a> + 'a>),
146 }
147
148 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
149 fn rows(&self) -> Option<usize> {
150 match self {
151 DataReprIter::None => Some(0),
152 DataReprIter::Invalid(_) => Some(1),
153 DataReprIter::IterText(v, _) => Some(v.rows.len()),
154 DataReprIter::IterData(v, _) => Some(v.rows()),
155 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
156 DataReprIter::IterIter(v) => v.rows(),
157 }
158 }
159
160 fn nth(&mut self, n: usize) -> bool {
161 let incr = |row: &mut Option<usize>, rows: usize| match *row {
162 None => {
163 *row = Some(n);
164 *row < Some(rows)
165 }
166 Some(w) => {
167 *row = Some(w.saturating_add(n).saturating_add(1));
168 *row < Some(rows)
169 }
170 };
171
172 match self {
173 DataReprIter::None => false,
174 DataReprIter::Invalid(row) => incr(row, 1),
175 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
176 DataReprIter::IterData(v, row) => incr(row, v.rows()),
177 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
178 DataReprIter::IterIter(v) => v.nth(n),
179 }
180 }
181
182 fn row_height(&self) -> u16 {
184 match self {
185 DataReprIter::None => 1,
186 DataReprIter::Invalid(_) => 1,
187 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
188 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
189 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
190 DataReprIter::IterIter(v) => v.row_height(),
191 }
192 }
193
194 fn row_style(&self) -> Option<Style> {
195 match self {
196 DataReprIter::None => None,
197 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
198 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
199 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
200 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
201 DataReprIter::IterIter(v) => v.row_style(),
202 }
203 }
204
205 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
207 match self {
208 DataReprIter::None => {}
209 DataReprIter::Invalid(_) => {
210 if column == 0 {
211 #[cfg(feature = "perf_warnings")]
212 warn!(
213 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
214 );
215
216 buf.set_string(
217 area.x,
218 area.y,
219 "TableDataIter must implement a valid cloned() for this",
220 Style::default(),
221 );
222 }
223 }
224 DataReprIter::IterText(v, n) => {
225 v.render_cell(ctx, column, n.expect("row"), area, buf)
226 }
227 DataReprIter::IterData(v, n) => {
228 v.render_cell(ctx, column, n.expect("row"), area, buf)
229 }
230 DataReprIter::IterDataRef(v, n) => {
231 v.render_cell(ctx, column, n.expect("row"), area, buf)
232 }
233 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
234 }
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct TableStyle {
242 pub style: Style,
243 pub block: Option<Block<'static>>,
244 pub border_style: Option<Style>,
245 pub title_style: Option<Style>,
246 pub scroll: Option<ScrollStyle>,
247 pub header: Option<Style>,
248 pub footer: Option<Style>,
249 pub focus_style: Option<Style>,
250
251 pub select_row: Option<Style>,
252 pub select_column: Option<Style>,
253 pub select_cell: Option<Style>,
254 pub select_header: Option<Style>,
255 pub select_footer: Option<Style>,
256
257 pub show_row_focus: bool,
258 pub show_column_focus: bool,
259 pub show_cell_focus: bool,
260 pub show_header_focus: bool,
261 pub show_footer_focus: bool,
262 pub show_empty: bool,
263 pub empty_str: Option<Cow<'static, str>>,
264
265 pub non_exhaustive: NonExhaustive,
266}
267
268#[derive(Debug)]
270pub struct TableState<Selection = RowSelection> {
271 pub focus: FocusFlag,
274
275 pub area: Rect,
278 pub inner: Rect,
281
282 pub header_area: Rect,
285 pub table_area: Rect,
288 pub row_areas: Vec<Rect>,
291 pub column_areas: Vec<Rect>,
295 pub column_layout: Vec<Rect>,
299 pub footer_area: Rect,
302
303 pub rows: usize,
306 pub _counted_rows: usize,
308 pub columns: usize,
311
312 pub vscroll: ScrollState,
315 pub hscroll: ScrollState,
318
319 pub selection: Selection,
322
323 pub mouse: MouseFlags,
325
326 pub non_exhaustive: NonExhaustive,
327}
328
329impl<Selection> Default for Table<'_, Selection> {
330 fn default() -> Self {
331 Self {
332 data: Default::default(),
333 no_row_count: Default::default(),
334 header: Default::default(),
335 footer: Default::default(),
336 widths: Default::default(),
337 flex: Default::default(),
338 column_spacing: Default::default(),
339 layout_width: Default::default(),
340 layout_column_widths: true,
341 block: Default::default(),
342 hscroll: Default::default(),
343 vscroll: Default::default(),
344 header_style: Default::default(),
345 footer_style: Default::default(),
346 style: Default::default(),
347 auto_styles: true,
348 select_row_style: Default::default(),
349 show_row_focus: true,
350 select_column_style: Default::default(),
351 show_column_focus: Default::default(),
352 select_cell_style: Default::default(),
353 show_cell_focus: Default::default(),
354 select_header_style: Default::default(),
355 show_header_focus: Default::default(),
356 select_footer_style: Default::default(),
357 show_footer_focus: Default::default(),
358 focus_style: Default::default(),
359 _phantom: Default::default(),
360 show_empty: Default::default(),
361 empty_str: Cow::Borrowed(" \u{2205} "),
362 }
363 }
364}
365
366impl<'a, Selection> Table<'a, Selection> {
367 pub fn new() -> Self
369 where
370 Selection: Default,
371 {
372 Self::default()
373 }
374
375 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
380 where
381 R: IntoIterator,
382 R::Item: Into<Row<'a>>,
383 C: IntoIterator,
384 C::Item: Into<Constraint>,
385 Selection: Default,
386 {
387 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
388 let data = TextTableData {
389 rows: rows.into_iter().map(|v| v.into()).collect(),
390 };
391 Self {
392 data: DataRepr::Text(data),
393 widths,
394 ..Default::default()
395 }
396 }
397
398 pub fn rows<T>(mut self, rows: T) -> Self
402 where
403 T: IntoIterator<Item = Row<'a>>,
404 {
405 let rows = rows.into_iter().collect();
406 self.data = DataRepr::Text(TextTableData { rows });
407 self
408 }
409
410 #[inline]
477 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
478 self.widths = data.widths();
479 self.header = data.header();
480 self.footer = data.footer();
481 self.data = DataRepr::Data(Box::new(data));
482 self
483 }
484
485 #[inline]
594 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
595 #[cfg(feature = "perf_warnings")]
596 if data.rows().is_none() {
597 use log::warn;
598 warn!("Table::iter - rows is None, this will be slower");
599 }
600 self.header = data.header();
601 self.footer = data.footer();
602 self.widths = data.widths();
603 self.data = DataRepr::Iter(Box::new(data));
604 self
605 }
606
607 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
625 self.no_row_count = no_row_count;
626 self
627 }
628
629 #[inline]
631 pub fn header(mut self, header: Row<'a>) -> Self {
632 self.header = Some(header);
633 self
634 }
635
636 #[inline]
638 pub fn footer(mut self, footer: Row<'a>) -> Self {
639 self.footer = Some(footer);
640 self
641 }
642
643 pub fn widths<I>(mut self, widths: I) -> Self
645 where
646 I: IntoIterator,
647 I::Item: Into<Constraint>,
648 {
649 self.widths = widths.into_iter().map(|v| v.into()).collect();
650 self
651 }
652
653 #[inline]
655 pub fn flex(mut self, flex: Flex) -> Self {
656 self.flex = flex;
657 self
658 }
659
660 #[inline]
662 pub fn column_spacing(mut self, spacing: u16) -> Self {
663 self.column_spacing = spacing;
664 self
665 }
666
667 #[inline]
670 pub fn layout_width(mut self, width: u16) -> Self {
671 self.layout_width = Some(width);
672 self
673 }
674
675 #[inline]
684 pub fn layout_column_widths(mut self) -> Self {
685 self.layout_column_widths = true;
686 self
687 }
688
689 #[deprecated(since = "1.1.1", note = "no longer supported")]
696 #[inline]
697 pub fn auto_layout_width(self) -> Self {
698 self
699 }
700
701 #[inline]
703 pub fn block(mut self, block: Block<'a>) -> Self {
704 self.block = Some(block);
705 self.block = self.block.map(|v| v.style(self.style));
706 self
707 }
708
709 pub fn border_style(mut self, style: Style) -> Self {
711 self.block = self.block.map(|v| v.border_style(style));
712 self
713 }
714
715 pub fn title_style(mut self, style: Style) -> Self {
717 self.block = self.block.map(|v| v.title_style(style));
718 self
719 }
720
721 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
723 self.hscroll = Some(scroll.clone().override_horizontal());
724 self.vscroll = Some(scroll.override_vertical());
725 self
726 }
727
728 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
730 self.hscroll = Some(scroll.override_horizontal());
731 self
732 }
733
734 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
736 self.vscroll = Some(scroll.override_vertical());
737 self
738 }
739
740 #[inline]
742 pub fn styles(mut self, styles: TableStyle) -> Self {
743 self.style = styles.style;
744 if styles.block.is_some() {
745 self.block = styles.block;
746 }
747 if let Some(border_style) = styles.border_style {
748 self.block = self.block.map(|v| v.border_style(border_style));
749 }
750 if let Some(title_style) = styles.title_style {
751 self.block = self.block.map(|v| v.title_style(title_style));
752 }
753 self.block = self.block.map(|v| v.style(self.style));
754
755 if let Some(styles) = styles.scroll {
756 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
757 self.vscroll = self.vscroll.map(|v| v.styles(styles));
758 }
759 if styles.header.is_some() {
760 self.header_style = styles.header;
761 }
762 if styles.footer.is_some() {
763 self.footer_style = styles.footer;
764 }
765 if styles.select_row.is_some() {
766 self.select_row_style = styles.select_row;
767 }
768 self.show_row_focus = styles.show_row_focus;
769 if styles.select_column.is_some() {
770 self.select_column_style = styles.select_column;
771 }
772 self.show_column_focus = styles.show_column_focus;
773 if styles.select_cell.is_some() {
774 self.select_cell_style = styles.select_cell;
775 }
776 self.show_cell_focus = styles.show_cell_focus;
777 if styles.select_header.is_some() {
778 self.select_header_style = styles.select_header;
779 }
780 self.show_header_focus = styles.show_header_focus;
781 if styles.select_footer.is_some() {
782 self.select_footer_style = styles.select_footer;
783 }
784 self.show_footer_focus = styles.show_footer_focus;
785 if styles.focus_style.is_some() {
786 self.focus_style = styles.focus_style;
787 }
788 self.show_empty = styles.show_empty;
789 if let Some(empty_str) = styles.empty_str {
790 self.empty_str = empty_str;
791 }
792 self
793 }
794
795 #[inline]
797 pub fn style(mut self, style: Style) -> Self {
798 self.style = style;
799 self.block = self.block.map(|v| v.style(self.style));
800 self
801 }
802
803 #[inline]
805 pub fn header_style(mut self, style: Option<Style>) -> Self {
806 self.header_style = style;
807 self
808 }
809
810 #[inline]
812 pub fn footer_style(mut self, style: Option<Style>) -> Self {
813 self.footer_style = style;
814 self
815 }
816
817 #[inline]
823 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
824 self.auto_styles = auto_styles;
825 self
826 }
827
828 #[inline]
831 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
832 self.select_row_style = select_style;
833 self
834 }
835
836 #[inline]
838 pub fn show_row_focus(mut self, show: bool) -> Self {
839 self.show_row_focus = show;
840 self
841 }
842
843 #[inline]
846 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
847 self.select_column_style = select_style;
848 self
849 }
850
851 #[inline]
853 pub fn show_column_focus(mut self, show: bool) -> Self {
854 self.show_column_focus = show;
855 self
856 }
857
858 #[inline]
861 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
862 self.select_cell_style = select_style;
863 self
864 }
865
866 #[inline]
868 pub fn show_cell_focus(mut self, show: bool) -> Self {
869 self.show_cell_focus = show;
870 self
871 }
872
873 #[inline]
876 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
877 self.select_header_style = select_style;
878 self
879 }
880
881 #[inline]
883 pub fn show_header_focus(mut self, show: bool) -> Self {
884 self.show_header_focus = show;
885 self
886 }
887
888 #[inline]
891 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
892 self.select_footer_style = select_style;
893 self
894 }
895
896 #[inline]
898 pub fn show_footer_focus(mut self, show: bool) -> Self {
899 self.show_footer_focus = show;
900 self
901 }
902
903 #[inline]
909 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
910 self.focus_style = focus_style;
911 self
912 }
913
914 #[inline]
916 pub fn show_empty(mut self, show: bool) -> Self {
917 self.show_empty = show;
918 self
919 }
920
921 #[inline]
922 pub fn show_empty_str(mut self, str: impl Into<Cow<'a, str>>) -> Self {
923 self.empty_str = str.into();
924 self
925 }
926
927 pub fn width(&self) -> u16 {
929 let sa = ScrollArea::new()
930 .style(self.style)
931 .block(self.block.as_ref())
932 .h_scroll(self.hscroll.as_ref())
933 .v_scroll(self.vscroll.as_ref());
934 let padding = sa.padding();
935 let width = self.total_width(0);
936
937 width + padding.left + padding.right
938 }
939
940 pub fn heigth(&self) -> u16 {
942 let sa = ScrollArea::new()
943 .style(self.style)
944 .block(self.block.as_ref())
945 .h_scroll(self.hscroll.as_ref())
946 .v_scroll(self.vscroll.as_ref());
947 let padding = sa.padding();
948 let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
949 let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
950
951 1 + header + footer + padding.top + padding.bottom
952 }
953
954 #[deprecated(since = "1.1.1", note = "not in use")]
955 pub fn debug(self, _: bool) -> Self {
956 self
957 }
958}
959
960impl<Selection> Table<'_, Selection> {
961 #[inline]
963 fn total_width(&self, area_width: u16) -> u16 {
964 if let Some(layout_width) = self.layout_width {
965 layout_width
966 } else if self.layout_column_widths {
967 let mut width = 0;
968 for w in self.widths.iter().copied() {
969 match w {
970 Constraint::Min(v) => width += v + self.column_spacing,
971 Constraint::Max(v) => width += v + self.column_spacing,
972 Constraint::Length(v) => width += v + self.column_spacing,
973 Constraint::Percentage(p) => {
974 width += (((area_width as u32) * (p as u32)) / 100) as u16;
975 }
976 Constraint::Ratio(n, d) => {
977 width += (((area_width as u32) * n) / d) as u16;
978 }
979 Constraint::Fill(_) => {
980 width += 10;
982 }
983 }
984 }
985 max(width, area_width)
986 } else {
987 area_width
988 }
989 }
990
991 #[inline]
993 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
994 let width = self.total_width(width);
995 let area = Rect::new(0, 0, width, 0);
996
997 let (layout, spacers) = Layout::horizontal(&self.widths)
998 .flex(self.flex)
999 .spacing(self.column_spacing)
1000 .split_with_spacers(area);
1001
1002 (width, layout, spacers)
1003 }
1004
1005 #[inline]
1007 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
1008 let heights = vec![
1009 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
1010 Constraint::Fill(1),
1011 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
1012 ];
1013
1014 Layout::vertical(heights).split(area)
1015 }
1016}
1017
1018impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
1019where
1020 Selection: TableSelection,
1021{
1022 type State = TableState<Selection>;
1023
1024 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1025 let iter = self.data.iter();
1026 self.render_iter(iter, area, buf, state);
1027 }
1028}
1029
1030impl<Selection> StatefulWidget for Table<'_, Selection>
1031where
1032 Selection: TableSelection,
1033{
1034 type State = TableState<Selection>;
1035
1036 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1037 let iter = mem::take(&mut self.data).into_iter();
1038 self.render_iter(iter, area, buf, state);
1039 }
1040}
1041
1042impl<'a, Selection> Table<'a, Selection>
1043where
1044 Selection: TableSelection,
1045{
1046 fn render_iter<'b>(
1051 &self,
1052 mut data: DataReprIter<'a, 'b>,
1053 area: Rect,
1054 buf: &mut Buffer,
1055 state: &mut TableState<Selection>,
1056 ) {
1057 if let Some(rows) = data.rows() {
1058 state.rows = rows;
1059 }
1060 state.columns = self.widths.len();
1061 state.area = area;
1062
1063 let sa = ScrollArea::new()
1064 .style(self.style)
1065 .block(self.block.as_ref())
1066 .h_scroll(self.hscroll.as_ref())
1067 .v_scroll(self.vscroll.as_ref());
1068 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1069
1070 let l_rows = self.layout_areas(state.inner);
1071 state.header_area = l_rows[0];
1072 state.table_area = l_rows[1];
1073 state.footer_area = l_rows[2];
1074
1075 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1077 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1078
1079 sa.render_block(area, buf);
1081
1082 self.render_header(
1084 state.columns,
1085 width,
1086 l_columns.as_ref(),
1087 l_spacers.as_ref(),
1088 state.header_area,
1089 buf,
1090 state,
1091 );
1092 self.render_footer(
1093 state.columns,
1094 width,
1095 l_columns.as_ref(),
1096 l_spacers.as_ref(),
1097 state.footer_area,
1098 buf,
1099 state,
1100 );
1101
1102 state.row_areas.clear();
1104 state.vscroll.set_page_len(0);
1105 state.hscroll.set_page_len(area.width as usize);
1106
1107 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1108 let mut row = None;
1109 let mut row_y = state.table_area.y;
1110 let mut row_heights = Vec::new();
1111 #[cfg(feature = "perf_warnings")]
1112 let mut insane_offset = false;
1113
1114 let mut ctx = TableContext {
1115 focus: state.focus.get(),
1116 selected_cell: false,
1117 selected_row: false,
1118 selected_column: false,
1119 style: self.style,
1120 row_style: None,
1121 select_style: None,
1122 space_area: Default::default(),
1123 row_area: Default::default(),
1124 non_exhaustive: NonExhaustive,
1125 };
1126
1127 if data.nth(state.vscroll.offset()) {
1128 row = Some(state.vscroll.offset());
1129 loop {
1130 ctx.row_style = data.row_style();
1131 let render_row_area = Rect::new(0, 0, width, data.row_height());
1135 ctx.row_area = render_row_area;
1136 row_buf.resize(render_row_area);
1137 if self.auto_styles {
1138 if let Some(row_style) = ctx.row_style {
1139 row_buf.set_style(render_row_area, row_style);
1140 } else {
1141 row_buf.set_style(render_row_area, self.style);
1142 }
1143 }
1144 row_heights.push(render_row_area.height);
1145
1146 let visible_row_area = Rect::new(
1148 state.table_area.x,
1149 row_y,
1150 state.table_area.width,
1151 render_row_area.height,
1152 )
1153 .intersection(state.table_area);
1154 state.row_areas.push(visible_row_area);
1155 if render_row_area.height == visible_row_area.height {
1157 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1158 }
1159
1160 if render_row_area.height > 0 {
1162 let mut col = 0;
1163 loop {
1164 if col >= state.columns {
1165 break;
1166 }
1167
1168 let render_cell_area = Rect::new(
1169 l_columns[col].x,
1170 0,
1171 l_columns[col].width,
1172 render_row_area.height,
1173 );
1174 ctx.space_area = Rect::new(
1175 l_spacers[col + 1].x,
1176 0,
1177 l_spacers[col + 1].width,
1178 render_row_area.height,
1179 );
1180
1181 if state.selection.is_selected_cell(col, row.expect("row")) {
1182 ctx.selected_cell = true;
1183 ctx.selected_row = false;
1184 ctx.selected_column = false;
1185 ctx.select_style = self.patch_select(
1186 self.select_cell_style,
1187 state.focus.get(),
1188 self.show_cell_focus,
1189 );
1190 } else if state.selection.is_selected_row(row.expect("row")) {
1191 ctx.selected_cell = false;
1192 ctx.selected_row = true;
1193 ctx.selected_column = false;
1194 ctx.select_style = if self.select_row_style.is_some() {
1196 self.patch_select(
1197 self.select_row_style,
1198 state.focus.get(),
1199 self.show_row_focus,
1200 )
1201 } else {
1202 self.patch_select(
1203 Some(self.style),
1204 state.focus.get(),
1205 self.show_row_focus,
1206 )
1207 };
1208 } else if state.selection.is_selected_column(col) {
1209 ctx.selected_cell = false;
1210 ctx.selected_row = false;
1211 ctx.selected_column = true;
1212 ctx.select_style = self.patch_select(
1213 self.select_column_style,
1214 state.focus.get(),
1215 self.show_column_focus,
1216 );
1217 } else {
1218 ctx.selected_cell = false;
1219 ctx.selected_row = false;
1220 ctx.selected_column = false;
1221 ctx.select_style = None;
1222 }
1223
1224 if render_cell_area.right() > state.hscroll.offset as u16
1226 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1227 {
1228 if self.auto_styles {
1229 if let Some(select_style) = ctx.select_style {
1230 row_buf.set_style(render_cell_area, select_style);
1231 row_buf.set_style(ctx.space_area, select_style);
1232 }
1233 }
1234 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1235 }
1236
1237 col += 1;
1238 }
1239
1240 transfer_buffer(
1242 &mut row_buf,
1243 state.hscroll.offset() as u16,
1244 visible_row_area,
1245 buf,
1246 );
1247 }
1248
1249 if visible_row_area.bottom() >= state.table_area.bottom() {
1250 break;
1251 }
1252 if !data.nth(0) {
1253 break;
1254 }
1255 row = Some(row.expect("row").saturating_add(1));
1256 row_y += render_row_area.height;
1257 }
1258 } else {
1259 if data.rows().is_none() || data.rows() == Some(0) {
1264 } else {
1266 #[cfg(feature = "perf_warnings")]
1267 {
1268 insane_offset = true;
1269 }
1270 }
1271 }
1272
1273 #[allow(unused_variables)]
1275 let algorithm;
1276 #[allow(unused_assignments)]
1277 {
1278 if let Some(rows) = data.rows() {
1279 algorithm = 0;
1280 let skip_rows = rows
1284 .saturating_sub(row.map_or(0, |v| v + 1))
1285 .saturating_sub(state.table_area.height as usize);
1286 if skip_rows > 0 {
1288 row_heights.clear();
1289 }
1290 let nth_row = skip_rows;
1291 if data.nth(nth_row) {
1293 let mut sum_height = row_heights.iter().sum::<u16>();
1294 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1295 loop {
1296 let row_height = data.row_height();
1297 row_heights.push(row_height);
1298
1299 sum_height += row_height;
1302 if sum_height
1303 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1304 > state.table_area.height
1305 {
1306 let lost_height = row_heights.remove(0);
1307 sum_height -= lost_height;
1308 }
1309
1310 if !data.nth(0) {
1311 break;
1312 }
1313
1314 row = Some(row.expect("row") + 1);
1315 if row.expect("row") > rows {
1317 break;
1318 }
1319 }
1320 while data.nth(0) {
1323 row = Some(row.expect("row") + 1);
1324 }
1325 } else {
1326 }
1329
1330 state.rows = rows;
1331 state._counted_rows = row.map_or(0, |v| v + 1);
1332
1333 if let Some(last_page) = state.calc_last_page(row_heights) {
1335 state.vscroll.set_max_offset(state.rows - last_page);
1336 } else {
1337 state.vscroll.set_max_offset(
1341 state.rows.saturating_sub(state.table_area.height as usize),
1342 );
1343 }
1344 } else if self.no_row_count {
1345 algorithm = 1;
1346
1347 if row.is_some() {
1351 if data.nth(0) {
1352 row = Some(row.expect("row").saturating_add(1));
1354 if data.nth(0) {
1355 row = Some(usize::MAX - 1);
1357 }
1358 }
1359 }
1360
1361 state.rows = row.map_or(0, |v| v + 1);
1362 state._counted_rows = row.map_or(0, |v| v + 1);
1363 state.vscroll.set_max_offset(usize::MAX - 1);
1365 if state.vscroll.page_len() == 0 {
1366 state.vscroll.set_page_len(state.table_area.height as usize);
1367 }
1368 } else {
1369 algorithm = 2;
1370
1371 let mut sum_height = row_heights.iter().sum::<u16>();
1373 while data.nth(0) {
1374 let row_height = data.row_height();
1375 row_heights.push(row_height);
1376
1377 sum_height += row_height;
1380 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1381 > state.table_area.height
1382 {
1383 let lost_height = row_heights.remove(0);
1384 sum_height -= lost_height;
1385 }
1386 row = Some(row.map_or(0, |v| v + 1));
1387 }
1388
1389 state.rows = row.map_or(0, |v| v + 1);
1390 state._counted_rows = row.map_or(0, |v| v + 1);
1391
1392 if let Some(last_page) = state.calc_last_page(row_heights) {
1394 state.vscroll.set_max_offset(state.rows - last_page);
1395 } else {
1396 state.vscroll.set_max_offset(0);
1397 }
1398 }
1399 }
1400 {
1401 state
1402 .hscroll
1403 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1404 }
1405
1406 if state.rows == 0 && self.show_empty {
1407 let area = Rect::new(state.inner.x, state.inner.y, 3, 1);
1408 let style = if state.is_focused() {
1409 self.focus_style.unwrap_or_default()
1410 } else {
1411 self.style
1412 };
1413 Span::from(self.empty_str.as_ref())
1414 .style(style)
1415 .render(area, buf);
1416 }
1417
1418 ScrollArea::new()
1420 .style(self.style)
1421 .block(self.block.as_ref())
1422 .h_scroll(self.hscroll.as_ref())
1423 .v_scroll(self.vscroll.as_ref())
1424 .render_scrollbars(
1425 area,
1426 buf,
1427 &mut ScrollAreaState::new()
1428 .h_scroll(&mut state.hscroll)
1429 .v_scroll(&mut state.vscroll),
1430 );
1431
1432 #[cfg(feature = "perf_warnings")]
1433 {
1434 use std::fmt::Write;
1435 let mut msg = String::new();
1436 if insane_offset {
1437 _ = write!(
1438 msg,
1439 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1440 state.vscroll.offset(),
1441 state.rows,
1442 state._counted_rows,
1443 algorithm
1444 );
1445 }
1446 if state.rows != state._counted_rows {
1447 _ = write!(
1448 msg,
1449 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1450 state.rows, state._counted_rows, algorithm
1451 );
1452 }
1453 if !msg.is_empty() {
1454 use log::warn;
1455 use ratatui::style::Stylize;
1456 use ratatui::text::Text;
1457
1458 warn!("{}", &msg);
1459 Text::from(msg)
1460 .white()
1461 .on_red()
1462 .render(state.table_area, buf);
1463 }
1464 }
1465 }
1466
1467 #[allow(clippy::too_many_arguments)]
1468 fn render_footer(
1469 &self,
1470 columns: usize,
1471 width: u16,
1472 l_columns: &[Rect],
1473 l_spacers: &[Rect],
1474 area: Rect,
1475 buf: &mut Buffer,
1476 state: &mut TableState<Selection>,
1477 ) {
1478 if let Some(footer) = &self.footer {
1479 let render_row_area = Rect::new(0, 0, width, footer.height);
1480 let mut row_buf = Buffer::empty(render_row_area);
1481
1482 row_buf.set_style(render_row_area, self.style);
1483 if let Some(footer_style) = footer.style {
1484 row_buf.set_style(render_row_area, footer_style);
1485 } else if let Some(footer_style) = self.footer_style {
1486 row_buf.set_style(render_row_area, footer_style);
1487 }
1488
1489 let mut col = 0;
1490 loop {
1491 if col >= columns {
1492 break;
1493 }
1494
1495 let render_cell_area =
1496 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1497 let render_space_area = Rect::new(
1498 l_spacers[col + 1].x,
1499 0,
1500 l_spacers[col + 1].width,
1501 area.height,
1502 );
1503
1504 if state.selection.is_selected_column(col) {
1505 if let Some(selected_style) = self.patch_select(
1506 self.select_footer_style,
1507 state.focus.get(),
1508 self.show_footer_focus,
1509 ) {
1510 row_buf.set_style(render_cell_area, selected_style);
1511 row_buf.set_style(render_space_area, selected_style);
1512 }
1513 };
1514
1515 if render_cell_area.right() > state.hscroll.offset as u16
1517 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1518 {
1519 if let Some(cell) = footer.cells.get(col) {
1520 if let Some(cell_style) = cell.style {
1521 row_buf.set_style(render_cell_area, cell_style);
1522 }
1523 cell.content.clone().render(render_cell_area, &mut row_buf);
1524 }
1525 }
1526
1527 col += 1;
1528 }
1529
1530 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1532 }
1533 }
1534
1535 #[allow(clippy::too_many_arguments)]
1536 fn render_header(
1537 &self,
1538 columns: usize,
1539 width: u16,
1540 l_columns: &[Rect],
1541 l_spacers: &[Rect],
1542 area: Rect,
1543 buf: &mut Buffer,
1544 state: &mut TableState<Selection>,
1545 ) {
1546 if let Some(header) = &self.header {
1547 let render_row_area = Rect::new(0, 0, width, header.height);
1548 let mut row_buf = Buffer::empty(render_row_area);
1549
1550 row_buf.set_style(render_row_area, self.style);
1551 if let Some(header_style) = header.style {
1552 row_buf.set_style(render_row_area, header_style);
1553 } else if let Some(header_style) = self.header_style {
1554 row_buf.set_style(render_row_area, header_style);
1555 }
1556
1557 let mut col = 0;
1558 loop {
1559 if col >= columns {
1560 break;
1561 }
1562
1563 let render_cell_area =
1564 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1565 let render_space_area = Rect::new(
1566 l_spacers[col + 1].x,
1567 0,
1568 l_spacers[col + 1].width,
1569 area.height,
1570 );
1571
1572 if state.selection.is_selected_column(col) {
1573 if let Some(selected_style) = self.patch_select(
1574 self.select_header_style,
1575 state.focus.get(),
1576 self.show_header_focus,
1577 ) {
1578 row_buf.set_style(render_cell_area, selected_style);
1579 row_buf.set_style(render_space_area, selected_style);
1580 }
1581 };
1582
1583 if render_cell_area.right() > state.hscroll.offset as u16
1585 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1586 {
1587 if let Some(cell) = header.cells.get(col) {
1588 if let Some(cell_style) = cell.style {
1589 row_buf.set_style(render_cell_area, cell_style);
1590 }
1591 cell.content.clone().render(render_cell_area, &mut row_buf);
1592 }
1593 }
1594
1595 col += 1;
1596 }
1597
1598 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1600 }
1601 }
1602
1603 fn calculate_column_areas(
1604 &self,
1605 columns: usize,
1606 l_columns: &[Rect],
1607 l_spacers: &[Rect],
1608 state: &mut TableState<Selection>,
1609 ) {
1610 state.column_areas.clear();
1611 state.column_layout.clear();
1612
1613 let mut col = 0;
1614 let shift = state.hscroll.offset() as isize;
1615 loop {
1616 if col >= columns {
1617 break;
1618 }
1619
1620 state.column_layout.push(Rect::new(
1621 l_columns[col].x,
1622 0,
1623 l_columns[col].width + l_spacers[col + 1].width,
1624 0,
1625 ));
1626
1627 let cell_x1 = l_columns[col].x as isize;
1628 let cell_x2 =
1629 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1630
1631 let squish_x1 = cell_x1.saturating_sub(shift);
1632 let squish_x2 = cell_x2.saturating_sub(shift);
1633
1634 let abs_x1 = max(0, squish_x1) as u16;
1635 let abs_x2 = max(0, squish_x2) as u16;
1636
1637 let v_area = Rect::new(
1638 state.table_area.x + abs_x1,
1639 state.table_area.y,
1640 abs_x2 - abs_x1,
1641 state.table_area.height,
1642 );
1643 state
1644 .column_areas
1645 .push(v_area.intersection(state.table_area));
1646
1647 col += 1;
1648 }
1649 }
1650
1651 #[expect(clippy::collapsible_else_if)]
1652 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1653 if let Some(style) = style {
1654 if let Some(focus_style) = self.focus_style {
1655 if focus && show {
1656 Some(style.patch(focus_style))
1657 } else {
1658 Some(fallback_select_style(style))
1659 }
1660 } else {
1661 if focus && show {
1662 Some(revert_style(style))
1663 } else {
1664 Some(fallback_select_style(style))
1665 }
1666 }
1667 } else {
1668 None
1669 }
1670 }
1671}
1672
1673impl Default for TableStyle {
1674 fn default() -> Self {
1675 Self {
1676 style: Default::default(),
1677 header: Default::default(),
1678 footer: Default::default(),
1679 select_row: Default::default(),
1680 select_column: Default::default(),
1681 select_cell: Default::default(),
1682 select_header: Default::default(),
1683 select_footer: Default::default(),
1684 show_row_focus: true,
1685 show_column_focus: Default::default(),
1686 show_cell_focus: Default::default(),
1687 show_header_focus: Default::default(),
1688 show_footer_focus: Default::default(),
1689 show_empty: Default::default(),
1690 empty_str: Default::default(),
1691 focus_style: Default::default(),
1692 block: Default::default(),
1693 border_style: Default::default(),
1694 title_style: Default::default(),
1695 scroll: Default::default(),
1696 non_exhaustive: NonExhaustive,
1697 }
1698 }
1699}
1700
1701impl<Selection: Clone> Clone for TableState<Selection> {
1702 fn clone(&self) -> Self {
1703 Self {
1704 focus: self.focus.new_instance(),
1705 area: self.area,
1706 inner: self.inner,
1707 header_area: self.header_area,
1708 table_area: self.table_area,
1709 row_areas: self.row_areas.clone(),
1710 column_areas: self.column_areas.clone(),
1711 column_layout: self.column_layout.clone(),
1712 footer_area: self.footer_area,
1713 rows: self.rows,
1714 _counted_rows: self._counted_rows,
1715 columns: self.columns,
1716 vscroll: self.vscroll.clone(),
1717 hscroll: self.hscroll.clone(),
1718 selection: self.selection.clone(),
1719 mouse: Default::default(),
1720 non_exhaustive: NonExhaustive,
1721 }
1722 }
1723}
1724
1725impl<Selection: Default> Default for TableState<Selection> {
1726 fn default() -> Self {
1727 Self {
1728 focus: Default::default(),
1729 area: Default::default(),
1730 inner: Default::default(),
1731 header_area: Default::default(),
1732 table_area: Default::default(),
1733 row_areas: Default::default(),
1734 column_areas: Default::default(),
1735 column_layout: Default::default(),
1736 footer_area: Default::default(),
1737 rows: Default::default(),
1738 _counted_rows: Default::default(),
1739 columns: Default::default(),
1740 vscroll: Default::default(),
1741 hscroll: Default::default(),
1742 selection: Default::default(),
1743 mouse: Default::default(),
1744 non_exhaustive: NonExhaustive,
1745 }
1746 }
1747}
1748
1749impl<Selection> HasFocus for TableState<Selection> {
1750 fn build(&self, builder: &mut FocusBuilder) {
1751 builder.leaf_widget(self);
1752 }
1753
1754 #[inline]
1755 fn focus(&self) -> FocusFlag {
1756 self.focus.clone()
1757 }
1758
1759 #[inline]
1760 fn area(&self) -> Rect {
1761 self.area
1762 }
1763}
1764
1765impl<Selection> HasScreenCursor for TableState<Selection> {
1766 fn screen_cursor(&self) -> Option<(u16, u16)> {
1767 None
1768 }
1769}
1770
1771impl<Selection> RelocatableState for TableState<Selection> {
1772 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1773 self.area.relocate(shift, clip);
1774 self.inner.relocate(shift, clip);
1775 self.header_area.relocate(shift, clip);
1776 self.table_area.relocate(shift, clip);
1777 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1778 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1779 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1780 self.footer_area.relocate(shift, clip);
1781 self.hscroll.relocate(shift, clip);
1782 self.vscroll.relocate(shift, clip);
1783 }
1784}
1785
1786impl<Selection> TableState<Selection> {
1787 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1788 let mut sum_heights = 0;
1789 let mut n_rows = 0;
1790 while let Some(h) = row_heights.pop() {
1791 sum_heights += h;
1792 n_rows += 1;
1793 if sum_heights >= self.table_area.height {
1794 break;
1795 }
1796 }
1797
1798 if sum_heights < self.table_area.height {
1799 None
1800 } else {
1801 Some(n_rows)
1802 }
1803 }
1804}
1805
1806impl<Selection> TableState<Selection>
1808where
1809 Selection: Default,
1810{
1811 pub fn new() -> Self {
1812 Self::default()
1813 }
1814
1815 pub fn named(name: &str) -> Self {
1816 Self {
1817 focus: FocusFlag::new().with_name(name),
1818 ..TableState::default()
1819 }
1820 }
1821}
1822
1823impl<Selection> TableState<Selection> {
1825 #[inline]
1827 pub fn rows(&self) -> usize {
1828 self.rows
1829 }
1830
1831 pub fn rows_changed(&mut self, rows: usize) {
1843 self.rows = rows;
1844 self.vscroll
1845 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1846 }
1847
1848 #[inline]
1850 pub fn columns(&self) -> usize {
1851 self.columns
1852 }
1853}
1854
1855impl<Selection> TableState<Selection> {
1857 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1863 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1864 return None;
1865 }
1866
1867 let mut areas = Vec::new();
1868
1869 let r = self.row_areas[row - self.vscroll.offset()];
1870 for c in &self.column_areas {
1871 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1872 }
1873
1874 Some((r, areas))
1875 }
1876
1877 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1879 let col = self.column_at_clicked(pos);
1880 let row = self.row_at_clicked(pos);
1881
1882 match (col, row) {
1883 (Some(col), Some(row)) => Some((col, row)),
1884 _ => None,
1885 }
1886 }
1887
1888 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1890 self.mouse.column_at(&self.column_areas, pos.0)
1891 }
1892
1893 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1895 self.mouse
1896 .row_at(&self.row_areas, pos.1)
1897 .map(|v| self.vscroll.offset() + v)
1898 }
1899
1900 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1903 let col = self.column_at_drag(pos);
1904 let row = self.row_at_drag(pos);
1905
1906 (col, row)
1907 }
1908
1909 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1916 match self
1917 .mouse
1918 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1919 {
1920 Ok(v) => self.vscroll.offset() + v,
1921 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1922 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1923 }
1924 }
1925
1926 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1930 match self
1931 .mouse
1932 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1933 {
1934 Ok(v) => v,
1935 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1936 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1937 }
1938 }
1939}
1940
1941impl<Selection: TableSelection> TableState<Selection> {
1943 pub fn clear_offset(&mut self) {
1945 self.vscroll.set_offset(0);
1946 self.hscroll.set_offset(0);
1947 }
1948
1949 pub fn row_max_offset(&self) -> usize {
1954 self.vscroll.max_offset()
1955 }
1956
1957 pub fn row_offset(&self) -> usize {
1959 self.vscroll.offset()
1960 }
1961
1962 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1969 self.vscroll.set_offset(offset)
1970 }
1971
1972 pub fn page_len(&self) -> usize {
1974 self.vscroll.page_len()
1975 }
1976
1977 pub fn row_scroll_by(&self) -> usize {
1979 self.vscroll.scroll_by()
1980 }
1981
1982 pub fn x_max_offset(&self) -> usize {
1987 self.hscroll.max_offset()
1988 }
1989
1990 pub fn x_offset(&self) -> usize {
1992 self.hscroll.offset()
1993 }
1994
1995 pub fn set_x_offset(&mut self, offset: usize) -> bool {
2002 self.hscroll.set_offset(offset)
2003 }
2004
2005 pub fn page_width(&self) -> usize {
2007 self.hscroll.page_len()
2008 }
2009
2010 pub fn x_scroll_by(&self) -> usize {
2012 self.hscroll.scroll_by()
2013 }
2014
2015 pub fn scroll_to_selected(&mut self) -> bool {
2019 if let Some(selected) = self.selection.lead_selection() {
2020 let c = self.scroll_to_col(selected.0);
2021 let r = self.scroll_to_row(selected.1);
2022 r || c
2023 } else {
2024 false
2025 }
2026 }
2027
2028 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
2033 if pos >= self.rows {
2034 false
2035 } else if pos == self.row_offset().saturating_add(self.page_len()) {
2036 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
2038 if heights < self.table_area.height {
2039 false
2040 } else {
2041 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2042 }
2043 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2044 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2045 } else if pos < self.row_offset() {
2046 self.set_row_offset(pos)
2047 } else {
2048 false
2049 }
2050 }
2051
2052 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2054 if let Some(col) = self.column_layout.get(pos) {
2055 if (col.left() as usize) < self.x_offset() {
2056 self.set_x_offset(col.x as usize)
2057 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2058 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2059 } else {
2060 false
2061 }
2062 } else {
2063 false
2064 }
2065 }
2066
2067 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2069 if pos >= self.x_offset().saturating_add(self.page_width()) {
2070 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2071 } else if pos < self.x_offset() {
2072 self.set_x_offset(pos)
2073 } else {
2074 false
2075 }
2076 }
2077
2078 pub fn scroll_up(&mut self, n: usize) -> bool {
2080 self.vscroll.scroll_up(n)
2081 }
2082
2083 pub fn scroll_down(&mut self, n: usize) -> bool {
2085 self.vscroll.scroll_down(n)
2086 }
2087
2088 pub fn scroll_left(&mut self, n: usize) -> bool {
2090 self.hscroll.scroll_left(n)
2091 }
2092
2093 pub fn scroll_right(&mut self, n: usize) -> bool {
2095 self.hscroll.scroll_right(n)
2096 }
2097}
2098
2099impl TableState<RowSelection> {
2100 pub fn items_added(&mut self, pos: usize, n: usize) {
2104 self.vscroll.items_added(pos, n);
2105 self.selection.items_added(pos, n);
2106 self.rows += n;
2107 }
2108
2109 pub fn items_removed(&mut self, pos: usize, n: usize) {
2113 self.vscroll.items_removed(pos, n);
2114 self.selection
2115 .items_removed(pos, n, self.rows.saturating_sub(1));
2116 self.rows -= n;
2117 }
2118
2119 #[inline]
2122 pub fn set_scroll_selection(&mut self, scroll: bool) {
2123 self.selection.set_scroll_selected(scroll);
2124 }
2125
2126 #[inline]
2128 pub fn clear_selection(&mut self) {
2129 self.selection.clear();
2130 }
2131
2132 #[inline]
2134 pub fn has_selection(&mut self) -> bool {
2135 self.selection.has_selection()
2136 }
2137
2138 #[inline]
2141 pub fn selected(&self) -> Option<usize> {
2142 self.selection.selected()
2143 }
2144
2145 #[inline]
2148 #[allow(clippy::manual_filter)]
2149 pub fn selected_checked(&self) -> Option<usize> {
2150 if let Some(selected) = self.selection.selected() {
2151 if selected < self.rows {
2152 Some(selected)
2153 } else {
2154 None
2155 }
2156 } else {
2157 None
2158 }
2159 }
2160
2161 #[inline]
2164 pub fn select(&mut self, row: Option<usize>) -> bool {
2165 self.selection.select(row)
2166 }
2167
2168 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2172 if self.vscroll.max_offset() > 0 {
2173 (self.rows * offset) / self.vscroll.max_offset()
2174 } else {
2175 0 }
2177 }
2178
2179 #[inline]
2181 pub fn move_deselect(&mut self) -> bool {
2182 let r = self.selection.select(None);
2183 let s = self.set_row_offset(0);
2184 r || s
2185 }
2186
2187 #[inline]
2190 pub fn move_to(&mut self, row: usize) -> bool {
2191 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2192 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2193 r || s
2194 }
2195
2196 #[inline]
2199 pub fn move_up(&mut self, n: usize) -> bool {
2200 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2201 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2202 r || s
2203 }
2204
2205 #[inline]
2208 pub fn move_down(&mut self, n: usize) -> bool {
2209 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2210 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2211 r || s
2212 }
2213}
2214
2215impl TableState<RowSetSelection> {
2216 #[inline]
2218 pub fn clear_selection(&mut self) {
2219 self.selection.clear();
2220 }
2221
2222 #[inline]
2224 pub fn has_selection(&mut self) -> bool {
2225 self.selection.has_selection()
2226 }
2227
2228 #[inline]
2230 pub fn selected(&self) -> HashSet<usize> {
2231 self.selection.selected()
2232 }
2233
2234 #[inline]
2241 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2242 self.selection.set_lead(row, extend)
2243 }
2244
2245 #[inline]
2247 pub fn lead(&self) -> Option<usize> {
2248 self.selection.lead()
2249 }
2250
2251 #[inline]
2253 pub fn anchor(&self) -> Option<usize> {
2254 self.selection.anchor()
2255 }
2256
2257 #[inline]
2260 pub fn retire_selection(&mut self) {
2261 self.selection.retire_selection();
2262 }
2263
2264 #[inline]
2269 pub fn add_selected(&mut self, idx: usize) {
2270 self.selection.add(idx);
2271 }
2272
2273 #[inline]
2278 pub fn remove_selected(&mut self, idx: usize) {
2279 self.selection.remove(idx);
2280 }
2281
2282 #[inline]
2285 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2286 let r = self
2287 .selection
2288 .move_to(row, self.rows.saturating_sub(1), extend);
2289 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2290 r || s
2291 }
2292
2293 #[inline]
2296 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2297 let r = self
2298 .selection
2299 .move_up(n, self.rows.saturating_sub(1), extend);
2300 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2301 r || s
2302 }
2303
2304 #[inline]
2307 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2308 let r = self
2309 .selection
2310 .move_down(n, self.rows.saturating_sub(1), extend);
2311 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2312 r || s
2313 }
2314}
2315
2316impl TableState<CellSelection> {
2317 #[inline]
2318 pub fn clear_selection(&mut self) {
2319 self.selection.clear();
2320 }
2321
2322 #[inline]
2323 pub fn has_selection(&mut self) -> bool {
2324 self.selection.has_selection()
2325 }
2326
2327 #[inline]
2329 pub fn selected(&self) -> Option<(usize, usize)> {
2330 self.selection.selected()
2331 }
2332
2333 #[inline]
2335 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2336 self.selection.select_cell(select)
2337 }
2338
2339 #[inline]
2341 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2342 if let Some(row) = row {
2343 self.selection
2344 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2345 } else {
2346 self.selection.select_row(None)
2347 }
2348 }
2349
2350 #[inline]
2352 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2353 if let Some(column) = column {
2354 self.selection
2355 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2356 } else {
2357 self.selection.select_column(None)
2358 }
2359 }
2360
2361 #[inline]
2363 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2364 let r = self.selection.move_to(
2365 select,
2366 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2367 );
2368 let s = self.scroll_to_selected();
2369 r || s
2370 }
2371
2372 #[inline]
2374 pub fn move_to_row(&mut self, row: usize) -> bool {
2375 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2376 let s = self.scroll_to_selected();
2377 r || s
2378 }
2379
2380 #[inline]
2382 pub fn move_to_col(&mut self, col: usize) -> bool {
2383 let r = self
2384 .selection
2385 .move_to_col(col, self.columns.saturating_sub(1));
2386 let s = self.scroll_to_selected();
2387 r || s
2388 }
2389
2390 #[inline]
2393 pub fn move_up(&mut self, n: usize) -> bool {
2394 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2395 let s = self.scroll_to_selected();
2396 r || s
2397 }
2398
2399 #[inline]
2402 pub fn move_down(&mut self, n: usize) -> bool {
2403 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2404 let s = self.scroll_to_selected();
2405 r || s
2406 }
2407
2408 #[inline]
2411 pub fn move_left(&mut self, n: usize) -> bool {
2412 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2413 let s = self.scroll_to_selected();
2414 r || s
2415 }
2416
2417 #[inline]
2420 pub fn move_right(&mut self, n: usize) -> bool {
2421 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2422 let s = self.scroll_to_selected();
2423 r || s
2424 }
2425}
2426
2427impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2428 for TableState<Selection>
2429{
2430 fn handle(
2432 &mut self,
2433 event: &crossterm::event::Event,
2434 _keymap: DoubleClick,
2435 ) -> DoubleClickOutcome {
2436 match event {
2437 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2438 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2439 DoubleClickOutcome::ClickClick(col, row)
2440 } else {
2441 DoubleClickOutcome::Continue
2442 }
2443 }
2444 _ => DoubleClickOutcome::Continue,
2445 }
2446 }
2447}
2448
2449pub fn handle_doubleclick_events<Selection: TableSelection>(
2451 state: &mut TableState<Selection>,
2452 event: &crossterm::event::Event,
2453) -> DoubleClickOutcome {
2454 state.handle(event, DoubleClick)
2455}