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_event::util::MouseFlags;
11use rat_event::{ct_event, HandleEvent};
12use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
13use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
14use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
15use ratatui::buffer::Buffer;
16use ratatui::layout::{Constraint, Flex, Layout, Rect};
17use ratatui::style::Style;
18use ratatui::widgets::{Block, StatefulWidget, Widget};
19use std::cmp::{max, min};
20use std::collections::HashSet;
21use std::fmt::Debug;
22use std::marker::PhantomData;
23use std::mem;
24use std::rc::Rc;
25
26#[derive(Debug)]
41pub struct Table<'a, Selection> {
42 data: DataRepr<'a>,
43 no_row_count: bool,
44
45 header: Option<Row<'a>>,
46 footer: Option<Row<'a>>,
47
48 widths: Vec<Constraint>,
49 flex: Flex,
50 column_spacing: u16,
51 layout_width: Option<u16>,
52 auto_layout_width: bool,
53
54 block: Option<Block<'a>>,
55 hscroll: Option<Scroll<'a>>,
56 vscroll: Option<Scroll<'a>>,
57
58 header_style: Option<Style>,
59 footer_style: Option<Style>,
60 style: Style,
61
62 auto_styles: bool,
63 select_row_style: Option<Style>,
64 show_row_focus: bool,
65 select_column_style: Option<Style>,
66 show_column_focus: bool,
67 select_cell_style: Option<Style>,
68 show_cell_focus: bool,
69 select_header_style: Option<Style>,
70 show_header_focus: bool,
71 select_footer_style: Option<Style>,
72 show_footer_focus: bool,
73
74 focus_style: Option<Style>,
75
76 debug: bool,
77
78 _phantom: PhantomData<Selection>,
79}
80
81mod data {
82 use crate::textdata::TextTableData;
83 use crate::{TableContext, TableData, TableDataIter};
84 #[cfg(debug_assertions)]
85 use log::warn;
86 use ratatui::buffer::Buffer;
87 use ratatui::layout::Rect;
88 use ratatui::style::{Style, Stylize};
89 use std::fmt::{Debug, Formatter};
90
91 #[derive(Default)]
92 pub(super) enum DataRepr<'a> {
93 #[default]
94 None,
95 Text(TextTableData<'a>),
96 Data(Box<dyn TableData<'a> + 'a>),
97 Iter(Box<dyn TableDataIter<'a> + 'a>),
98 }
99
100 impl<'a> DataRepr<'a> {
101 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
102 match self {
103 DataRepr::None => DataReprIter::None,
104 DataRepr::Text(v) => DataReprIter::IterText(v, None),
105 DataRepr::Data(v) => DataReprIter::IterData(v, None),
106 DataRepr::Iter(v) => DataReprIter::IterIter(v),
107 }
108 }
109
110 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
111 match self {
112 DataRepr::None => DataReprIter::None,
113 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
114 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
115 DataRepr::Iter(v) => {
116 if let Some(v) = v.cloned() {
118 DataReprIter::IterIter(v)
119 } else {
120 DataReprIter::Invalid(None)
121 }
122 }
123 }
124 }
125 }
126
127 impl Debug for DataRepr<'_> {
128 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("Data").finish()
130 }
131 }
132
133 #[derive(Default)]
134 pub(super) enum DataReprIter<'a, 'b> {
135 #[default]
136 None,
137 #[allow(dead_code)]
138 Invalid(Option<usize>),
139 IterText(TextTableData<'a>, Option<usize>),
140 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
141 #[allow(dead_code)]
142 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
143 IterIter(Box<dyn TableDataIter<'a> + 'a>),
144 }
145
146 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
147 fn rows(&self) -> Option<usize> {
148 match self {
149 DataReprIter::None => Some(0),
150 DataReprIter::Invalid(_) => Some(1),
151 DataReprIter::IterText(v, _) => Some(v.rows.len()),
152 DataReprIter::IterData(v, _) => Some(v.rows()),
153 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
154 DataReprIter::IterIter(v) => v.rows(),
155 }
156 }
157
158 fn nth(&mut self, n: usize) -> bool {
159 let incr = |row: &mut Option<usize>, rows: usize| match *row {
160 None => {
161 *row = Some(n);
162 *row < Some(rows)
163 }
164 Some(w) => {
165 *row = Some(w.saturating_add(n).saturating_add(1));
166 *row < Some(rows)
167 }
168 };
169
170 match self {
171 DataReprIter::None => false,
172 DataReprIter::Invalid(row) => incr(row, 1),
173 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
174 DataReprIter::IterData(v, row) => incr(row, v.rows()),
175 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
176 DataReprIter::IterIter(v) => v.nth(n),
177 }
178 }
179
180 fn row_height(&self) -> u16 {
182 match self {
183 DataReprIter::None => 1,
184 DataReprIter::Invalid(_) => 1,
185 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
186 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
187 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
188 DataReprIter::IterIter(v) => v.row_height(),
189 }
190 }
191
192 fn row_style(&self) -> Option<Style> {
193 match self {
194 DataReprIter::None => None,
195 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
196 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
197 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
198 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
199 DataReprIter::IterIter(v) => v.row_style(),
200 }
201 }
202
203 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
205 match self {
206 DataReprIter::None => {}
207 DataReprIter::Invalid(_) => {
208 if column == 0 {
209 #[cfg(debug_assertions)]
210 warn!("Table::render_ref - TableDataIter must implement a valid cloned() for this to work.");
211
212 buf.set_string(
213 area.x,
214 area.y,
215 "TableDataIter must implement a valid cloned() for this",
216 Style::default(),
217 );
218 }
219 }
220 DataReprIter::IterText(v, n) => {
221 v.render_cell(ctx, column, n.expect("row"), area, buf)
222 }
223 DataReprIter::IterData(v, n) => {
224 v.render_cell(ctx, column, n.expect("row"), area, buf)
225 }
226 DataReprIter::IterDataRef(v, n) => {
227 v.render_cell(ctx, column, n.expect("row"), area, buf)
228 }
229 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
230 }
231 }
232 }
233}
234
235#[derive(Debug)]
237pub struct TableStyle {
238 pub style: Style,
239 pub header: Option<Style>,
240 pub footer: Option<Style>,
241
242 pub select_row: Option<Style>,
243 pub select_column: Option<Style>,
244 pub select_cell: Option<Style>,
245 pub select_header: Option<Style>,
246 pub select_footer: Option<Style>,
247
248 pub show_row_focus: bool,
249 pub show_column_focus: bool,
250 pub show_cell_focus: bool,
251 pub show_header_focus: bool,
252 pub show_footer_focus: bool,
253
254 pub focus_style: Option<Style>,
255
256 pub block: Option<Block<'static>>,
257 pub border_style: Option<Style>,
258 pub scroll: Option<ScrollStyle>,
259
260 pub non_exhaustive: NonExhaustive,
261}
262
263#[derive(Debug)]
265pub struct TableState<Selection> {
266 pub focus: FocusFlag,
269
270 pub area: Rect,
273 pub inner: Rect,
276
277 pub header_area: Rect,
280 pub table_area: Rect,
283 pub row_areas: Vec<Rect>,
286 pub column_areas: Vec<Rect>,
290 pub column_layout: Vec<Rect>,
294 pub footer_area: Rect,
297
298 pub rows: usize,
301 pub _counted_rows: usize,
303 pub columns: usize,
306
307 pub vscroll: ScrollState,
310 pub hscroll: ScrollState,
313
314 pub selection: Selection,
317
318 pub mouse: MouseFlags,
320
321 pub non_exhaustive: NonExhaustive,
322}
323
324impl<Selection> Default for Table<'_, Selection> {
325 fn default() -> Self {
326 Self {
327 data: Default::default(),
328 no_row_count: Default::default(),
329 header: Default::default(),
330 footer: Default::default(),
331 widths: Default::default(),
332 flex: Default::default(),
333 column_spacing: Default::default(),
334 layout_width: Default::default(),
335 auto_layout_width: Default::default(),
336 block: Default::default(),
337 hscroll: Default::default(),
338 vscroll: Default::default(),
339 header_style: Default::default(),
340 footer_style: Default::default(),
341 style: Default::default(),
342 auto_styles: true,
343 select_row_style: Default::default(),
344 show_row_focus: true,
345 select_column_style: Default::default(),
346 show_column_focus: Default::default(),
347 select_cell_style: Default::default(),
348 show_cell_focus: Default::default(),
349 select_header_style: Default::default(),
350 show_header_focus: Default::default(),
351 select_footer_style: Default::default(),
352 show_footer_focus: Default::default(),
353 focus_style: Default::default(),
354 debug: Default::default(),
355 _phantom: Default::default(),
356 }
357 }
358}
359
360impl<'a, Selection> Table<'a, Selection> {
361 pub fn new() -> Self
363 where
364 Selection: Default,
365 {
366 Self::default()
367 }
368
369 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
374 where
375 R: IntoIterator,
376 R::Item: Into<Row<'a>>,
377 C: IntoIterator,
378 C::Item: Into<Constraint>,
379 Selection: Default,
380 {
381 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
382 let data = TextTableData {
383 rows: rows.into_iter().map(|v| v.into()).collect(),
384 };
385 Self {
386 data: DataRepr::Text(data),
387 widths,
388 ..Default::default()
389 }
390 }
391
392 pub fn rows<T>(mut self, rows: T) -> Self
396 where
397 T: IntoIterator<Item = Row<'a>>,
398 {
399 let rows = rows.into_iter().collect();
400 self.data = DataRepr::Text(TextTableData { rows });
401 self
402 }
403
404 #[inline]
471 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
472 self.widths = data.widths();
473 self.header = data.header();
474 self.footer = data.footer();
475 self.data = DataRepr::Data(Box::new(data));
476 self
477 }
478
479 #[inline]
588 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
589 #[cfg(debug_assertions)]
590 if data.rows().is_none() {
591 use log::warn;
592 warn!("Table::iter - rows is None, this will be slower");
593 }
594 self.header = data.header();
595 self.footer = data.footer();
596 self.widths = data.widths();
597 self.data = DataRepr::Iter(Box::new(data));
598 self
599 }
600
601 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
619 self.no_row_count = no_row_count;
620 self
621 }
622
623 #[inline]
625 pub fn header(mut self, header: Row<'a>) -> Self {
626 self.header = Some(header);
627 self
628 }
629
630 #[inline]
632 pub fn footer(mut self, footer: Row<'a>) -> Self {
633 self.footer = Some(footer);
634 self
635 }
636
637 pub fn widths<I>(mut self, widths: I) -> Self
639 where
640 I: IntoIterator,
641 I::Item: Into<Constraint>,
642 {
643 self.widths = widths.into_iter().map(|v| v.into()).collect();
644 self
645 }
646
647 #[inline]
649 pub fn flex(mut self, flex: Flex) -> Self {
650 self.flex = flex;
651 self
652 }
653
654 #[inline]
656 pub fn column_spacing(mut self, spacing: u16) -> Self {
657 self.column_spacing = spacing;
658 self
659 }
660
661 #[inline]
665 pub fn layout_width(mut self, width: u16) -> Self {
666 self.layout_width = Some(width);
667 self
668 }
669
670 #[inline]
677 pub fn auto_layout_width(mut self) -> Self {
678 self.auto_layout_width = true;
679 self
680 }
681
682 #[inline]
684 pub fn block(mut self, block: Block<'a>) -> Self {
685 self.block = Some(block);
686 self.block = self.block.map(|v| v.style(self.style));
687 self
688 }
689
690 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
692 self.hscroll = Some(scroll.clone().override_horizontal());
693 self.vscroll = Some(scroll.override_vertical());
694 self
695 }
696
697 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
699 self.hscroll = Some(scroll.override_horizontal());
700 self
701 }
702
703 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
705 self.vscroll = Some(scroll.override_vertical());
706 self
707 }
708
709 #[inline]
711 pub fn styles(mut self, styles: TableStyle) -> Self {
712 self.style = styles.style;
713 if styles.header.is_some() {
714 self.header_style = styles.header;
715 }
716 if styles.footer.is_some() {
717 self.footer_style = styles.footer;
718 }
719 if styles.select_row.is_some() {
720 self.select_row_style = styles.select_row;
721 }
722 self.show_row_focus = styles.show_row_focus;
723 if styles.select_column.is_some() {
724 self.select_column_style = styles.select_column;
725 }
726 self.show_column_focus = styles.show_column_focus;
727 if styles.select_cell.is_some() {
728 self.select_cell_style = styles.select_cell;
729 }
730 self.show_cell_focus = styles.show_cell_focus;
731 if styles.select_header.is_some() {
732 self.select_header_style = styles.select_header;
733 }
734 self.show_header_focus = styles.show_header_focus;
735 if styles.select_footer.is_some() {
736 self.select_footer_style = styles.select_footer;
737 }
738 self.show_footer_focus = styles.show_footer_focus;
739 if styles.focus_style.is_some() {
740 self.focus_style = styles.focus_style;
741 }
742 if let Some(border_style) = styles.border_style {
743 self.block = self.block.map(|v| v.border_style(border_style));
744 }
745 self.block = self.block.map(|v| v.style(self.style));
746 if styles.block.is_some() {
747 self.block = styles.block;
748 }
749 if let Some(styles) = styles.scroll {
750 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
751 self.vscroll = self.vscroll.map(|v| v.styles(styles));
752 }
753 self
754 }
755
756 #[inline]
758 pub fn style(mut self, style: Style) -> Self {
759 self.style = style;
760 self.block = self.block.map(|v| v.style(self.style));
761 self
762 }
763
764 #[inline]
766 pub fn header_style(mut self, style: Option<Style>) -> Self {
767 self.header_style = style;
768 self
769 }
770
771 #[inline]
773 pub fn footer_style(mut self, style: Option<Style>) -> Self {
774 self.footer_style = style;
775 self
776 }
777
778 #[inline]
784 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
785 self.auto_styles = auto_styles;
786 self
787 }
788
789 #[inline]
792 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
793 self.select_row_style = select_style;
794 self
795 }
796
797 #[inline]
799 pub fn show_row_focus(mut self, show: bool) -> Self {
800 self.show_row_focus = show;
801 self
802 }
803
804 #[inline]
807 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
808 self.select_column_style = select_style;
809 self
810 }
811
812 #[inline]
814 pub fn show_column_focus(mut self, show: bool) -> Self {
815 self.show_column_focus = show;
816 self
817 }
818
819 #[inline]
822 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
823 self.select_cell_style = select_style;
824 self
825 }
826
827 #[inline]
829 pub fn show_cell_focus(mut self, show: bool) -> Self {
830 self.show_cell_focus = show;
831 self
832 }
833
834 #[inline]
837 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
838 self.select_header_style = select_style;
839 self
840 }
841
842 #[inline]
844 pub fn show_header_focus(mut self, show: bool) -> Self {
845 self.show_header_focus = show;
846 self
847 }
848
849 #[inline]
852 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
853 self.select_footer_style = select_style;
854 self
855 }
856
857 #[inline]
859 pub fn show_footer_focus(mut self, show: bool) -> Self {
860 self.show_footer_focus = show;
861 self
862 }
863
864 #[inline]
870 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
871 self.focus_style = focus_style;
872 self
873 }
874
875 pub fn debug(mut self, debug: bool) -> Self {
877 self.debug = debug;
878 self
879 }
880}
881
882impl<Selection> Table<'_, Selection> {
883 #[inline]
885 fn total_width(&self, area_width: u16) -> u16 {
886 if let Some(layout_width) = self.layout_width {
887 layout_width
888 } else if self.auto_layout_width {
889 let mut width = 0;
890 for w in &self.widths {
891 match w {
892 Constraint::Min(v) => width += *v + self.column_spacing,
893 Constraint::Max(v) => width += *v + self.column_spacing,
894 Constraint::Length(v) => width += *v + self.column_spacing,
895 _ => unimplemented!("Invalid layout constraint."),
896 }
897 }
898 width
899 } else {
900 area_width
901 }
902 }
903
904 #[inline]
906 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
907 let width = self.total_width(width);
908 let area = Rect::new(0, 0, width, 0);
909
910 let (layout, spacers) = Layout::horizontal(&self.widths)
911 .flex(self.flex)
912 .spacing(self.column_spacing)
913 .split_with_spacers(area);
914
915 (width, layout, spacers)
916 }
917
918 #[inline]
920 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
921 let heights = vec![
922 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
923 Constraint::Fill(1),
924 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
925 ];
926
927 Layout::vertical(heights).split(area)
928 }
929}
930
931impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
932where
933 Selection: TableSelection,
934{
935 type State = TableState<Selection>;
936
937 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
938 let iter = (&self.data).iter();
939 self.render_iter(iter, area, buf, state);
940 }
941}
942
943impl<Selection> StatefulWidget for Table<'_, Selection>
944where
945 Selection: TableSelection,
946{
947 type State = TableState<Selection>;
948
949 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
950 let iter = mem::take(&mut self.data).into_iter();
951 self.render_iter(iter, area, buf, state);
952 }
953}
954
955impl<'a, Selection> Table<'a, Selection>
956where
957 Selection: TableSelection,
958{
959 fn render_iter<'b>(
964 &self,
965 mut data: DataReprIter<'a, 'b>,
966 area: Rect,
967 buf: &mut Buffer,
968 state: &mut TableState<Selection>,
969 ) {
970 if let Some(rows) = data.rows() {
971 state.rows = rows;
972 }
973 state.columns = self.widths.len();
974 state.area = area;
975
976 let sa = ScrollArea::new()
977 .style(self.style)
978 .block(self.block.as_ref())
979 .h_scroll(self.hscroll.as_ref())
980 .v_scroll(self.vscroll.as_ref());
981 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
982
983 let l_rows = self.layout_areas(state.inner);
984 state.header_area = l_rows[0];
985 state.table_area = l_rows[1];
986 state.footer_area = l_rows[2];
987
988 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
990 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
991
992 sa.render(
994 area,
995 buf,
996 &mut ScrollAreaState::new()
997 .h_scroll(&mut state.hscroll)
998 .v_scroll(&mut state.vscroll),
999 );
1000
1001 self.render_header(
1003 state.columns,
1004 width,
1005 l_columns.as_ref(),
1006 l_spacers.as_ref(),
1007 state.header_area,
1008 buf,
1009 state,
1010 );
1011 self.render_footer(
1012 state.columns,
1013 width,
1014 l_columns.as_ref(),
1015 l_spacers.as_ref(),
1016 state.footer_area,
1017 buf,
1018 state,
1019 );
1020
1021 state.row_areas.clear();
1023 state.vscroll.set_page_len(0);
1024 state.hscroll.set_page_len(area.width as usize);
1025
1026 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1027 let mut row = None;
1028 let mut row_y = state.table_area.y;
1029 let mut row_heights = Vec::new();
1030 #[cfg(debug_assertions)]
1031 let mut insane_offset = false;
1032
1033 let mut ctx = TableContext {
1034 focus: state.focus.get(),
1035 selected_cell: false,
1036 selected_row: false,
1037 selected_column: false,
1038 style: self.style,
1039 row_style: None,
1040 select_style: None,
1041 space_area: Default::default(),
1042 row_area: Default::default(),
1043 non_exhaustive: NonExhaustive,
1044 };
1045
1046 if data.nth(state.vscroll.offset()) {
1047 row = Some(state.vscroll.offset());
1048 loop {
1049 ctx.row_style = data.row_style();
1050 let render_row_area = Rect::new(0, 0, width, data.row_height());
1054 ctx.row_area = render_row_area;
1055 row_buf.resize(render_row_area);
1056 if self.auto_styles {
1057 if let Some(row_style) = ctx.row_style {
1058 row_buf.set_style(render_row_area, row_style);
1059 } else {
1060 row_buf.set_style(render_row_area, self.style);
1061 }
1062 }
1063 row_heights.push(render_row_area.height);
1064
1065 let visible_row_area = Rect::new(
1067 state.table_area.x,
1068 row_y,
1069 state.table_area.width,
1070 render_row_area.height,
1071 )
1072 .intersection(state.table_area);
1073 state.row_areas.push(visible_row_area);
1074 if render_row_area.height == visible_row_area.height {
1076 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1077 }
1078
1079 if render_row_area.height > 0 {
1081 let mut col = 0;
1082 loop {
1083 if col >= state.columns {
1084 break;
1085 }
1086
1087 let render_cell_area = Rect::new(
1088 l_columns[col].x,
1089 0,
1090 l_columns[col].width,
1091 render_row_area.height,
1092 );
1093 ctx.space_area = Rect::new(
1094 l_spacers[col + 1].x,
1095 0,
1096 l_spacers[col + 1].width,
1097 render_row_area.height,
1098 );
1099
1100 if state.selection.is_selected_cell(col, row.expect("row")) {
1101 ctx.selected_cell = true;
1102 ctx.selected_row = false;
1103 ctx.selected_column = false;
1104 ctx.select_style = self.patch_select(
1105 self.select_cell_style,
1106 state.focus.get(),
1107 self.show_cell_focus,
1108 );
1109 } else if state.selection.is_selected_row(row.expect("row")) {
1110 ctx.selected_cell = false;
1111 ctx.selected_row = true;
1112 ctx.selected_column = false;
1113 ctx.select_style = if self.select_row_style.is_some() {
1115 self.patch_select(
1116 self.select_row_style,
1117 state.focus.get(),
1118 self.show_row_focus,
1119 )
1120 } else {
1121 self.patch_select(
1122 Some(self.style),
1123 state.focus.get(),
1124 self.show_row_focus,
1125 )
1126 };
1127 } else if state.selection.is_selected_column(col) {
1128 ctx.selected_cell = false;
1129 ctx.selected_row = false;
1130 ctx.selected_column = true;
1131 ctx.select_style = self.patch_select(
1132 self.select_column_style,
1133 state.focus.get(),
1134 self.show_column_focus,
1135 );
1136 } else {
1137 ctx.selected_cell = false;
1138 ctx.selected_row = false;
1139 ctx.selected_column = false;
1140 ctx.select_style = None;
1141 }
1142
1143 if render_cell_area.right() > state.hscroll.offset as u16
1145 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1146 {
1147 if self.auto_styles {
1148 if let Some(select_style) = ctx.select_style {
1149 row_buf.set_style(render_cell_area, select_style);
1150 row_buf.set_style(ctx.space_area, select_style);
1151 }
1152 }
1153 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1154 }
1155
1156 col += 1;
1157 }
1158
1159 transfer_buffer(
1161 &mut row_buf,
1162 state.hscroll.offset() as u16,
1163 visible_row_area,
1164 buf,
1165 );
1166 }
1167
1168 if visible_row_area.bottom() >= state.table_area.bottom() {
1169 break;
1170 }
1171 if !data.nth(0) {
1172 break;
1173 }
1174 row = Some(row.expect("row").saturating_add(1));
1175 row_y += render_row_area.height;
1176 }
1177 } else {
1178 if data.rows().is_none() || data.rows() == Some(0) {
1183 } else {
1185 #[cfg(debug_assertions)]
1186 {
1187 insane_offset = true;
1188 }
1189 }
1190 }
1191
1192 #[allow(unused_variables)]
1194 let algorithm;
1195 #[allow(unused_assignments)]
1196 {
1197 if let Some(rows) = data.rows() {
1198 algorithm = 0;
1199 let skip_rows = rows
1203 .saturating_sub(row.map_or(0, |v| v + 1))
1204 .saturating_sub(state.table_area.height as usize);
1205 if skip_rows > 0 {
1207 row_heights.clear();
1208 }
1209 let nth_row = skip_rows;
1210 if data.nth(nth_row) {
1212 let mut sum_height = row_heights.iter().sum::<u16>();
1213 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1214 loop {
1215 let row_height = data.row_height();
1216 row_heights.push(row_height);
1217
1218 sum_height += row_height;
1221 if sum_height
1222 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1223 > state.table_area.height
1224 {
1225 let lost_height = row_heights.remove(0);
1226 sum_height -= lost_height;
1227 }
1228
1229 if !data.nth(0) {
1230 break;
1231 }
1232
1233 row = Some(row.expect("row") + 1);
1234 if row.expect("row") > rows {
1236 break;
1237 }
1238 }
1239 while data.nth(0) {
1242 row = Some(row.expect("row") + 1);
1243 }
1244 } else {
1245 }
1248
1249 state.rows = rows;
1250 state._counted_rows = row.map_or(0, |v| v + 1);
1251
1252 if let Some(last_page) = state.calc_last_page(row_heights) {
1254 state.vscroll.set_max_offset(state.rows - last_page);
1255 } else {
1256 state.vscroll.set_max_offset(
1260 state.rows.saturating_sub(state.table_area.height as usize),
1261 );
1262 }
1263 } else if self.no_row_count {
1264 algorithm = 1;
1265
1266 if row.is_some() {
1270 if data.nth(0) {
1271 row = Some(row.expect("row").saturating_add(1));
1273 if data.nth(0) {
1274 row = Some(usize::MAX - 1);
1276 }
1277 }
1278 }
1279
1280 state.rows = row.map_or(0, |v| v + 1);
1281 state._counted_rows = row.map_or(0, |v| v + 1);
1282 state.vscroll.set_max_offset(usize::MAX - 1);
1284 if state.vscroll.page_len() == 0 {
1285 state.vscroll.set_page_len(state.table_area.height as usize);
1286 }
1287 } else {
1288 algorithm = 2;
1289
1290 let mut sum_height = row_heights.iter().sum::<u16>();
1292 while data.nth(0) {
1293 let row_height = data.row_height();
1294 row_heights.push(row_height);
1295
1296 sum_height += row_height;
1299 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1300 > state.table_area.height
1301 {
1302 let lost_height = row_heights.remove(0);
1303 sum_height -= lost_height;
1304 }
1305 row = Some(row.map_or(0, |v| v + 1));
1306 }
1307
1308 state.rows = row.map_or(0, |v| v + 1);
1309 state._counted_rows = row.map_or(0, |v| v + 1);
1310
1311 if let Some(last_page) = state.calc_last_page(row_heights) {
1313 state.vscroll.set_max_offset(state.rows - last_page);
1314 } else {
1315 state.vscroll.set_max_offset(0);
1316 }
1317 }
1318 }
1319 {
1320 state
1321 .hscroll
1322 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1323 }
1324
1325 #[cfg(debug_assertions)]
1326 {
1327 use std::fmt::Write;
1328 let mut msg = String::new();
1329 if insane_offset {
1330 _= write!(msg,
1331 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1332 state.vscroll.offset(), state.rows, state._counted_rows, algorithm
1333 );
1334 }
1335 if state.rows != state._counted_rows {
1336 _ = write!(
1337 msg,
1338 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1339 state.rows, state._counted_rows, algorithm
1340 );
1341 }
1342 if !msg.is_empty() {
1343 use log::warn;
1344 use ratatui::style::Stylize;
1345 use ratatui::text::Text;
1346
1347 warn!("{}", &msg);
1348 Text::from(msg)
1349 .white()
1350 .on_red()
1351 .render(state.table_area, buf);
1352 }
1353 }
1354 }
1355
1356 #[allow(clippy::too_many_arguments)]
1357 fn render_footer(
1358 &self,
1359 columns: usize,
1360 width: u16,
1361 l_columns: &[Rect],
1362 l_spacers: &[Rect],
1363 area: Rect,
1364 buf: &mut Buffer,
1365 state: &mut TableState<Selection>,
1366 ) {
1367 if let Some(footer) = &self.footer {
1368 let render_row_area = Rect::new(0, 0, width, footer.height);
1369 let mut row_buf = Buffer::empty(render_row_area);
1370
1371 row_buf.set_style(render_row_area, self.style);
1372 if let Some(footer_style) = footer.style {
1373 row_buf.set_style(render_row_area, footer_style);
1374 } else if let Some(footer_style) = self.footer_style {
1375 row_buf.set_style(render_row_area, footer_style);
1376 }
1377
1378 let mut col = 0;
1379 loop {
1380 if col >= columns {
1381 break;
1382 }
1383
1384 let render_cell_area =
1385 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1386 let render_space_area = Rect::new(
1387 l_spacers[col + 1].x,
1388 0,
1389 l_spacers[col + 1].width,
1390 area.height,
1391 );
1392
1393 if state.selection.is_selected_column(col) {
1394 if let Some(selected_style) = self.patch_select(
1395 self.select_footer_style,
1396 state.focus.get(),
1397 self.show_footer_focus,
1398 ) {
1399 row_buf.set_style(render_cell_area, selected_style);
1400 row_buf.set_style(render_space_area, selected_style);
1401 }
1402 };
1403
1404 if render_cell_area.right() > state.hscroll.offset as u16
1406 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1407 {
1408 if let Some(cell) = footer.cells.get(col) {
1409 if let Some(cell_style) = cell.style {
1410 row_buf.set_style(render_cell_area, cell_style);
1411 }
1412 cell.content.clone().render(render_cell_area, &mut row_buf);
1413 }
1414 }
1415
1416 col += 1;
1417 }
1418
1419 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1421 }
1422 }
1423
1424 #[allow(clippy::too_many_arguments)]
1425 fn render_header(
1426 &self,
1427 columns: usize,
1428 width: u16,
1429 l_columns: &[Rect],
1430 l_spacers: &[Rect],
1431 area: Rect,
1432 buf: &mut Buffer,
1433 state: &mut TableState<Selection>,
1434 ) {
1435 if let Some(header) = &self.header {
1436 let render_row_area = Rect::new(0, 0, width, header.height);
1437 let mut row_buf = Buffer::empty(render_row_area);
1438
1439 row_buf.set_style(render_row_area, self.style);
1440 if let Some(header_style) = header.style {
1441 row_buf.set_style(render_row_area, header_style);
1442 } else if let Some(header_style) = self.header_style {
1443 row_buf.set_style(render_row_area, header_style);
1444 }
1445
1446 let mut col = 0;
1447 loop {
1448 if col >= columns {
1449 break;
1450 }
1451
1452 let render_cell_area =
1453 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1454 let render_space_area = Rect::new(
1455 l_spacers[col + 1].x,
1456 0,
1457 l_spacers[col + 1].width,
1458 area.height,
1459 );
1460
1461 if state.selection.is_selected_column(col) {
1462 if let Some(selected_style) = self.patch_select(
1463 self.select_header_style,
1464 state.focus.get(),
1465 self.show_header_focus,
1466 ) {
1467 row_buf.set_style(render_cell_area, selected_style);
1468 row_buf.set_style(render_space_area, selected_style);
1469 }
1470 };
1471
1472 if render_cell_area.right() > state.hscroll.offset as u16
1474 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1475 {
1476 if let Some(cell) = header.cells.get(col) {
1477 if let Some(cell_style) = cell.style {
1478 row_buf.set_style(render_cell_area, cell_style);
1479 }
1480 cell.content.clone().render(render_cell_area, &mut row_buf);
1481 }
1482 }
1483
1484 col += 1;
1485 }
1486
1487 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1489 }
1490 }
1491
1492 fn calculate_column_areas(
1493 &self,
1494 columns: usize,
1495 l_columns: &[Rect],
1496 l_spacers: &[Rect],
1497 state: &mut TableState<Selection>,
1498 ) {
1499 state.column_areas.clear();
1500 state.column_layout.clear();
1501
1502 let mut col = 0;
1503 let shift = state.hscroll.offset() as isize;
1504 loop {
1505 if col >= columns {
1506 break;
1507 }
1508
1509 state.column_layout.push(Rect::new(
1510 l_columns[col].x,
1511 0,
1512 l_columns[col].width + l_spacers[col + 1].width,
1513 0,
1514 ));
1515
1516 let cell_x1 = l_columns[col].x as isize;
1517 let cell_x2 =
1518 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1519
1520 let squish_x1 = cell_x1.saturating_sub(shift);
1521 let squish_x2 = cell_x2.saturating_sub(shift);
1522
1523 let abs_x1 = max(0, squish_x1) as u16;
1524 let abs_x2 = max(0, squish_x2) as u16;
1525
1526 let v_area = Rect::new(
1527 state.table_area.x + abs_x1,
1528 state.table_area.y,
1529 abs_x2 - abs_x1,
1530 state.table_area.height,
1531 );
1532 state
1533 .column_areas
1534 .push(v_area.intersection(state.table_area));
1535
1536 col += 1;
1537 }
1538 }
1539
1540 #[expect(clippy::collapsible_else_if)]
1541 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1542 if let Some(style) = style {
1543 if let Some(focus_style) = self.focus_style {
1544 if focus && show {
1545 Some(style.patch(focus_style))
1546 } else {
1547 Some(fallback_select_style(style))
1548 }
1549 } else {
1550 if focus && show {
1551 Some(revert_style(style))
1552 } else {
1553 Some(fallback_select_style(style))
1554 }
1555 }
1556 } else {
1557 None
1558 }
1559 }
1560}
1561
1562impl Default for TableStyle {
1563 fn default() -> Self {
1564 Self {
1565 style: Default::default(),
1566 header: None,
1567 footer: None,
1568 select_row: None,
1569 select_column: None,
1570 select_cell: None,
1571 select_header: None,
1572 select_footer: None,
1573 show_row_focus: true, show_column_focus: false,
1575 show_cell_focus: false,
1576 show_header_focus: false,
1577 show_footer_focus: false,
1578 focus_style: None,
1579 block: None,
1580 border_style: None,
1581 scroll: None,
1582 non_exhaustive: NonExhaustive,
1583 }
1584 }
1585}
1586
1587impl<Selection: Clone> Clone for TableState<Selection> {
1588 fn clone(&self) -> Self {
1589 Self {
1590 focus: FocusFlag::named(self.focus.name()),
1591 area: self.area,
1592 inner: self.inner,
1593 header_area: self.header_area,
1594 table_area: self.table_area,
1595 row_areas: self.row_areas.clone(),
1596 column_areas: self.column_areas.clone(),
1597 column_layout: self.column_layout.clone(),
1598 footer_area: self.footer_area,
1599 rows: self.rows,
1600 _counted_rows: self._counted_rows,
1601 columns: self.columns,
1602 vscroll: self.vscroll.clone(),
1603 hscroll: self.hscroll.clone(),
1604 selection: self.selection.clone(),
1605 mouse: Default::default(),
1606 non_exhaustive: NonExhaustive,
1607 }
1608 }
1609}
1610
1611impl<Selection: Default> Default for TableState<Selection> {
1612 fn default() -> Self {
1613 Self {
1614 focus: Default::default(),
1615 area: Default::default(),
1616 inner: Default::default(),
1617 header_area: Default::default(),
1618 table_area: Default::default(),
1619 row_areas: Default::default(),
1620 column_areas: Default::default(),
1621 column_layout: Default::default(),
1622 footer_area: Default::default(),
1623 rows: Default::default(),
1624 _counted_rows: Default::default(),
1625 columns: Default::default(),
1626 vscroll: Default::default(),
1627 hscroll: Default::default(),
1628 selection: Default::default(),
1629 mouse: Default::default(),
1630 non_exhaustive: NonExhaustive,
1631 }
1632 }
1633}
1634
1635impl<Selection> HasFocus for TableState<Selection> {
1636 fn build(&self, builder: &mut FocusBuilder) {
1637 builder.leaf_widget(self);
1638 }
1639
1640 #[inline]
1641 fn focus(&self) -> FocusFlag {
1642 self.focus.clone()
1643 }
1644
1645 #[inline]
1646 fn area(&self) -> Rect {
1647 self.area
1648 }
1649}
1650
1651impl<Selection> RelocatableState for TableState<Selection> {
1652 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1653 self.area = relocate_area(self.area, shift, clip);
1654 self.inner = relocate_area(self.inner, shift, clip);
1655 self.table_area = relocate_area(self.table_area, shift, clip);
1656 self.footer_area = relocate_area(self.footer_area, shift, clip);
1657 self.header_area = relocate_area(self.header_area, shift, clip);
1658
1659 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1660 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1661 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1662
1663 self.hscroll.relocate(shift, clip);
1664 self.vscroll.relocate(shift, clip);
1665 }
1666}
1667
1668impl<Selection> TableState<Selection> {
1669 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1670 let mut sum_heights = 0;
1671 let mut n_rows = 0;
1672 while let Some(h) = row_heights.pop() {
1673 sum_heights += h;
1674 n_rows += 1;
1675 if sum_heights >= self.table_area.height {
1676 break;
1677 }
1678 }
1679
1680 if sum_heights < self.table_area.height {
1681 None
1682 } else {
1683 Some(n_rows)
1684 }
1685 }
1686}
1687
1688impl<Selection> TableState<Selection>
1690where
1691 Selection: Default,
1692{
1693 pub fn new() -> Self {
1694 Self::default()
1695 }
1696
1697 pub fn named(name: &str) -> Self {
1698 Self {
1699 focus: FocusFlag::named(name),
1700 ..TableState::default()
1701 }
1702 }
1703}
1704
1705impl<Selection> TableState<Selection> {
1707 #[inline]
1709 pub fn rows(&self) -> usize {
1710 self.rows
1711 }
1712
1713 #[inline]
1715 pub fn columns(&self) -> usize {
1716 self.columns
1717 }
1718}
1719
1720impl<Selection> TableState<Selection> {
1722 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1728 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1729 return None;
1730 }
1731
1732 let mut areas = Vec::new();
1733
1734 let r = self.row_areas[row - self.vscroll.offset()];
1735 for c in &self.column_areas {
1736 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1737 }
1738
1739 Some((r, areas))
1740 }
1741
1742 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1744 let col = self.column_at_clicked(pos);
1745 let row = self.row_at_clicked(pos);
1746
1747 match (col, row) {
1748 (Some(col), Some(row)) => Some((col, row)),
1749 _ => None,
1750 }
1751 }
1752
1753 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1755 self.mouse.column_at(&self.column_areas, pos.0)
1756 }
1757
1758 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1760 self.mouse
1761 .row_at(&self.row_areas, pos.1)
1762 .map(|v| self.vscroll.offset() + v)
1763 }
1764
1765 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1768 let col = self.column_at_drag(pos);
1769 let row = self.row_at_drag(pos);
1770
1771 (col, row)
1772 }
1773
1774 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1781 match self
1782 .mouse
1783 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1784 {
1785 Ok(v) => self.vscroll.offset() + v,
1786 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1787 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1788 }
1789 }
1790
1791 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1795 match self
1796 .mouse
1797 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1798 {
1799 Ok(v) => v,
1800 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1801 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1802 }
1803 }
1804}
1805
1806impl<Selection: TableSelection> TableState<Selection> {
1808 pub fn clear_offset(&mut self) {
1810 self.vscroll.set_offset(0);
1811 self.hscroll.set_offset(0);
1812 }
1813
1814 pub fn row_max_offset(&self) -> usize {
1819 self.vscroll.max_offset()
1820 }
1821
1822 pub fn row_offset(&self) -> usize {
1824 self.vscroll.offset()
1825 }
1826
1827 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1834 self.vscroll.set_offset(offset)
1835 }
1836
1837 pub fn page_len(&self) -> usize {
1839 self.vscroll.page_len()
1840 }
1841
1842 pub fn row_scroll_by(&self) -> usize {
1844 self.vscroll.scroll_by()
1845 }
1846
1847 pub fn x_max_offset(&self) -> usize {
1852 self.hscroll.max_offset()
1853 }
1854
1855 pub fn x_offset(&self) -> usize {
1857 self.hscroll.offset()
1858 }
1859
1860 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1867 self.hscroll.set_offset(offset)
1868 }
1869
1870 pub fn page_width(&self) -> usize {
1872 self.hscroll.page_len()
1873 }
1874
1875 pub fn x_scroll_by(&self) -> usize {
1877 self.hscroll.scroll_by()
1878 }
1879
1880 pub fn scroll_to_selected(&mut self) -> bool {
1883 if let Some(selected) = self.selection.lead_selection() {
1884 let c = self.scroll_to_col(selected.0);
1885 let r = self.scroll_to_row(selected.1);
1886 r || c
1887 } else {
1888 false
1889 }
1890 }
1891
1892 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1895 if pos >= self.rows {
1896 false
1897 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1898 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1900 if heights < self.table_area.height {
1901 false
1902 } else {
1903 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1904 }
1905 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1906 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1907 } else if pos < self.row_offset() {
1908 self.set_row_offset(pos)
1909 } else {
1910 false
1911 }
1912 }
1913
1914 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1916 if let Some(col) = self.column_layout.get(pos) {
1917 if (col.left() as usize) < self.x_offset() {
1918 self.set_x_offset(col.x as usize)
1919 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1920 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
1921 } else {
1922 false
1923 }
1924 } else {
1925 false
1926 }
1927 }
1928
1929 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
1931 if pos >= self.x_offset().saturating_add(self.page_width()) {
1932 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
1933 } else if pos < self.x_offset() {
1934 self.set_x_offset(pos)
1935 } else {
1936 false
1937 }
1938 }
1939
1940 pub fn scroll_up(&mut self, n: usize) -> bool {
1942 self.vscroll.scroll_up(n)
1943 }
1944
1945 pub fn scroll_down(&mut self, n: usize) -> bool {
1947 self.vscroll.scroll_down(n)
1948 }
1949
1950 pub fn scroll_left(&mut self, n: usize) -> bool {
1952 self.hscroll.scroll_left(n)
1953 }
1954
1955 pub fn scroll_right(&mut self, n: usize) -> bool {
1957 self.hscroll.scroll_right(n)
1958 }
1959}
1960
1961impl TableState<RowSelection> {
1962 pub fn items_added(&mut self, pos: usize, n: usize) {
1965 self.vscroll.items_added(pos, n);
1966 self.selection.items_added(pos, n);
1967 self.rows += n;
1968 }
1969
1970 pub fn items_removed(&mut self, pos: usize, n: usize) {
1973 self.vscroll.items_removed(pos, n);
1974 self.selection
1975 .items_removed(pos, n, self.rows.saturating_sub(1));
1976 self.rows -= n;
1977 }
1978
1979 #[inline]
1981 pub fn set_scroll_selection(&mut self, scroll: bool) {
1982 self.selection.set_scroll_selected(scroll);
1983 }
1984
1985 #[inline]
1987 pub fn clear_selection(&mut self) {
1988 self.selection.clear();
1989 }
1990
1991 #[inline]
1993 pub fn has_selection(&mut self) -> bool {
1994 self.selection.has_selection()
1995 }
1996
1997 #[inline]
2000 pub fn selected(&self) -> Option<usize> {
2001 self.selection.selected()
2002 }
2003
2004 #[inline]
2007 #[allow(clippy::manual_filter)]
2008 pub fn selected_checked(&self) -> Option<usize> {
2009 if let Some(selected) = self.selection.selected() {
2010 if selected < self.rows {
2011 Some(selected)
2012 } else {
2013 None
2014 }
2015 } else {
2016 None
2017 }
2018 }
2019
2020 #[inline]
2023 pub fn select(&mut self, row: Option<usize>) -> bool {
2024 self.selection.select(row)
2025 }
2026
2027 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2031 if self.vscroll.max_offset() > 0 {
2032 (self.rows * offset) / self.vscroll.max_offset()
2033 } else {
2034 0 }
2036 }
2037
2038 #[inline]
2041 pub fn move_to(&mut self, row: usize) -> bool {
2042 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2043 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2044 r || s
2045 }
2046
2047 #[inline]
2050 pub fn move_up(&mut self, n: usize) -> bool {
2051 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2052 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2053 r || s
2054 }
2055
2056 #[inline]
2059 pub fn move_down(&mut self, n: usize) -> bool {
2060 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2061 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2062 r || s
2063 }
2064}
2065
2066impl TableState<RowSetSelection> {
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]
2081 pub fn selected(&self) -> HashSet<usize> {
2082 self.selection.selected()
2083 }
2084
2085 #[inline]
2092 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2093 self.selection.set_lead(row, extend)
2094 }
2095
2096 #[inline]
2098 pub fn lead(&self) -> Option<usize> {
2099 self.selection.lead()
2100 }
2101
2102 #[inline]
2104 pub fn anchor(&self) -> Option<usize> {
2105 self.selection.anchor()
2106 }
2107
2108 #[inline]
2111 pub fn retire_selection(&mut self) {
2112 self.selection.retire_selection();
2113 }
2114
2115 #[inline]
2118 pub fn add_selected(&mut self, idx: usize) {
2119 self.selection.add(idx);
2120 }
2121
2122 #[inline]
2125 pub fn remove_selected(&mut self, idx: usize) {
2126 self.selection.remove(idx);
2127 }
2128
2129 #[inline]
2132 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2133 let r = self
2134 .selection
2135 .move_to(row, self.rows.saturating_sub(1), extend);
2136 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2137 r || s
2138 }
2139
2140 #[inline]
2143 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2144 let r = self
2145 .selection
2146 .move_up(n, self.rows.saturating_sub(1), extend);
2147 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2148 r || s
2149 }
2150
2151 #[inline]
2154 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2155 let r = self
2156 .selection
2157 .move_down(n, self.rows.saturating_sub(1), extend);
2158 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2159 r || s
2160 }
2161}
2162
2163impl TableState<CellSelection> {
2164 #[inline]
2165 pub fn clear_selection(&mut self) {
2166 self.selection.clear();
2167 }
2168
2169 #[inline]
2170 pub fn has_selection(&mut self) -> bool {
2171 self.selection.has_selection()
2172 }
2173
2174 #[inline]
2176 pub fn selected(&self) -> Option<(usize, usize)> {
2177 self.selection.selected()
2178 }
2179
2180 #[inline]
2182 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2183 self.selection.select_cell(select)
2184 }
2185
2186 #[inline]
2188 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2189 if let Some(row) = row {
2190 self.selection
2191 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2192 } else {
2193 self.selection.select_row(None)
2194 }
2195 }
2196
2197 #[inline]
2199 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2200 if let Some(column) = column {
2201 self.selection
2202 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2203 } else {
2204 self.selection.select_column(None)
2205 }
2206 }
2207
2208 #[inline]
2210 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2211 let r = self.selection.move_to(
2212 select,
2213 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2214 );
2215 let s = self.scroll_to_selected();
2216 r || s
2217 }
2218
2219 #[inline]
2221 pub fn move_to_row(&mut self, row: usize) -> bool {
2222 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2223 let s = self.scroll_to_selected();
2224 r || s
2225 }
2226
2227 #[inline]
2229 pub fn move_to_col(&mut self, col: usize) -> bool {
2230 let r = self
2231 .selection
2232 .move_to_col(col, self.columns.saturating_sub(1));
2233 let s = self.scroll_to_selected();
2234 r || s
2235 }
2236
2237 #[inline]
2240 pub fn move_up(&mut self, n: usize) -> bool {
2241 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2242 let s = self.scroll_to_selected();
2243 r || s
2244 }
2245
2246 #[inline]
2249 pub fn move_down(&mut self, n: usize) -> bool {
2250 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2251 let s = self.scroll_to_selected();
2252 r || s
2253 }
2254
2255 #[inline]
2258 pub fn move_left(&mut self, n: usize) -> bool {
2259 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2260 let s = self.scroll_to_selected();
2261 r || s
2262 }
2263
2264 #[inline]
2267 pub fn move_right(&mut self, n: usize) -> bool {
2268 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2269 let s = self.scroll_to_selected();
2270 r || s
2271 }
2272}
2273
2274impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2275 for TableState<Selection>
2276{
2277 fn handle(
2279 &mut self,
2280 event: &crossterm::event::Event,
2281 _keymap: DoubleClick,
2282 ) -> DoubleClickOutcome {
2283 match event {
2284 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2285 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2286 DoubleClickOutcome::ClickClick(col, row)
2287 } else {
2288 DoubleClickOutcome::Continue
2289 }
2290 }
2291 _ => DoubleClickOutcome::Continue,
2292 }
2293 }
2294}
2295
2296pub fn handle_doubleclick_events<Selection: TableSelection>(
2298 state: &mut TableState<Selection>,
2299 event: &crossterm::event::Event,
2300) -> DoubleClickOutcome {
2301 state.handle(event, DoubleClick)
2302}