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(debug_assertions)]
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(debug_assertions)]
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(debug_assertions)]
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 #[deprecated(since = "1.1.1", note = "not in use")]
902 pub fn debug(self, _: bool) -> Self {
903 self
904 }
905}
906
907impl<Selection> Table<'_, Selection> {
908 #[inline]
910 fn total_width(&self, area_width: u16) -> u16 {
911 if let Some(layout_width) = self.layout_width {
912 layout_width
913 } else if self.layout_column_widths {
914 let mut width = 0;
915 for w in self.widths.iter().copied() {
916 match w {
917 Constraint::Min(v) => width += v + self.column_spacing,
918 Constraint::Max(v) => width += v + self.column_spacing,
919 Constraint::Length(v) => width += v + self.column_spacing,
920 Constraint::Percentage(p) => {
921 width += (((area_width as u32) * (p as u32)) / 100) as u16;
922 }
923 Constraint::Ratio(n, d) => {
924 width += (((area_width as u32) * n) / d) as u16;
925 }
926 Constraint::Fill(_) => {
927 width += 10;
929 }
930 }
931 }
932 max(width, area_width)
933 } else {
934 area_width
935 }
936 }
937
938 #[inline]
940 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
941 let width = self.total_width(width);
942 let area = Rect::new(0, 0, width, 0);
943
944 let (layout, spacers) = Layout::horizontal(&self.widths)
945 .flex(self.flex)
946 .spacing(self.column_spacing)
947 .split_with_spacers(area);
948
949 (width, layout, spacers)
950 }
951
952 #[inline]
954 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
955 let heights = vec![
956 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
957 Constraint::Fill(1),
958 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
959 ];
960
961 Layout::vertical(heights).split(area)
962 }
963}
964
965impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
966where
967 Selection: TableSelection,
968{
969 type State = TableState<Selection>;
970
971 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
972 let iter = self.data.iter();
973 self.render_iter(iter, area, buf, state);
974 }
975}
976
977impl<Selection> StatefulWidget for Table<'_, Selection>
978where
979 Selection: TableSelection,
980{
981 type State = TableState<Selection>;
982
983 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
984 let iter = mem::take(&mut self.data).into_iter();
985 self.render_iter(iter, area, buf, state);
986 }
987}
988
989impl<'a, Selection> Table<'a, Selection>
990where
991 Selection: TableSelection,
992{
993 fn render_iter<'b>(
998 &self,
999 mut data: DataReprIter<'a, 'b>,
1000 area: Rect,
1001 buf: &mut Buffer,
1002 state: &mut TableState<Selection>,
1003 ) {
1004 if let Some(rows) = data.rows() {
1005 state.rows = rows;
1006 }
1007 state.columns = self.widths.len();
1008 state.area = area;
1009
1010 let sa = ScrollArea::new()
1011 .style(self.style)
1012 .block(self.block.as_ref())
1013 .h_scroll(self.hscroll.as_ref())
1014 .v_scroll(self.vscroll.as_ref())
1015 .ignore_scroll();
1016 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1017
1018 let l_rows = self.layout_areas(state.inner);
1019 state.header_area = l_rows[0];
1020 state.table_area = l_rows[1];
1021 state.footer_area = l_rows[2];
1022
1023 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1025 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1026
1027 sa.render(
1029 area,
1030 buf,
1031 &mut ScrollAreaState::new()
1032 .h_scroll(&mut state.hscroll)
1033 .v_scroll(&mut state.vscroll),
1034 );
1035
1036 self.render_header(
1038 state.columns,
1039 width,
1040 l_columns.as_ref(),
1041 l_spacers.as_ref(),
1042 state.header_area,
1043 buf,
1044 state,
1045 );
1046 self.render_footer(
1047 state.columns,
1048 width,
1049 l_columns.as_ref(),
1050 l_spacers.as_ref(),
1051 state.footer_area,
1052 buf,
1053 state,
1054 );
1055
1056 state.row_areas.clear();
1058 state.vscroll.set_page_len(0);
1059 state.hscroll.set_page_len(area.width as usize);
1060
1061 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1062 let mut row = None;
1063 let mut row_y = state.table_area.y;
1064 let mut row_heights = Vec::new();
1065 #[cfg(debug_assertions)]
1066 let mut insane_offset = false;
1067
1068 let mut ctx = TableContext {
1069 focus: state.focus.get(),
1070 selected_cell: false,
1071 selected_row: false,
1072 selected_column: false,
1073 style: self.style,
1074 row_style: None,
1075 select_style: None,
1076 space_area: Default::default(),
1077 row_area: Default::default(),
1078 non_exhaustive: NonExhaustive,
1079 };
1080
1081 if data.nth(state.vscroll.offset()) {
1082 row = Some(state.vscroll.offset());
1083 loop {
1084 ctx.row_style = data.row_style();
1085 let render_row_area = Rect::new(0, 0, width, data.row_height());
1089 ctx.row_area = render_row_area;
1090 row_buf.resize(render_row_area);
1091 if self.auto_styles {
1092 if let Some(row_style) = ctx.row_style {
1093 row_buf.set_style(render_row_area, row_style);
1094 } else {
1095 row_buf.set_style(render_row_area, self.style);
1096 }
1097 }
1098 row_heights.push(render_row_area.height);
1099
1100 let visible_row_area = Rect::new(
1102 state.table_area.x,
1103 row_y,
1104 state.table_area.width,
1105 render_row_area.height,
1106 )
1107 .intersection(state.table_area);
1108 state.row_areas.push(visible_row_area);
1109 if render_row_area.height == visible_row_area.height {
1111 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1112 }
1113
1114 if render_row_area.height > 0 {
1116 let mut col = 0;
1117 loop {
1118 if col >= state.columns {
1119 break;
1120 }
1121
1122 let render_cell_area = Rect::new(
1123 l_columns[col].x,
1124 0,
1125 l_columns[col].width,
1126 render_row_area.height,
1127 );
1128 ctx.space_area = Rect::new(
1129 l_spacers[col + 1].x,
1130 0,
1131 l_spacers[col + 1].width,
1132 render_row_area.height,
1133 );
1134
1135 if state.selection.is_selected_cell(col, row.expect("row")) {
1136 ctx.selected_cell = true;
1137 ctx.selected_row = false;
1138 ctx.selected_column = false;
1139 ctx.select_style = self.patch_select(
1140 self.select_cell_style,
1141 state.focus.get(),
1142 self.show_cell_focus,
1143 );
1144 } else if state.selection.is_selected_row(row.expect("row")) {
1145 ctx.selected_cell = false;
1146 ctx.selected_row = true;
1147 ctx.selected_column = false;
1148 ctx.select_style = if self.select_row_style.is_some() {
1150 self.patch_select(
1151 self.select_row_style,
1152 state.focus.get(),
1153 self.show_row_focus,
1154 )
1155 } else {
1156 self.patch_select(
1157 Some(self.style),
1158 state.focus.get(),
1159 self.show_row_focus,
1160 )
1161 };
1162 } else if state.selection.is_selected_column(col) {
1163 ctx.selected_cell = false;
1164 ctx.selected_row = false;
1165 ctx.selected_column = true;
1166 ctx.select_style = self.patch_select(
1167 self.select_column_style,
1168 state.focus.get(),
1169 self.show_column_focus,
1170 );
1171 } else {
1172 ctx.selected_cell = false;
1173 ctx.selected_row = false;
1174 ctx.selected_column = false;
1175 ctx.select_style = None;
1176 }
1177
1178 if render_cell_area.right() > state.hscroll.offset as u16
1180 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1181 {
1182 if self.auto_styles {
1183 if let Some(select_style) = ctx.select_style {
1184 row_buf.set_style(render_cell_area, select_style);
1185 row_buf.set_style(ctx.space_area, select_style);
1186 }
1187 }
1188 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1189 }
1190
1191 col += 1;
1192 }
1193
1194 transfer_buffer(
1196 &mut row_buf,
1197 state.hscroll.offset() as u16,
1198 visible_row_area,
1199 buf,
1200 );
1201 }
1202
1203 if visible_row_area.bottom() >= state.table_area.bottom() {
1204 break;
1205 }
1206 if !data.nth(0) {
1207 break;
1208 }
1209 row = Some(row.expect("row").saturating_add(1));
1210 row_y += render_row_area.height;
1211 }
1212 } else {
1213 if data.rows().is_none() || data.rows() == Some(0) {
1218 } else {
1220 #[cfg(debug_assertions)]
1221 {
1222 insane_offset = true;
1223 }
1224 }
1225 }
1226
1227 #[allow(unused_variables)]
1229 let algorithm;
1230 #[allow(unused_assignments)]
1231 {
1232 if let Some(rows) = data.rows() {
1233 algorithm = 0;
1234 let skip_rows = rows
1238 .saturating_sub(row.map_or(0, |v| v + 1))
1239 .saturating_sub(state.table_area.height as usize);
1240 if skip_rows > 0 {
1242 row_heights.clear();
1243 }
1244 let nth_row = skip_rows;
1245 if data.nth(nth_row) {
1247 let mut sum_height = row_heights.iter().sum::<u16>();
1248 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1249 loop {
1250 let row_height = data.row_height();
1251 row_heights.push(row_height);
1252
1253 sum_height += row_height;
1256 if sum_height
1257 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1258 > state.table_area.height
1259 {
1260 let lost_height = row_heights.remove(0);
1261 sum_height -= lost_height;
1262 }
1263
1264 if !data.nth(0) {
1265 break;
1266 }
1267
1268 row = Some(row.expect("row") + 1);
1269 if row.expect("row") > rows {
1271 break;
1272 }
1273 }
1274 while data.nth(0) {
1277 row = Some(row.expect("row") + 1);
1278 }
1279 } else {
1280 }
1283
1284 state.rows = rows;
1285 state._counted_rows = row.map_or(0, |v| v + 1);
1286
1287 if let Some(last_page) = state.calc_last_page(row_heights) {
1289 state.vscroll.set_max_offset(state.rows - last_page);
1290 } else {
1291 state.vscroll.set_max_offset(
1295 state.rows.saturating_sub(state.table_area.height as usize),
1296 );
1297 }
1298 } else if self.no_row_count {
1299 algorithm = 1;
1300
1301 if row.is_some() {
1305 if data.nth(0) {
1306 row = Some(row.expect("row").saturating_add(1));
1308 if data.nth(0) {
1309 row = Some(usize::MAX - 1);
1311 }
1312 }
1313 }
1314
1315 state.rows = row.map_or(0, |v| v + 1);
1316 state._counted_rows = row.map_or(0, |v| v + 1);
1317 state.vscroll.set_max_offset(usize::MAX - 1);
1319 if state.vscroll.page_len() == 0 {
1320 state.vscroll.set_page_len(state.table_area.height as usize);
1321 }
1322 } else {
1323 algorithm = 2;
1324
1325 let mut sum_height = row_heights.iter().sum::<u16>();
1327 while data.nth(0) {
1328 let row_height = data.row_height();
1329 row_heights.push(row_height);
1330
1331 sum_height += row_height;
1334 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1335 > state.table_area.height
1336 {
1337 let lost_height = row_heights.remove(0);
1338 sum_height -= lost_height;
1339 }
1340 row = Some(row.map_or(0, |v| v + 1));
1341 }
1342
1343 state.rows = row.map_or(0, |v| v + 1);
1344 state._counted_rows = row.map_or(0, |v| v + 1);
1345
1346 if let Some(last_page) = state.calc_last_page(row_heights) {
1348 state.vscroll.set_max_offset(state.rows - last_page);
1349 } else {
1350 state.vscroll.set_max_offset(0);
1351 }
1352 }
1353 }
1354 {
1355 state
1356 .hscroll
1357 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1358 }
1359
1360 ScrollArea::new()
1362 .style(self.style)
1363 .block(self.block.as_ref())
1364 .ignore_block()
1365 .h_scroll(self.hscroll.as_ref())
1366 .v_scroll(self.vscroll.as_ref())
1367 .render(
1368 area,
1369 buf,
1370 &mut ScrollAreaState::new()
1371 .h_scroll(&mut state.hscroll)
1372 .v_scroll(&mut state.vscroll),
1373 );
1374
1375 #[cfg(debug_assertions)]
1376 {
1377 use std::fmt::Write;
1378 let mut msg = String::new();
1379 if insane_offset {
1380 _ = write!(
1381 msg,
1382 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1383 state.vscroll.offset(),
1384 state.rows,
1385 state._counted_rows,
1386 algorithm
1387 );
1388 }
1389 if state.rows != state._counted_rows {
1390 _ = write!(
1391 msg,
1392 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1393 state.rows, state._counted_rows, algorithm
1394 );
1395 }
1396 if !msg.is_empty() {
1397 use log::warn;
1398 use ratatui::style::Stylize;
1399 use ratatui::text::Text;
1400
1401 warn!("{}", &msg);
1402 Text::from(msg)
1403 .white()
1404 .on_red()
1405 .render(state.table_area, buf);
1406 }
1407 }
1408 }
1409
1410 #[allow(clippy::too_many_arguments)]
1411 fn render_footer(
1412 &self,
1413 columns: usize,
1414 width: u16,
1415 l_columns: &[Rect],
1416 l_spacers: &[Rect],
1417 area: Rect,
1418 buf: &mut Buffer,
1419 state: &mut TableState<Selection>,
1420 ) {
1421 if let Some(footer) = &self.footer {
1422 let render_row_area = Rect::new(0, 0, width, footer.height);
1423 let mut row_buf = Buffer::empty(render_row_area);
1424
1425 row_buf.set_style(render_row_area, self.style);
1426 if let Some(footer_style) = footer.style {
1427 row_buf.set_style(render_row_area, footer_style);
1428 } else if let Some(footer_style) = self.footer_style {
1429 row_buf.set_style(render_row_area, footer_style);
1430 }
1431
1432 let mut col = 0;
1433 loop {
1434 if col >= columns {
1435 break;
1436 }
1437
1438 let render_cell_area =
1439 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1440 let render_space_area = Rect::new(
1441 l_spacers[col + 1].x,
1442 0,
1443 l_spacers[col + 1].width,
1444 area.height,
1445 );
1446
1447 if state.selection.is_selected_column(col) {
1448 if let Some(selected_style) = self.patch_select(
1449 self.select_footer_style,
1450 state.focus.get(),
1451 self.show_footer_focus,
1452 ) {
1453 row_buf.set_style(render_cell_area, selected_style);
1454 row_buf.set_style(render_space_area, selected_style);
1455 }
1456 };
1457
1458 if render_cell_area.right() > state.hscroll.offset as u16
1460 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1461 {
1462 if let Some(cell) = footer.cells.get(col) {
1463 if let Some(cell_style) = cell.style {
1464 row_buf.set_style(render_cell_area, cell_style);
1465 }
1466 cell.content.clone().render(render_cell_area, &mut row_buf);
1467 }
1468 }
1469
1470 col += 1;
1471 }
1472
1473 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1475 }
1476 }
1477
1478 #[allow(clippy::too_many_arguments)]
1479 fn render_header(
1480 &self,
1481 columns: usize,
1482 width: u16,
1483 l_columns: &[Rect],
1484 l_spacers: &[Rect],
1485 area: Rect,
1486 buf: &mut Buffer,
1487 state: &mut TableState<Selection>,
1488 ) {
1489 if let Some(header) = &self.header {
1490 let render_row_area = Rect::new(0, 0, width, header.height);
1491 let mut row_buf = Buffer::empty(render_row_area);
1492
1493 row_buf.set_style(render_row_area, self.style);
1494 if let Some(header_style) = header.style {
1495 row_buf.set_style(render_row_area, header_style);
1496 } else if let Some(header_style) = self.header_style {
1497 row_buf.set_style(render_row_area, header_style);
1498 }
1499
1500 let mut col = 0;
1501 loop {
1502 if col >= columns {
1503 break;
1504 }
1505
1506 let render_cell_area =
1507 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1508 let render_space_area = Rect::new(
1509 l_spacers[col + 1].x,
1510 0,
1511 l_spacers[col + 1].width,
1512 area.height,
1513 );
1514
1515 if state.selection.is_selected_column(col) {
1516 if let Some(selected_style) = self.patch_select(
1517 self.select_header_style,
1518 state.focus.get(),
1519 self.show_header_focus,
1520 ) {
1521 row_buf.set_style(render_cell_area, selected_style);
1522 row_buf.set_style(render_space_area, selected_style);
1523 }
1524 };
1525
1526 if render_cell_area.right() > state.hscroll.offset as u16
1528 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1529 {
1530 if let Some(cell) = header.cells.get(col) {
1531 if let Some(cell_style) = cell.style {
1532 row_buf.set_style(render_cell_area, cell_style);
1533 }
1534 cell.content.clone().render(render_cell_area, &mut row_buf);
1535 }
1536 }
1537
1538 col += 1;
1539 }
1540
1541 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1543 }
1544 }
1545
1546 fn calculate_column_areas(
1547 &self,
1548 columns: usize,
1549 l_columns: &[Rect],
1550 l_spacers: &[Rect],
1551 state: &mut TableState<Selection>,
1552 ) {
1553 state.column_areas.clear();
1554 state.column_layout.clear();
1555
1556 let mut col = 0;
1557 let shift = state.hscroll.offset() as isize;
1558 loop {
1559 if col >= columns {
1560 break;
1561 }
1562
1563 state.column_layout.push(Rect::new(
1564 l_columns[col].x,
1565 0,
1566 l_columns[col].width + l_spacers[col + 1].width,
1567 0,
1568 ));
1569
1570 let cell_x1 = l_columns[col].x as isize;
1571 let cell_x2 =
1572 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1573
1574 let squish_x1 = cell_x1.saturating_sub(shift);
1575 let squish_x2 = cell_x2.saturating_sub(shift);
1576
1577 let abs_x1 = max(0, squish_x1) as u16;
1578 let abs_x2 = max(0, squish_x2) as u16;
1579
1580 let v_area = Rect::new(
1581 state.table_area.x + abs_x1,
1582 state.table_area.y,
1583 abs_x2 - abs_x1,
1584 state.table_area.height,
1585 );
1586 state
1587 .column_areas
1588 .push(v_area.intersection(state.table_area));
1589
1590 col += 1;
1591 }
1592 }
1593
1594 #[expect(clippy::collapsible_else_if)]
1595 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1596 if let Some(style) = style {
1597 if let Some(focus_style) = self.focus_style {
1598 if focus && show {
1599 Some(style.patch(focus_style))
1600 } else {
1601 Some(fallback_select_style(style))
1602 }
1603 } else {
1604 if focus && show {
1605 Some(revert_style(style))
1606 } else {
1607 Some(fallback_select_style(style))
1608 }
1609 }
1610 } else {
1611 None
1612 }
1613 }
1614}
1615
1616impl Default for TableStyle {
1617 fn default() -> Self {
1618 Self {
1619 style: Default::default(),
1620 header: None,
1621 footer: None,
1622 select_row: None,
1623 select_column: None,
1624 select_cell: None,
1625 select_header: None,
1626 select_footer: None,
1627 show_row_focus: true, show_column_focus: false,
1629 show_cell_focus: false,
1630 show_header_focus: false,
1631 show_footer_focus: false,
1632 focus_style: None,
1633 block: None,
1634 border_style: None,
1635 title_style: None,
1636 scroll: None,
1637 non_exhaustive: NonExhaustive,
1638 }
1639 }
1640}
1641
1642impl<Selection: Clone> Clone for TableState<Selection> {
1643 fn clone(&self) -> Self {
1644 Self {
1645 focus: self.focus.new_instance(),
1646 area: self.area,
1647 inner: self.inner,
1648 header_area: self.header_area,
1649 table_area: self.table_area,
1650 row_areas: self.row_areas.clone(),
1651 column_areas: self.column_areas.clone(),
1652 column_layout: self.column_layout.clone(),
1653 footer_area: self.footer_area,
1654 rows: self.rows,
1655 _counted_rows: self._counted_rows,
1656 columns: self.columns,
1657 vscroll: self.vscroll.clone(),
1658 hscroll: self.hscroll.clone(),
1659 selection: self.selection.clone(),
1660 mouse: Default::default(),
1661 non_exhaustive: NonExhaustive,
1662 }
1663 }
1664}
1665
1666impl<Selection: Default> Default for TableState<Selection> {
1667 fn default() -> Self {
1668 Self {
1669 focus: Default::default(),
1670 area: Default::default(),
1671 inner: Default::default(),
1672 header_area: Default::default(),
1673 table_area: Default::default(),
1674 row_areas: Default::default(),
1675 column_areas: Default::default(),
1676 column_layout: Default::default(),
1677 footer_area: Default::default(),
1678 rows: Default::default(),
1679 _counted_rows: Default::default(),
1680 columns: Default::default(),
1681 vscroll: Default::default(),
1682 hscroll: Default::default(),
1683 selection: Default::default(),
1684 mouse: Default::default(),
1685 non_exhaustive: NonExhaustive,
1686 }
1687 }
1688}
1689
1690impl<Selection> HasFocus for TableState<Selection> {
1691 fn build(&self, builder: &mut FocusBuilder) {
1692 builder.leaf_widget(self);
1693 }
1694
1695 #[inline]
1696 fn focus(&self) -> FocusFlag {
1697 self.focus.clone()
1698 }
1699
1700 #[inline]
1701 fn area(&self) -> Rect {
1702 self.area
1703 }
1704}
1705
1706impl<Selection> HasScreenCursor for TableState<Selection> {
1707 fn screen_cursor(&self) -> Option<(u16, u16)> {
1708 None
1709 }
1710}
1711
1712impl<Selection> RelocatableState for TableState<Selection> {
1713 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1714 self.area.relocate(shift, clip);
1715 self.inner.relocate(shift, clip);
1716 self.header_area.relocate(shift, clip);
1717 self.table_area.relocate(shift, clip);
1718 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1719 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1720 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1721 self.footer_area.relocate(shift, clip);
1722 self.hscroll.relocate(shift, clip);
1723 self.vscroll.relocate(shift, clip);
1724 }
1725}
1726
1727impl<Selection> TableState<Selection> {
1728 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1729 let mut sum_heights = 0;
1730 let mut n_rows = 0;
1731 while let Some(h) = row_heights.pop() {
1732 sum_heights += h;
1733 n_rows += 1;
1734 if sum_heights >= self.table_area.height {
1735 break;
1736 }
1737 }
1738
1739 if sum_heights < self.table_area.height {
1740 None
1741 } else {
1742 Some(n_rows)
1743 }
1744 }
1745}
1746
1747impl<Selection> TableState<Selection>
1749where
1750 Selection: Default,
1751{
1752 pub fn new() -> Self {
1753 Self::default()
1754 }
1755
1756 pub fn named(name: &str) -> Self {
1757 Self {
1758 focus: FocusFlag::new().with_name(name),
1759 ..TableState::default()
1760 }
1761 }
1762}
1763
1764impl<Selection> TableState<Selection> {
1766 #[inline]
1768 pub fn rows(&self) -> usize {
1769 self.rows
1770 }
1771
1772 pub fn rows_changed(&mut self, rows: usize) {
1784 self.rows = rows;
1785 self.vscroll
1786 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1787 }
1788
1789 #[inline]
1791 pub fn columns(&self) -> usize {
1792 self.columns
1793 }
1794}
1795
1796impl<Selection> TableState<Selection> {
1798 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1804 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1805 return None;
1806 }
1807
1808 let mut areas = Vec::new();
1809
1810 let r = self.row_areas[row - self.vscroll.offset()];
1811 for c in &self.column_areas {
1812 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1813 }
1814
1815 Some((r, areas))
1816 }
1817
1818 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1820 let col = self.column_at_clicked(pos);
1821 let row = self.row_at_clicked(pos);
1822
1823 match (col, row) {
1824 (Some(col), Some(row)) => Some((col, row)),
1825 _ => None,
1826 }
1827 }
1828
1829 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1831 self.mouse.column_at(&self.column_areas, pos.0)
1832 }
1833
1834 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1836 self.mouse
1837 .row_at(&self.row_areas, pos.1)
1838 .map(|v| self.vscroll.offset() + v)
1839 }
1840
1841 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1844 let col = self.column_at_drag(pos);
1845 let row = self.row_at_drag(pos);
1846
1847 (col, row)
1848 }
1849
1850 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1857 match self
1858 .mouse
1859 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1860 {
1861 Ok(v) => self.vscroll.offset() + v,
1862 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1863 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1864 }
1865 }
1866
1867 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1871 match self
1872 .mouse
1873 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1874 {
1875 Ok(v) => v,
1876 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1877 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1878 }
1879 }
1880}
1881
1882impl<Selection: TableSelection> TableState<Selection> {
1884 pub fn clear_offset(&mut self) {
1886 self.vscroll.set_offset(0);
1887 self.hscroll.set_offset(0);
1888 }
1889
1890 pub fn row_max_offset(&self) -> usize {
1895 self.vscroll.max_offset()
1896 }
1897
1898 pub fn row_offset(&self) -> usize {
1900 self.vscroll.offset()
1901 }
1902
1903 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1910 self.vscroll.set_offset(offset)
1911 }
1912
1913 pub fn page_len(&self) -> usize {
1915 self.vscroll.page_len()
1916 }
1917
1918 pub fn row_scroll_by(&self) -> usize {
1920 self.vscroll.scroll_by()
1921 }
1922
1923 pub fn x_max_offset(&self) -> usize {
1928 self.hscroll.max_offset()
1929 }
1930
1931 pub fn x_offset(&self) -> usize {
1933 self.hscroll.offset()
1934 }
1935
1936 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1943 self.hscroll.set_offset(offset)
1944 }
1945
1946 pub fn page_width(&self) -> usize {
1948 self.hscroll.page_len()
1949 }
1950
1951 pub fn x_scroll_by(&self) -> usize {
1953 self.hscroll.scroll_by()
1954 }
1955
1956 pub fn scroll_to_selected(&mut self) -> bool {
1960 if let Some(selected) = self.selection.lead_selection() {
1961 let c = self.scroll_to_col(selected.0);
1962 let r = self.scroll_to_row(selected.1);
1963 r || c
1964 } else {
1965 false
1966 }
1967 }
1968
1969 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1974 if pos >= self.rows {
1975 false
1976 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1977 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1979 if heights < self.table_area.height {
1980 false
1981 } else {
1982 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1983 }
1984 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1985 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1986 } else if pos < self.row_offset() {
1987 self.set_row_offset(pos)
1988 } else {
1989 false
1990 }
1991 }
1992
1993 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1995 if let Some(col) = self.column_layout.get(pos) {
1996 if (col.left() as usize) < self.x_offset() {
1997 self.set_x_offset(col.x as usize)
1998 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1999 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2000 } else {
2001 false
2002 }
2003 } else {
2004 false
2005 }
2006 }
2007
2008 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2010 if pos >= self.x_offset().saturating_add(self.page_width()) {
2011 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2012 } else if pos < self.x_offset() {
2013 self.set_x_offset(pos)
2014 } else {
2015 false
2016 }
2017 }
2018
2019 pub fn scroll_up(&mut self, n: usize) -> bool {
2021 self.vscroll.scroll_up(n)
2022 }
2023
2024 pub fn scroll_down(&mut self, n: usize) -> bool {
2026 self.vscroll.scroll_down(n)
2027 }
2028
2029 pub fn scroll_left(&mut self, n: usize) -> bool {
2031 self.hscroll.scroll_left(n)
2032 }
2033
2034 pub fn scroll_right(&mut self, n: usize) -> bool {
2036 self.hscroll.scroll_right(n)
2037 }
2038}
2039
2040impl TableState<RowSelection> {
2041 pub fn items_added(&mut self, pos: usize, n: usize) {
2045 self.vscroll.items_added(pos, n);
2046 self.selection.items_added(pos, n);
2047 self.rows += n;
2048 }
2049
2050 pub fn items_removed(&mut self, pos: usize, n: usize) {
2054 self.vscroll.items_removed(pos, n);
2055 self.selection
2056 .items_removed(pos, n, self.rows.saturating_sub(1));
2057 self.rows -= n;
2058 }
2059
2060 #[inline]
2063 pub fn set_scroll_selection(&mut self, scroll: bool) {
2064 self.selection.set_scroll_selected(scroll);
2065 }
2066
2067 #[inline]
2069 pub fn clear_selection(&mut self) {
2070 self.selection.clear();
2071 }
2072
2073 #[inline]
2075 pub fn has_selection(&mut self) -> bool {
2076 self.selection.has_selection()
2077 }
2078
2079 #[inline]
2082 pub fn selected(&self) -> Option<usize> {
2083 self.selection.selected()
2084 }
2085
2086 #[inline]
2089 #[allow(clippy::manual_filter)]
2090 pub fn selected_checked(&self) -> Option<usize> {
2091 if let Some(selected) = self.selection.selected() {
2092 if selected < self.rows {
2093 Some(selected)
2094 } else {
2095 None
2096 }
2097 } else {
2098 None
2099 }
2100 }
2101
2102 #[inline]
2105 pub fn select(&mut self, row: Option<usize>) -> bool {
2106 self.selection.select(row)
2107 }
2108
2109 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2113 if self.vscroll.max_offset() > 0 {
2114 (self.rows * offset) / self.vscroll.max_offset()
2115 } else {
2116 0 }
2118 }
2119
2120 #[inline]
2123 pub fn move_to(&mut self, row: usize) -> bool {
2124 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2125 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2126 r || s
2127 }
2128
2129 #[inline]
2132 pub fn move_up(&mut self, n: usize) -> bool {
2133 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2134 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2135 r || s
2136 }
2137
2138 #[inline]
2141 pub fn move_down(&mut self, n: usize) -> bool {
2142 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2143 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2144 r || s
2145 }
2146}
2147
2148impl TableState<RowSetSelection> {
2149 #[inline]
2151 pub fn clear_selection(&mut self) {
2152 self.selection.clear();
2153 }
2154
2155 #[inline]
2157 pub fn has_selection(&mut self) -> bool {
2158 self.selection.has_selection()
2159 }
2160
2161 #[inline]
2163 pub fn selected(&self) -> HashSet<usize> {
2164 self.selection.selected()
2165 }
2166
2167 #[inline]
2174 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2175 self.selection.set_lead(row, extend)
2176 }
2177
2178 #[inline]
2180 pub fn lead(&self) -> Option<usize> {
2181 self.selection.lead()
2182 }
2183
2184 #[inline]
2186 pub fn anchor(&self) -> Option<usize> {
2187 self.selection.anchor()
2188 }
2189
2190 #[inline]
2193 pub fn retire_selection(&mut self) {
2194 self.selection.retire_selection();
2195 }
2196
2197 #[inline]
2202 pub fn add_selected(&mut self, idx: usize) {
2203 self.selection.add(idx);
2204 }
2205
2206 #[inline]
2211 pub fn remove_selected(&mut self, idx: usize) {
2212 self.selection.remove(idx);
2213 }
2214
2215 #[inline]
2218 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2219 let r = self
2220 .selection
2221 .move_to(row, self.rows.saturating_sub(1), extend);
2222 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2223 r || s
2224 }
2225
2226 #[inline]
2229 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2230 let r = self
2231 .selection
2232 .move_up(n, self.rows.saturating_sub(1), extend);
2233 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2234 r || s
2235 }
2236
2237 #[inline]
2240 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2241 let r = self
2242 .selection
2243 .move_down(n, self.rows.saturating_sub(1), extend);
2244 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2245 r || s
2246 }
2247}
2248
2249impl TableState<CellSelection> {
2250 #[inline]
2251 pub fn clear_selection(&mut self) {
2252 self.selection.clear();
2253 }
2254
2255 #[inline]
2256 pub fn has_selection(&mut self) -> bool {
2257 self.selection.has_selection()
2258 }
2259
2260 #[inline]
2262 pub fn selected(&self) -> Option<(usize, usize)> {
2263 self.selection.selected()
2264 }
2265
2266 #[inline]
2268 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2269 self.selection.select_cell(select)
2270 }
2271
2272 #[inline]
2274 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2275 if let Some(row) = row {
2276 self.selection
2277 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2278 } else {
2279 self.selection.select_row(None)
2280 }
2281 }
2282
2283 #[inline]
2285 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2286 if let Some(column) = column {
2287 self.selection
2288 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2289 } else {
2290 self.selection.select_column(None)
2291 }
2292 }
2293
2294 #[inline]
2296 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2297 let r = self.selection.move_to(
2298 select,
2299 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2300 );
2301 let s = self.scroll_to_selected();
2302 r || s
2303 }
2304
2305 #[inline]
2307 pub fn move_to_row(&mut self, row: usize) -> bool {
2308 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2309 let s = self.scroll_to_selected();
2310 r || s
2311 }
2312
2313 #[inline]
2315 pub fn move_to_col(&mut self, col: usize) -> bool {
2316 let r = self
2317 .selection
2318 .move_to_col(col, self.columns.saturating_sub(1));
2319 let s = self.scroll_to_selected();
2320 r || s
2321 }
2322
2323 #[inline]
2326 pub fn move_up(&mut self, n: usize) -> bool {
2327 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2328 let s = self.scroll_to_selected();
2329 r || s
2330 }
2331
2332 #[inline]
2335 pub fn move_down(&mut self, n: usize) -> bool {
2336 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2337 let s = self.scroll_to_selected();
2338 r || s
2339 }
2340
2341 #[inline]
2344 pub fn move_left(&mut self, n: usize) -> bool {
2345 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2346 let s = self.scroll_to_selected();
2347 r || s
2348 }
2349
2350 #[inline]
2353 pub fn move_right(&mut self, n: usize) -> bool {
2354 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2355 let s = self.scroll_to_selected();
2356 r || s
2357 }
2358}
2359
2360impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2361 for TableState<Selection>
2362{
2363 fn handle(
2365 &mut self,
2366 event: &crossterm::event::Event,
2367 _keymap: DoubleClick,
2368 ) -> DoubleClickOutcome {
2369 match event {
2370 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2371 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2372 DoubleClickOutcome::ClickClick(col, row)
2373 } else {
2374 DoubleClickOutcome::Continue
2375 }
2376 }
2377 _ => DoubleClickOutcome::Continue,
2378 }
2379 }
2380}
2381
2382pub fn handle_doubleclick_events<Selection: TableSelection>(
2384 state: &mut TableState<Selection>,
2385 event: &crossterm::event::Event,
2386) -> DoubleClickOutcome {
2387 state.handle(event, DoubleClick)
2388}