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::widgets::{Block, StatefulWidget, Widget};
20use std::cmp::{max, min};
21use std::collections::HashSet;
22use std::fmt::Debug;
23use std::marker::PhantomData;
24use std::mem;
25use std::rc::Rc;
26
27#[derive(Debug)]
42pub struct Table<'a, Selection = RowSelection> {
43 data: DataRepr<'a>,
44 no_row_count: bool,
45
46 header: Option<Row<'a>>,
47 footer: Option<Row<'a>>,
48
49 widths: Vec<Constraint>,
50 flex: Flex,
51 column_spacing: u16,
52 layout_width: Option<u16>,
53 layout_column_widths: bool,
54
55 style: Style,
56 block: Option<Block<'a>>,
57 hscroll: Option<Scroll<'a>>,
58 vscroll: Option<Scroll<'a>>,
59 header_style: Option<Style>,
60 footer_style: Option<Style>,
61 focus_style: Option<Style>,
62
63 auto_styles: bool,
64 select_row_style: Option<Style>,
65 show_row_focus: bool,
66 select_column_style: Option<Style>,
67 show_column_focus: bool,
68 select_cell_style: Option<Style>,
69 show_cell_focus: bool,
70 select_header_style: Option<Style>,
71 show_header_focus: bool,
72 select_footer_style: Option<Style>,
73 show_footer_focus: bool,
74
75 _phantom: PhantomData<Selection>,
76}
77
78mod data {
79 use crate::textdata::TextTableData;
80 use crate::{TableContext, TableData, TableDataIter};
81 #[cfg(feature = "perf_warnings")]
82 use log::warn;
83 use ratatui::buffer::Buffer;
84 use ratatui::layout::Rect;
85 use ratatui::style::{Style, Stylize};
86 use std::fmt::{Debug, Formatter};
87
88 #[derive(Default)]
89 pub(super) enum DataRepr<'a> {
90 #[default]
91 None,
92 Text(TextTableData<'a>),
93 Data(Box<dyn TableData<'a> + 'a>),
94 Iter(Box<dyn TableDataIter<'a> + 'a>),
95 }
96
97 impl<'a> DataRepr<'a> {
98 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
99 match self {
100 DataRepr::None => DataReprIter::None,
101 DataRepr::Text(v) => DataReprIter::IterText(v, None),
102 DataRepr::Data(v) => DataReprIter::IterData(v, None),
103 DataRepr::Iter(v) => DataReprIter::IterIter(v),
104 }
105 }
106
107 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
108 match self {
109 DataRepr::None => DataReprIter::None,
110 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
111 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
112 DataRepr::Iter(v) => {
113 if let Some(v) = v.cloned() {
115 DataReprIter::IterIter(v)
116 } else {
117 DataReprIter::Invalid(None)
118 }
119 }
120 }
121 }
122 }
123
124 impl Debug for DataRepr<'_> {
125 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("Data").finish()
127 }
128 }
129
130 #[derive(Default)]
131 pub(super) enum DataReprIter<'a, 'b> {
132 #[default]
133 None,
134 #[allow(dead_code)]
135 Invalid(Option<usize>),
136 IterText(TextTableData<'a>, Option<usize>),
137 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
138 #[allow(dead_code)]
139 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
140 IterIter(Box<dyn TableDataIter<'a> + 'a>),
141 }
142
143 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
144 fn rows(&self) -> Option<usize> {
145 match self {
146 DataReprIter::None => Some(0),
147 DataReprIter::Invalid(_) => Some(1),
148 DataReprIter::IterText(v, _) => Some(v.rows.len()),
149 DataReprIter::IterData(v, _) => Some(v.rows()),
150 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
151 DataReprIter::IterIter(v) => v.rows(),
152 }
153 }
154
155 fn nth(&mut self, n: usize) -> bool {
156 let incr = |row: &mut Option<usize>, rows: usize| match *row {
157 None => {
158 *row = Some(n);
159 *row < Some(rows)
160 }
161 Some(w) => {
162 *row = Some(w.saturating_add(n).saturating_add(1));
163 *row < Some(rows)
164 }
165 };
166
167 match self {
168 DataReprIter::None => false,
169 DataReprIter::Invalid(row) => incr(row, 1),
170 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
171 DataReprIter::IterData(v, row) => incr(row, v.rows()),
172 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
173 DataReprIter::IterIter(v) => v.nth(n),
174 }
175 }
176
177 fn row_height(&self) -> u16 {
179 match self {
180 DataReprIter::None => 1,
181 DataReprIter::Invalid(_) => 1,
182 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
183 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
184 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
185 DataReprIter::IterIter(v) => v.row_height(),
186 }
187 }
188
189 fn row_style(&self) -> Option<Style> {
190 match self {
191 DataReprIter::None => None,
192 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
193 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
194 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
195 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
196 DataReprIter::IterIter(v) => v.row_style(),
197 }
198 }
199
200 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
202 match self {
203 DataReprIter::None => {}
204 DataReprIter::Invalid(_) => {
205 if column == 0 {
206 #[cfg(feature = "perf_warnings")]
207 warn!(
208 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
209 );
210
211 buf.set_string(
212 area.x,
213 area.y,
214 "TableDataIter must implement a valid cloned() for this",
215 Style::default(),
216 );
217 }
218 }
219 DataReprIter::IterText(v, n) => {
220 v.render_cell(ctx, column, n.expect("row"), area, buf)
221 }
222 DataReprIter::IterData(v, n) => {
223 v.render_cell(ctx, column, n.expect("row"), area, buf)
224 }
225 DataReprIter::IterDataRef(v, n) => {
226 v.render_cell(ctx, column, n.expect("row"), area, buf)
227 }
228 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
229 }
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct TableStyle {
237 pub style: Style,
238 pub block: Option<Block<'static>>,
239 pub border_style: Option<Style>,
240 pub title_style: Option<Style>,
241 pub scroll: Option<ScrollStyle>,
242 pub header: Option<Style>,
243 pub footer: Option<Style>,
244 pub focus_style: Option<Style>,
245
246 pub select_row: Option<Style>,
247 pub select_column: Option<Style>,
248 pub select_cell: Option<Style>,
249 pub select_header: Option<Style>,
250 pub select_footer: Option<Style>,
251
252 pub show_row_focus: bool,
253 pub show_column_focus: bool,
254 pub show_cell_focus: bool,
255 pub show_header_focus: bool,
256 pub show_footer_focus: bool,
257
258 pub non_exhaustive: NonExhaustive,
259}
260
261#[derive(Debug)]
263pub struct TableState<Selection = RowSelection> {
264 pub focus: FocusFlag,
267
268 pub area: Rect,
271 pub inner: Rect,
274
275 pub header_area: Rect,
278 pub table_area: Rect,
281 pub row_areas: Vec<Rect>,
284 pub column_areas: Vec<Rect>,
288 pub column_layout: Vec<Rect>,
292 pub footer_area: Rect,
295
296 pub rows: usize,
299 pub _counted_rows: usize,
301 pub columns: usize,
304
305 pub vscroll: ScrollState,
308 pub hscroll: ScrollState,
311
312 pub selection: Selection,
315
316 pub mouse: MouseFlags,
318
319 pub non_exhaustive: NonExhaustive,
320}
321
322impl<Selection> Default for Table<'_, Selection> {
323 fn default() -> Self {
324 Self {
325 data: Default::default(),
326 no_row_count: Default::default(),
327 header: Default::default(),
328 footer: Default::default(),
329 widths: Default::default(),
330 flex: Default::default(),
331 column_spacing: Default::default(),
332 layout_width: Default::default(),
333 layout_column_widths: true,
334 block: Default::default(),
335 hscroll: Default::default(),
336 vscroll: Default::default(),
337 header_style: Default::default(),
338 footer_style: Default::default(),
339 style: Default::default(),
340 auto_styles: true,
341 select_row_style: Default::default(),
342 show_row_focus: true,
343 select_column_style: Default::default(),
344 show_column_focus: Default::default(),
345 select_cell_style: Default::default(),
346 show_cell_focus: Default::default(),
347 select_header_style: Default::default(),
348 show_header_focus: Default::default(),
349 select_footer_style: Default::default(),
350 show_footer_focus: Default::default(),
351 focus_style: Default::default(),
352 _phantom: Default::default(),
353 }
354 }
355}
356
357impl<'a, Selection> Table<'a, Selection> {
358 pub fn new() -> Self
360 where
361 Selection: Default,
362 {
363 Self::default()
364 }
365
366 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
371 where
372 R: IntoIterator,
373 R::Item: Into<Row<'a>>,
374 C: IntoIterator,
375 C::Item: Into<Constraint>,
376 Selection: Default,
377 {
378 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
379 let data = TextTableData {
380 rows: rows.into_iter().map(|v| v.into()).collect(),
381 };
382 Self {
383 data: DataRepr::Text(data),
384 widths,
385 ..Default::default()
386 }
387 }
388
389 pub fn rows<T>(mut self, rows: T) -> Self
393 where
394 T: IntoIterator<Item = Row<'a>>,
395 {
396 let rows = rows.into_iter().collect();
397 self.data = DataRepr::Text(TextTableData { rows });
398 self
399 }
400
401 #[inline]
468 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
469 self.widths = data.widths();
470 self.header = data.header();
471 self.footer = data.footer();
472 self.data = DataRepr::Data(Box::new(data));
473 self
474 }
475
476 #[inline]
585 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
586 #[cfg(feature = "perf_warnings")]
587 if data.rows().is_none() {
588 use log::warn;
589 warn!("Table::iter - rows is None, this will be slower");
590 }
591 self.header = data.header();
592 self.footer = data.footer();
593 self.widths = data.widths();
594 self.data = DataRepr::Iter(Box::new(data));
595 self
596 }
597
598 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
616 self.no_row_count = no_row_count;
617 self
618 }
619
620 #[inline]
622 pub fn header(mut self, header: Row<'a>) -> Self {
623 self.header = Some(header);
624 self
625 }
626
627 #[inline]
629 pub fn footer(mut self, footer: Row<'a>) -> Self {
630 self.footer = Some(footer);
631 self
632 }
633
634 pub fn widths<I>(mut self, widths: I) -> Self
636 where
637 I: IntoIterator,
638 I::Item: Into<Constraint>,
639 {
640 self.widths = widths.into_iter().map(|v| v.into()).collect();
641 self
642 }
643
644 #[inline]
646 pub fn flex(mut self, flex: Flex) -> Self {
647 self.flex = flex;
648 self
649 }
650
651 #[inline]
653 pub fn column_spacing(mut self, spacing: u16) -> Self {
654 self.column_spacing = spacing;
655 self
656 }
657
658 #[inline]
661 pub fn layout_width(mut self, width: u16) -> Self {
662 self.layout_width = Some(width);
663 self
664 }
665
666 #[inline]
675 pub fn layout_column_widths(mut self) -> Self {
676 self.layout_column_widths = true;
677 self
678 }
679
680 #[deprecated(since = "1.1.1", note = "no longer supported")]
687 #[inline]
688 pub fn auto_layout_width(self) -> Self {
689 self
690 }
691
692 #[inline]
694 pub fn block(mut self, block: Block<'a>) -> Self {
695 self.block = Some(block);
696 self.block = self.block.map(|v| v.style(self.style));
697 self
698 }
699
700 pub fn border_style(mut self, style: Style) -> Self {
702 self.block = self.block.map(|v| v.border_style(style));
703 self
704 }
705
706 pub fn title_style(mut self, style: Style) -> Self {
708 self.block = self.block.map(|v| v.title_style(style));
709 self
710 }
711
712 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
714 self.hscroll = Some(scroll.clone().override_horizontal());
715 self.vscroll = Some(scroll.override_vertical());
716 self
717 }
718
719 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
721 self.hscroll = Some(scroll.override_horizontal());
722 self
723 }
724
725 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
727 self.vscroll = Some(scroll.override_vertical());
728 self
729 }
730
731 #[inline]
733 pub fn styles(mut self, styles: TableStyle) -> Self {
734 self.style = styles.style;
735 if styles.block.is_some() {
736 self.block = styles.block;
737 }
738 if let Some(border_style) = styles.border_style {
739 self.block = self.block.map(|v| v.border_style(border_style));
740 }
741 if let Some(title_style) = styles.title_style {
742 self.block = self.block.map(|v| v.title_style(title_style));
743 }
744 self.block = self.block.map(|v| v.style(self.style));
745
746 if let Some(styles) = styles.scroll {
747 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
748 self.vscroll = self.vscroll.map(|v| v.styles(styles));
749 }
750 if styles.header.is_some() {
751 self.header_style = styles.header;
752 }
753 if styles.footer.is_some() {
754 self.footer_style = styles.footer;
755 }
756 if styles.select_row.is_some() {
757 self.select_row_style = styles.select_row;
758 }
759 self.show_row_focus = styles.show_row_focus;
760 if styles.select_column.is_some() {
761 self.select_column_style = styles.select_column;
762 }
763 self.show_column_focus = styles.show_column_focus;
764 if styles.select_cell.is_some() {
765 self.select_cell_style = styles.select_cell;
766 }
767 self.show_cell_focus = styles.show_cell_focus;
768 if styles.select_header.is_some() {
769 self.select_header_style = styles.select_header;
770 }
771 self.show_header_focus = styles.show_header_focus;
772 if styles.select_footer.is_some() {
773 self.select_footer_style = styles.select_footer;
774 }
775 self.show_footer_focus = styles.show_footer_focus;
776 if styles.focus_style.is_some() {
777 self.focus_style = styles.focus_style;
778 }
779 self
780 }
781
782 #[inline]
784 pub fn style(mut self, style: Style) -> Self {
785 self.style = style;
786 self.block = self.block.map(|v| v.style(self.style));
787 self
788 }
789
790 #[inline]
792 pub fn header_style(mut self, style: Option<Style>) -> Self {
793 self.header_style = style;
794 self
795 }
796
797 #[inline]
799 pub fn footer_style(mut self, style: Option<Style>) -> Self {
800 self.footer_style = style;
801 self
802 }
803
804 #[inline]
810 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
811 self.auto_styles = auto_styles;
812 self
813 }
814
815 #[inline]
818 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
819 self.select_row_style = select_style;
820 self
821 }
822
823 #[inline]
825 pub fn show_row_focus(mut self, show: bool) -> Self {
826 self.show_row_focus = show;
827 self
828 }
829
830 #[inline]
833 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
834 self.select_column_style = select_style;
835 self
836 }
837
838 #[inline]
840 pub fn show_column_focus(mut self, show: bool) -> Self {
841 self.show_column_focus = show;
842 self
843 }
844
845 #[inline]
848 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
849 self.select_cell_style = select_style;
850 self
851 }
852
853 #[inline]
855 pub fn show_cell_focus(mut self, show: bool) -> Self {
856 self.show_cell_focus = show;
857 self
858 }
859
860 #[inline]
863 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
864 self.select_header_style = select_style;
865 self
866 }
867
868 #[inline]
870 pub fn show_header_focus(mut self, show: bool) -> Self {
871 self.show_header_focus = show;
872 self
873 }
874
875 #[inline]
878 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
879 self.select_footer_style = select_style;
880 self
881 }
882
883 #[inline]
885 pub fn show_footer_focus(mut self, show: bool) -> Self {
886 self.show_footer_focus = show;
887 self
888 }
889
890 #[inline]
896 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
897 self.focus_style = focus_style;
898 self
899 }
900
901 pub fn width(&self) -> u16 {
903 let sa = ScrollArea::new()
904 .style(self.style)
905 .block(self.block.as_ref())
906 .h_scroll(self.hscroll.as_ref())
907 .v_scroll(self.vscroll.as_ref())
908 .ignore_scroll();
909 let padding = sa.padding();
910 let width = self.total_width(0);
911
912 width + padding.left + padding.right
913 }
914
915 pub fn heigth(&self) -> u16 {
917 let sa = ScrollArea::new()
918 .style(self.style)
919 .block(self.block.as_ref())
920 .h_scroll(self.hscroll.as_ref())
921 .v_scroll(self.vscroll.as_ref())
922 .ignore_scroll();
923 let padding = sa.padding();
924 let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
925 let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
926
927 1 + header + footer + padding.top + padding.bottom
928 }
929
930 #[deprecated(since = "1.1.1", note = "not in use")]
931 pub fn debug(self, _: bool) -> Self {
932 self
933 }
934}
935
936impl<Selection> Table<'_, Selection> {
937 #[inline]
939 fn total_width(&self, area_width: u16) -> u16 {
940 if let Some(layout_width) = self.layout_width {
941 layout_width
942 } else if self.layout_column_widths {
943 let mut width = 0;
944 for w in self.widths.iter().copied() {
945 match w {
946 Constraint::Min(v) => width += v + self.column_spacing,
947 Constraint::Max(v) => width += v + self.column_spacing,
948 Constraint::Length(v) => width += v + self.column_spacing,
949 Constraint::Percentage(p) => {
950 width += (((area_width as u32) * (p as u32)) / 100) as u16;
951 }
952 Constraint::Ratio(n, d) => {
953 width += (((area_width as u32) * n) / d) as u16;
954 }
955 Constraint::Fill(_) => {
956 width += 10;
958 }
959 }
960 }
961 max(width, area_width)
962 } else {
963 area_width
964 }
965 }
966
967 #[inline]
969 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
970 let width = self.total_width(width);
971 let area = Rect::new(0, 0, width, 0);
972
973 let (layout, spacers) = Layout::horizontal(&self.widths)
974 .flex(self.flex)
975 .spacing(self.column_spacing)
976 .split_with_spacers(area);
977
978 (width, layout, spacers)
979 }
980
981 #[inline]
983 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
984 let heights = vec![
985 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
986 Constraint::Fill(1),
987 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
988 ];
989
990 Layout::vertical(heights).split(area)
991 }
992}
993
994impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
995where
996 Selection: TableSelection,
997{
998 type State = TableState<Selection>;
999
1000 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1001 let iter = self.data.iter();
1002 self.render_iter(iter, area, buf, state);
1003 }
1004}
1005
1006impl<Selection> StatefulWidget for Table<'_, Selection>
1007where
1008 Selection: TableSelection,
1009{
1010 type State = TableState<Selection>;
1011
1012 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1013 let iter = mem::take(&mut self.data).into_iter();
1014 self.render_iter(iter, area, buf, state);
1015 }
1016}
1017
1018impl<'a, Selection> Table<'a, Selection>
1019where
1020 Selection: TableSelection,
1021{
1022 fn render_iter<'b>(
1027 &self,
1028 mut data: DataReprIter<'a, 'b>,
1029 area: Rect,
1030 buf: &mut Buffer,
1031 state: &mut TableState<Selection>,
1032 ) {
1033 if let Some(rows) = data.rows() {
1034 state.rows = rows;
1035 }
1036 state.columns = self.widths.len();
1037 state.area = area;
1038
1039 let sa = ScrollArea::new()
1040 .style(self.style)
1041 .block(self.block.as_ref())
1042 .h_scroll(self.hscroll.as_ref())
1043 .v_scroll(self.vscroll.as_ref())
1044 .ignore_scroll();
1045 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1046
1047 let l_rows = self.layout_areas(state.inner);
1048 state.header_area = l_rows[0];
1049 state.table_area = l_rows[1];
1050 state.footer_area = l_rows[2];
1051
1052 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1054 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1055
1056 sa.render(
1058 area,
1059 buf,
1060 &mut ScrollAreaState::new()
1061 .h_scroll(&mut state.hscroll)
1062 .v_scroll(&mut state.vscroll),
1063 );
1064
1065 self.render_header(
1067 state.columns,
1068 width,
1069 l_columns.as_ref(),
1070 l_spacers.as_ref(),
1071 state.header_area,
1072 buf,
1073 state,
1074 );
1075 self.render_footer(
1076 state.columns,
1077 width,
1078 l_columns.as_ref(),
1079 l_spacers.as_ref(),
1080 state.footer_area,
1081 buf,
1082 state,
1083 );
1084
1085 state.row_areas.clear();
1087 state.vscroll.set_page_len(0);
1088 state.hscroll.set_page_len(area.width as usize);
1089
1090 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1091 let mut row = None;
1092 let mut row_y = state.table_area.y;
1093 let mut row_heights = Vec::new();
1094 #[cfg(feature = "perf_warnings")]
1095 let mut insane_offset = false;
1096
1097 let mut ctx = TableContext {
1098 focus: state.focus.get(),
1099 selected_cell: false,
1100 selected_row: false,
1101 selected_column: false,
1102 style: self.style,
1103 row_style: None,
1104 select_style: None,
1105 space_area: Default::default(),
1106 row_area: Default::default(),
1107 non_exhaustive: NonExhaustive,
1108 };
1109
1110 if data.nth(state.vscroll.offset()) {
1111 row = Some(state.vscroll.offset());
1112 loop {
1113 ctx.row_style = data.row_style();
1114 let render_row_area = Rect::new(0, 0, width, data.row_height());
1118 ctx.row_area = render_row_area;
1119 row_buf.resize(render_row_area);
1120 if self.auto_styles {
1121 if let Some(row_style) = ctx.row_style {
1122 row_buf.set_style(render_row_area, row_style);
1123 } else {
1124 row_buf.set_style(render_row_area, self.style);
1125 }
1126 }
1127 row_heights.push(render_row_area.height);
1128
1129 let visible_row_area = Rect::new(
1131 state.table_area.x,
1132 row_y,
1133 state.table_area.width,
1134 render_row_area.height,
1135 )
1136 .intersection(state.table_area);
1137 state.row_areas.push(visible_row_area);
1138 if render_row_area.height == visible_row_area.height {
1140 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1141 }
1142
1143 if render_row_area.height > 0 {
1145 let mut col = 0;
1146 loop {
1147 if col >= state.columns {
1148 break;
1149 }
1150
1151 let render_cell_area = Rect::new(
1152 l_columns[col].x,
1153 0,
1154 l_columns[col].width,
1155 render_row_area.height,
1156 );
1157 ctx.space_area = Rect::new(
1158 l_spacers[col + 1].x,
1159 0,
1160 l_spacers[col + 1].width,
1161 render_row_area.height,
1162 );
1163
1164 if state.selection.is_selected_cell(col, row.expect("row")) {
1165 ctx.selected_cell = true;
1166 ctx.selected_row = false;
1167 ctx.selected_column = false;
1168 ctx.select_style = self.patch_select(
1169 self.select_cell_style,
1170 state.focus.get(),
1171 self.show_cell_focus,
1172 );
1173 } else if state.selection.is_selected_row(row.expect("row")) {
1174 ctx.selected_cell = false;
1175 ctx.selected_row = true;
1176 ctx.selected_column = false;
1177 ctx.select_style = if self.select_row_style.is_some() {
1179 self.patch_select(
1180 self.select_row_style,
1181 state.focus.get(),
1182 self.show_row_focus,
1183 )
1184 } else {
1185 self.patch_select(
1186 Some(self.style),
1187 state.focus.get(),
1188 self.show_row_focus,
1189 )
1190 };
1191 } else if state.selection.is_selected_column(col) {
1192 ctx.selected_cell = false;
1193 ctx.selected_row = false;
1194 ctx.selected_column = true;
1195 ctx.select_style = self.patch_select(
1196 self.select_column_style,
1197 state.focus.get(),
1198 self.show_column_focus,
1199 );
1200 } else {
1201 ctx.selected_cell = false;
1202 ctx.selected_row = false;
1203 ctx.selected_column = false;
1204 ctx.select_style = None;
1205 }
1206
1207 if render_cell_area.right() > state.hscroll.offset as u16
1209 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1210 {
1211 if self.auto_styles {
1212 if let Some(select_style) = ctx.select_style {
1213 row_buf.set_style(render_cell_area, select_style);
1214 row_buf.set_style(ctx.space_area, select_style);
1215 }
1216 }
1217 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1218 }
1219
1220 col += 1;
1221 }
1222
1223 transfer_buffer(
1225 &mut row_buf,
1226 state.hscroll.offset() as u16,
1227 visible_row_area,
1228 buf,
1229 );
1230 }
1231
1232 if visible_row_area.bottom() >= state.table_area.bottom() {
1233 break;
1234 }
1235 if !data.nth(0) {
1236 break;
1237 }
1238 row = Some(row.expect("row").saturating_add(1));
1239 row_y += render_row_area.height;
1240 }
1241 } else {
1242 if data.rows().is_none() || data.rows() == Some(0) {
1247 } else {
1249 #[cfg(feature = "perf_warnings")]
1250 {
1251 insane_offset = true;
1252 }
1253 }
1254 }
1255
1256 #[allow(unused_variables)]
1258 let algorithm;
1259 #[allow(unused_assignments)]
1260 {
1261 if let Some(rows) = data.rows() {
1262 algorithm = 0;
1263 let skip_rows = rows
1267 .saturating_sub(row.map_or(0, |v| v + 1))
1268 .saturating_sub(state.table_area.height as usize);
1269 if skip_rows > 0 {
1271 row_heights.clear();
1272 }
1273 let nth_row = skip_rows;
1274 if data.nth(nth_row) {
1276 let mut sum_height = row_heights.iter().sum::<u16>();
1277 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1278 loop {
1279 let row_height = data.row_height();
1280 row_heights.push(row_height);
1281
1282 sum_height += row_height;
1285 if sum_height
1286 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1287 > state.table_area.height
1288 {
1289 let lost_height = row_heights.remove(0);
1290 sum_height -= lost_height;
1291 }
1292
1293 if !data.nth(0) {
1294 break;
1295 }
1296
1297 row = Some(row.expect("row") + 1);
1298 if row.expect("row") > rows {
1300 break;
1301 }
1302 }
1303 while data.nth(0) {
1306 row = Some(row.expect("row") + 1);
1307 }
1308 } else {
1309 }
1312
1313 state.rows = rows;
1314 state._counted_rows = row.map_or(0, |v| v + 1);
1315
1316 if let Some(last_page) = state.calc_last_page(row_heights) {
1318 state.vscroll.set_max_offset(state.rows - last_page);
1319 } else {
1320 state.vscroll.set_max_offset(
1324 state.rows.saturating_sub(state.table_area.height as usize),
1325 );
1326 }
1327 } else if self.no_row_count {
1328 algorithm = 1;
1329
1330 if row.is_some() {
1334 if data.nth(0) {
1335 row = Some(row.expect("row").saturating_add(1));
1337 if data.nth(0) {
1338 row = Some(usize::MAX - 1);
1340 }
1341 }
1342 }
1343
1344 state.rows = row.map_or(0, |v| v + 1);
1345 state._counted_rows = row.map_or(0, |v| v + 1);
1346 state.vscroll.set_max_offset(usize::MAX - 1);
1348 if state.vscroll.page_len() == 0 {
1349 state.vscroll.set_page_len(state.table_area.height as usize);
1350 }
1351 } else {
1352 algorithm = 2;
1353
1354 let mut sum_height = row_heights.iter().sum::<u16>();
1356 while data.nth(0) {
1357 let row_height = data.row_height();
1358 row_heights.push(row_height);
1359
1360 sum_height += row_height;
1363 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1364 > state.table_area.height
1365 {
1366 let lost_height = row_heights.remove(0);
1367 sum_height -= lost_height;
1368 }
1369 row = Some(row.map_or(0, |v| v + 1));
1370 }
1371
1372 state.rows = row.map_or(0, |v| v + 1);
1373 state._counted_rows = row.map_or(0, |v| v + 1);
1374
1375 if let Some(last_page) = state.calc_last_page(row_heights) {
1377 state.vscroll.set_max_offset(state.rows - last_page);
1378 } else {
1379 state.vscroll.set_max_offset(0);
1380 }
1381 }
1382 }
1383 {
1384 state
1385 .hscroll
1386 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1387 }
1388
1389 ScrollArea::new()
1391 .style(self.style)
1392 .block(self.block.as_ref())
1393 .ignore_block()
1394 .h_scroll(self.hscroll.as_ref())
1395 .v_scroll(self.vscroll.as_ref())
1396 .render(
1397 area,
1398 buf,
1399 &mut ScrollAreaState::new()
1400 .h_scroll(&mut state.hscroll)
1401 .v_scroll(&mut state.vscroll),
1402 );
1403
1404 #[cfg(feature = "perf_warnings")]
1405 {
1406 use std::fmt::Write;
1407 let mut msg = String::new();
1408 if insane_offset {
1409 _ = write!(
1410 msg,
1411 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1412 state.vscroll.offset(),
1413 state.rows,
1414 state._counted_rows,
1415 algorithm
1416 );
1417 }
1418 if state.rows != state._counted_rows {
1419 _ = write!(
1420 msg,
1421 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1422 state.rows, state._counted_rows, algorithm
1423 );
1424 }
1425 if !msg.is_empty() {
1426 use log::warn;
1427 use ratatui::style::Stylize;
1428 use ratatui::text::Text;
1429
1430 warn!("{}", &msg);
1431 Text::from(msg)
1432 .white()
1433 .on_red()
1434 .render(state.table_area, buf);
1435 }
1436 }
1437 }
1438
1439 #[allow(clippy::too_many_arguments)]
1440 fn render_footer(
1441 &self,
1442 columns: usize,
1443 width: u16,
1444 l_columns: &[Rect],
1445 l_spacers: &[Rect],
1446 area: Rect,
1447 buf: &mut Buffer,
1448 state: &mut TableState<Selection>,
1449 ) {
1450 if let Some(footer) = &self.footer {
1451 let render_row_area = Rect::new(0, 0, width, footer.height);
1452 let mut row_buf = Buffer::empty(render_row_area);
1453
1454 row_buf.set_style(render_row_area, self.style);
1455 if let Some(footer_style) = footer.style {
1456 row_buf.set_style(render_row_area, footer_style);
1457 } else if let Some(footer_style) = self.footer_style {
1458 row_buf.set_style(render_row_area, footer_style);
1459 }
1460
1461 let mut col = 0;
1462 loop {
1463 if col >= columns {
1464 break;
1465 }
1466
1467 let render_cell_area =
1468 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1469 let render_space_area = Rect::new(
1470 l_spacers[col + 1].x,
1471 0,
1472 l_spacers[col + 1].width,
1473 area.height,
1474 );
1475
1476 if state.selection.is_selected_column(col) {
1477 if let Some(selected_style) = self.patch_select(
1478 self.select_footer_style,
1479 state.focus.get(),
1480 self.show_footer_focus,
1481 ) {
1482 row_buf.set_style(render_cell_area, selected_style);
1483 row_buf.set_style(render_space_area, selected_style);
1484 }
1485 };
1486
1487 if render_cell_area.right() > state.hscroll.offset as u16
1489 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1490 {
1491 if let Some(cell) = footer.cells.get(col) {
1492 if let Some(cell_style) = cell.style {
1493 row_buf.set_style(render_cell_area, cell_style);
1494 }
1495 cell.content.clone().render(render_cell_area, &mut row_buf);
1496 }
1497 }
1498
1499 col += 1;
1500 }
1501
1502 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1504 }
1505 }
1506
1507 #[allow(clippy::too_many_arguments)]
1508 fn render_header(
1509 &self,
1510 columns: usize,
1511 width: u16,
1512 l_columns: &[Rect],
1513 l_spacers: &[Rect],
1514 area: Rect,
1515 buf: &mut Buffer,
1516 state: &mut TableState<Selection>,
1517 ) {
1518 if let Some(header) = &self.header {
1519 let render_row_area = Rect::new(0, 0, width, header.height);
1520 let mut row_buf = Buffer::empty(render_row_area);
1521
1522 row_buf.set_style(render_row_area, self.style);
1523 if let Some(header_style) = header.style {
1524 row_buf.set_style(render_row_area, header_style);
1525 } else if let Some(header_style) = self.header_style {
1526 row_buf.set_style(render_row_area, header_style);
1527 }
1528
1529 let mut col = 0;
1530 loop {
1531 if col >= columns {
1532 break;
1533 }
1534
1535 let render_cell_area =
1536 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1537 let render_space_area = Rect::new(
1538 l_spacers[col + 1].x,
1539 0,
1540 l_spacers[col + 1].width,
1541 area.height,
1542 );
1543
1544 if state.selection.is_selected_column(col) {
1545 if let Some(selected_style) = self.patch_select(
1546 self.select_header_style,
1547 state.focus.get(),
1548 self.show_header_focus,
1549 ) {
1550 row_buf.set_style(render_cell_area, selected_style);
1551 row_buf.set_style(render_space_area, selected_style);
1552 }
1553 };
1554
1555 if render_cell_area.right() > state.hscroll.offset as u16
1557 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1558 {
1559 if let Some(cell) = header.cells.get(col) {
1560 if let Some(cell_style) = cell.style {
1561 row_buf.set_style(render_cell_area, cell_style);
1562 }
1563 cell.content.clone().render(render_cell_area, &mut row_buf);
1564 }
1565 }
1566
1567 col += 1;
1568 }
1569
1570 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1572 }
1573 }
1574
1575 fn calculate_column_areas(
1576 &self,
1577 columns: usize,
1578 l_columns: &[Rect],
1579 l_spacers: &[Rect],
1580 state: &mut TableState<Selection>,
1581 ) {
1582 state.column_areas.clear();
1583 state.column_layout.clear();
1584
1585 let mut col = 0;
1586 let shift = state.hscroll.offset() as isize;
1587 loop {
1588 if col >= columns {
1589 break;
1590 }
1591
1592 state.column_layout.push(Rect::new(
1593 l_columns[col].x,
1594 0,
1595 l_columns[col].width + l_spacers[col + 1].width,
1596 0,
1597 ));
1598
1599 let cell_x1 = l_columns[col].x as isize;
1600 let cell_x2 =
1601 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1602
1603 let squish_x1 = cell_x1.saturating_sub(shift);
1604 let squish_x2 = cell_x2.saturating_sub(shift);
1605
1606 let abs_x1 = max(0, squish_x1) as u16;
1607 let abs_x2 = max(0, squish_x2) as u16;
1608
1609 let v_area = Rect::new(
1610 state.table_area.x + abs_x1,
1611 state.table_area.y,
1612 abs_x2 - abs_x1,
1613 state.table_area.height,
1614 );
1615 state
1616 .column_areas
1617 .push(v_area.intersection(state.table_area));
1618
1619 col += 1;
1620 }
1621 }
1622
1623 #[expect(clippy::collapsible_else_if)]
1624 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1625 if let Some(style) = style {
1626 if let Some(focus_style) = self.focus_style {
1627 if focus && show {
1628 Some(style.patch(focus_style))
1629 } else {
1630 Some(fallback_select_style(style))
1631 }
1632 } else {
1633 if focus && show {
1634 Some(revert_style(style))
1635 } else {
1636 Some(fallback_select_style(style))
1637 }
1638 }
1639 } else {
1640 None
1641 }
1642 }
1643}
1644
1645impl Default for TableStyle {
1646 fn default() -> Self {
1647 Self {
1648 style: Default::default(),
1649 header: None,
1650 footer: None,
1651 select_row: None,
1652 select_column: None,
1653 select_cell: None,
1654 select_header: None,
1655 select_footer: None,
1656 show_row_focus: true, show_column_focus: false,
1658 show_cell_focus: false,
1659 show_header_focus: false,
1660 show_footer_focus: false,
1661 focus_style: None,
1662 block: None,
1663 border_style: None,
1664 title_style: None,
1665 scroll: None,
1666 non_exhaustive: NonExhaustive,
1667 }
1668 }
1669}
1670
1671impl<Selection: Clone> Clone for TableState<Selection> {
1672 fn clone(&self) -> Self {
1673 Self {
1674 focus: self.focus.new_instance(),
1675 area: self.area,
1676 inner: self.inner,
1677 header_area: self.header_area,
1678 table_area: self.table_area,
1679 row_areas: self.row_areas.clone(),
1680 column_areas: self.column_areas.clone(),
1681 column_layout: self.column_layout.clone(),
1682 footer_area: self.footer_area,
1683 rows: self.rows,
1684 _counted_rows: self._counted_rows,
1685 columns: self.columns,
1686 vscroll: self.vscroll.clone(),
1687 hscroll: self.hscroll.clone(),
1688 selection: self.selection.clone(),
1689 mouse: Default::default(),
1690 non_exhaustive: NonExhaustive,
1691 }
1692 }
1693}
1694
1695impl<Selection: Default> Default for TableState<Selection> {
1696 fn default() -> Self {
1697 Self {
1698 focus: Default::default(),
1699 area: Default::default(),
1700 inner: Default::default(),
1701 header_area: Default::default(),
1702 table_area: Default::default(),
1703 row_areas: Default::default(),
1704 column_areas: Default::default(),
1705 column_layout: Default::default(),
1706 footer_area: Default::default(),
1707 rows: Default::default(),
1708 _counted_rows: Default::default(),
1709 columns: Default::default(),
1710 vscroll: Default::default(),
1711 hscroll: Default::default(),
1712 selection: Default::default(),
1713 mouse: Default::default(),
1714 non_exhaustive: NonExhaustive,
1715 }
1716 }
1717}
1718
1719impl<Selection> HasFocus for TableState<Selection> {
1720 fn build(&self, builder: &mut FocusBuilder) {
1721 builder.leaf_widget(self);
1722 }
1723
1724 #[inline]
1725 fn focus(&self) -> FocusFlag {
1726 self.focus.clone()
1727 }
1728
1729 #[inline]
1730 fn area(&self) -> Rect {
1731 self.area
1732 }
1733}
1734
1735impl<Selection> HasScreenCursor for TableState<Selection> {
1736 fn screen_cursor(&self) -> Option<(u16, u16)> {
1737 None
1738 }
1739}
1740
1741impl<Selection> RelocatableState for TableState<Selection> {
1742 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1743 self.area.relocate(shift, clip);
1744 self.inner.relocate(shift, clip);
1745 self.header_area.relocate(shift, clip);
1746 self.table_area.relocate(shift, clip);
1747 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1748 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1749 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1750 self.footer_area.relocate(shift, clip);
1751 self.hscroll.relocate(shift, clip);
1752 self.vscroll.relocate(shift, clip);
1753 }
1754}
1755
1756impl<Selection> TableState<Selection> {
1757 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1758 let mut sum_heights = 0;
1759 let mut n_rows = 0;
1760 while let Some(h) = row_heights.pop() {
1761 sum_heights += h;
1762 n_rows += 1;
1763 if sum_heights >= self.table_area.height {
1764 break;
1765 }
1766 }
1767
1768 if sum_heights < self.table_area.height {
1769 None
1770 } else {
1771 Some(n_rows)
1772 }
1773 }
1774}
1775
1776impl<Selection> TableState<Selection>
1778where
1779 Selection: Default,
1780{
1781 pub fn new() -> Self {
1782 Self::default()
1783 }
1784
1785 pub fn named(name: &str) -> Self {
1786 Self {
1787 focus: FocusFlag::new().with_name(name),
1788 ..TableState::default()
1789 }
1790 }
1791}
1792
1793impl<Selection> TableState<Selection> {
1795 #[inline]
1797 pub fn rows(&self) -> usize {
1798 self.rows
1799 }
1800
1801 pub fn rows_changed(&mut self, rows: usize) {
1813 self.rows = rows;
1814 self.vscroll
1815 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1816 }
1817
1818 #[inline]
1820 pub fn columns(&self) -> usize {
1821 self.columns
1822 }
1823}
1824
1825impl<Selection> TableState<Selection> {
1827 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1833 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1834 return None;
1835 }
1836
1837 let mut areas = Vec::new();
1838
1839 let r = self.row_areas[row - self.vscroll.offset()];
1840 for c in &self.column_areas {
1841 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1842 }
1843
1844 Some((r, areas))
1845 }
1846
1847 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1849 let col = self.column_at_clicked(pos);
1850 let row = self.row_at_clicked(pos);
1851
1852 match (col, row) {
1853 (Some(col), Some(row)) => Some((col, row)),
1854 _ => None,
1855 }
1856 }
1857
1858 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1860 self.mouse.column_at(&self.column_areas, pos.0)
1861 }
1862
1863 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1865 self.mouse
1866 .row_at(&self.row_areas, pos.1)
1867 .map(|v| self.vscroll.offset() + v)
1868 }
1869
1870 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1873 let col = self.column_at_drag(pos);
1874 let row = self.row_at_drag(pos);
1875
1876 (col, row)
1877 }
1878
1879 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1886 match self
1887 .mouse
1888 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1889 {
1890 Ok(v) => self.vscroll.offset() + v,
1891 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1892 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1893 }
1894 }
1895
1896 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1900 match self
1901 .mouse
1902 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1903 {
1904 Ok(v) => v,
1905 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1906 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1907 }
1908 }
1909}
1910
1911impl<Selection: TableSelection> TableState<Selection> {
1913 pub fn clear_offset(&mut self) {
1915 self.vscroll.set_offset(0);
1916 self.hscroll.set_offset(0);
1917 }
1918
1919 pub fn row_max_offset(&self) -> usize {
1924 self.vscroll.max_offset()
1925 }
1926
1927 pub fn row_offset(&self) -> usize {
1929 self.vscroll.offset()
1930 }
1931
1932 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1939 self.vscroll.set_offset(offset)
1940 }
1941
1942 pub fn page_len(&self) -> usize {
1944 self.vscroll.page_len()
1945 }
1946
1947 pub fn row_scroll_by(&self) -> usize {
1949 self.vscroll.scroll_by()
1950 }
1951
1952 pub fn x_max_offset(&self) -> usize {
1957 self.hscroll.max_offset()
1958 }
1959
1960 pub fn x_offset(&self) -> usize {
1962 self.hscroll.offset()
1963 }
1964
1965 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1972 self.hscroll.set_offset(offset)
1973 }
1974
1975 pub fn page_width(&self) -> usize {
1977 self.hscroll.page_len()
1978 }
1979
1980 pub fn x_scroll_by(&self) -> usize {
1982 self.hscroll.scroll_by()
1983 }
1984
1985 pub fn scroll_to_selected(&mut self) -> bool {
1989 if let Some(selected) = self.selection.lead_selection() {
1990 let c = self.scroll_to_col(selected.0);
1991 let r = self.scroll_to_row(selected.1);
1992 r || c
1993 } else {
1994 false
1995 }
1996 }
1997
1998 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
2003 if pos >= self.rows {
2004 false
2005 } else if pos == self.row_offset().saturating_add(self.page_len()) {
2006 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
2008 if heights < self.table_area.height {
2009 false
2010 } else {
2011 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2012 }
2013 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2014 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2015 } else if pos < self.row_offset() {
2016 self.set_row_offset(pos)
2017 } else {
2018 false
2019 }
2020 }
2021
2022 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2024 if let Some(col) = self.column_layout.get(pos) {
2025 if (col.left() as usize) < self.x_offset() {
2026 self.set_x_offset(col.x as usize)
2027 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2028 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2029 } else {
2030 false
2031 }
2032 } else {
2033 false
2034 }
2035 }
2036
2037 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2039 if pos >= self.x_offset().saturating_add(self.page_width()) {
2040 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2041 } else if pos < self.x_offset() {
2042 self.set_x_offset(pos)
2043 } else {
2044 false
2045 }
2046 }
2047
2048 pub fn scroll_up(&mut self, n: usize) -> bool {
2050 self.vscroll.scroll_up(n)
2051 }
2052
2053 pub fn scroll_down(&mut self, n: usize) -> bool {
2055 self.vscroll.scroll_down(n)
2056 }
2057
2058 pub fn scroll_left(&mut self, n: usize) -> bool {
2060 self.hscroll.scroll_left(n)
2061 }
2062
2063 pub fn scroll_right(&mut self, n: usize) -> bool {
2065 self.hscroll.scroll_right(n)
2066 }
2067}
2068
2069impl TableState<RowSelection> {
2070 pub fn items_added(&mut self, pos: usize, n: usize) {
2074 self.vscroll.items_added(pos, n);
2075 self.selection.items_added(pos, n);
2076 self.rows += n;
2077 }
2078
2079 pub fn items_removed(&mut self, pos: usize, n: usize) {
2083 self.vscroll.items_removed(pos, n);
2084 self.selection
2085 .items_removed(pos, n, self.rows.saturating_sub(1));
2086 self.rows -= n;
2087 }
2088
2089 #[inline]
2092 pub fn set_scroll_selection(&mut self, scroll: bool) {
2093 self.selection.set_scroll_selected(scroll);
2094 }
2095
2096 #[inline]
2098 pub fn clear_selection(&mut self) {
2099 self.selection.clear();
2100 }
2101
2102 #[inline]
2104 pub fn has_selection(&mut self) -> bool {
2105 self.selection.has_selection()
2106 }
2107
2108 #[inline]
2111 pub fn selected(&self) -> Option<usize> {
2112 self.selection.selected()
2113 }
2114
2115 #[inline]
2118 #[allow(clippy::manual_filter)]
2119 pub fn selected_checked(&self) -> Option<usize> {
2120 if let Some(selected) = self.selection.selected() {
2121 if selected < self.rows {
2122 Some(selected)
2123 } else {
2124 None
2125 }
2126 } else {
2127 None
2128 }
2129 }
2130
2131 #[inline]
2134 pub fn select(&mut self, row: Option<usize>) -> bool {
2135 self.selection.select(row)
2136 }
2137
2138 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2142 if self.vscroll.max_offset() > 0 {
2143 (self.rows * offset) / self.vscroll.max_offset()
2144 } else {
2145 0 }
2147 }
2148
2149 #[inline]
2151 pub fn move_deselect(&mut self) -> bool {
2152 let r = self.selection.select(None);
2153 let s = self.set_row_offset(0);
2154 r || s
2155 }
2156
2157 #[inline]
2160 pub fn move_to(&mut self, row: usize) -> bool {
2161 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2162 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2163 r || s
2164 }
2165
2166 #[inline]
2169 pub fn move_up(&mut self, n: usize) -> bool {
2170 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2171 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2172 r || s
2173 }
2174
2175 #[inline]
2178 pub fn move_down(&mut self, n: usize) -> bool {
2179 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2180 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2181 r || s
2182 }
2183}
2184
2185impl TableState<RowSetSelection> {
2186 #[inline]
2188 pub fn clear_selection(&mut self) {
2189 self.selection.clear();
2190 }
2191
2192 #[inline]
2194 pub fn has_selection(&mut self) -> bool {
2195 self.selection.has_selection()
2196 }
2197
2198 #[inline]
2200 pub fn selected(&self) -> HashSet<usize> {
2201 self.selection.selected()
2202 }
2203
2204 #[inline]
2211 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2212 self.selection.set_lead(row, extend)
2213 }
2214
2215 #[inline]
2217 pub fn lead(&self) -> Option<usize> {
2218 self.selection.lead()
2219 }
2220
2221 #[inline]
2223 pub fn anchor(&self) -> Option<usize> {
2224 self.selection.anchor()
2225 }
2226
2227 #[inline]
2230 pub fn retire_selection(&mut self) {
2231 self.selection.retire_selection();
2232 }
2233
2234 #[inline]
2239 pub fn add_selected(&mut self, idx: usize) {
2240 self.selection.add(idx);
2241 }
2242
2243 #[inline]
2248 pub fn remove_selected(&mut self, idx: usize) {
2249 self.selection.remove(idx);
2250 }
2251
2252 #[inline]
2255 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2256 let r = self
2257 .selection
2258 .move_to(row, self.rows.saturating_sub(1), extend);
2259 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2260 r || s
2261 }
2262
2263 #[inline]
2266 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2267 let r = self
2268 .selection
2269 .move_up(n, self.rows.saturating_sub(1), extend);
2270 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2271 r || s
2272 }
2273
2274 #[inline]
2277 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2278 let r = self
2279 .selection
2280 .move_down(n, self.rows.saturating_sub(1), extend);
2281 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2282 r || s
2283 }
2284}
2285
2286impl TableState<CellSelection> {
2287 #[inline]
2288 pub fn clear_selection(&mut self) {
2289 self.selection.clear();
2290 }
2291
2292 #[inline]
2293 pub fn has_selection(&mut self) -> bool {
2294 self.selection.has_selection()
2295 }
2296
2297 #[inline]
2299 pub fn selected(&self) -> Option<(usize, usize)> {
2300 self.selection.selected()
2301 }
2302
2303 #[inline]
2305 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2306 self.selection.select_cell(select)
2307 }
2308
2309 #[inline]
2311 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2312 if let Some(row) = row {
2313 self.selection
2314 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2315 } else {
2316 self.selection.select_row(None)
2317 }
2318 }
2319
2320 #[inline]
2322 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2323 if let Some(column) = column {
2324 self.selection
2325 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2326 } else {
2327 self.selection.select_column(None)
2328 }
2329 }
2330
2331 #[inline]
2333 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2334 let r = self.selection.move_to(
2335 select,
2336 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2337 );
2338 let s = self.scroll_to_selected();
2339 r || s
2340 }
2341
2342 #[inline]
2344 pub fn move_to_row(&mut self, row: usize) -> bool {
2345 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2346 let s = self.scroll_to_selected();
2347 r || s
2348 }
2349
2350 #[inline]
2352 pub fn move_to_col(&mut self, col: usize) -> bool {
2353 let r = self
2354 .selection
2355 .move_to_col(col, self.columns.saturating_sub(1));
2356 let s = self.scroll_to_selected();
2357 r || s
2358 }
2359
2360 #[inline]
2363 pub fn move_up(&mut self, n: usize) -> bool {
2364 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2365 let s = self.scroll_to_selected();
2366 r || s
2367 }
2368
2369 #[inline]
2372 pub fn move_down(&mut self, n: usize) -> bool {
2373 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2374 let s = self.scroll_to_selected();
2375 r || s
2376 }
2377
2378 #[inline]
2381 pub fn move_left(&mut self, n: usize) -> bool {
2382 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2383 let s = self.scroll_to_selected();
2384 r || s
2385 }
2386
2387 #[inline]
2390 pub fn move_right(&mut self, n: usize) -> bool {
2391 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2392 let s = self.scroll_to_selected();
2393 r || s
2394 }
2395}
2396
2397impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2398 for TableState<Selection>
2399{
2400 fn handle(
2402 &mut self,
2403 event: &crossterm::event::Event,
2404 _keymap: DoubleClick,
2405 ) -> DoubleClickOutcome {
2406 match event {
2407 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2408 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2409 DoubleClickOutcome::ClickClick(col, row)
2410 } else {
2411 DoubleClickOutcome::Continue
2412 }
2413 }
2414 _ => DoubleClickOutcome::Continue,
2415 }
2416 }
2417}
2418
2419pub fn handle_doubleclick_events<Selection: TableSelection>(
2421 state: &mut TableState<Selection>,
2422 event: &crossterm::event::Event,
2423) -> DoubleClickOutcome {
2424 state.handle(event, DoubleClick)
2425}