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::{HandleEvent, ct_event};
12use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
13use rat_reloc::{RelocatableState, relocate_area, relocate_areas};
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 = RowSelection> {
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
53 block: Option<Block<'a>>,
54 hscroll: Option<Scroll<'a>>,
55 vscroll: Option<Scroll<'a>>,
56
57 header_style: Option<Style>,
58 footer_style: Option<Style>,
59 style: Style,
60
61 auto_styles: bool,
62 select_row_style: Option<Style>,
63 show_row_focus: bool,
64 select_column_style: Option<Style>,
65 show_column_focus: bool,
66 select_cell_style: Option<Style>,
67 show_cell_focus: bool,
68 select_header_style: Option<Style>,
69 show_header_focus: bool,
70 select_footer_style: Option<Style>,
71 show_footer_focus: bool,
72
73 focus_style: Option<Style>,
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)]
236pub struct TableStyle {
237 pub style: Style,
238 pub header: Option<Style>,
239 pub footer: Option<Style>,
240
241 pub select_row: Option<Style>,
242 pub select_column: Option<Style>,
243 pub select_cell: Option<Style>,
244 pub select_header: Option<Style>,
245 pub select_footer: Option<Style>,
246
247 pub show_row_focus: bool,
248 pub show_column_focus: bool,
249 pub show_cell_focus: bool,
250 pub show_header_focus: bool,
251 pub show_footer_focus: bool,
252
253 pub focus_style: Option<Style>,
254
255 pub block: Option<Block<'static>>,
256 pub border_style: Option<Style>,
257 pub scroll: Option<ScrollStyle>,
258
259 pub non_exhaustive: NonExhaustive,
260}
261
262#[derive(Debug)]
264pub struct TableState<Selection> {
265 pub focus: FocusFlag,
268
269 pub area: Rect,
272 pub inner: Rect,
275
276 pub header_area: Rect,
279 pub table_area: Rect,
282 pub row_areas: Vec<Rect>,
285 pub column_areas: Vec<Rect>,
289 pub column_layout: Vec<Rect>,
293 pub footer_area: Rect,
296
297 pub rows: usize,
300 pub _counted_rows: usize,
302 pub columns: usize,
305
306 pub vscroll: ScrollState,
309 pub hscroll: ScrollState,
312
313 pub selection: Selection,
316
317 pub mouse: MouseFlags,
319
320 pub non_exhaustive: NonExhaustive,
321}
322
323impl<Selection> Default for Table<'_, Selection> {
324 fn default() -> Self {
325 Self {
326 data: Default::default(),
327 no_row_count: Default::default(),
328 header: Default::default(),
329 footer: Default::default(),
330 widths: Default::default(),
331 flex: Default::default(),
332 column_spacing: Default::default(),
333 layout_width: Default::default(),
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 #[deprecated(since = "1.1.1", note = "no longer supported")]
673 #[inline]
674 pub fn auto_layout_width(self) -> Self {
675 self
676 }
677
678 #[inline]
680 pub fn block(mut self, block: Block<'a>) -> Self {
681 self.block = Some(block);
682 self.block = self.block.map(|v| v.style(self.style));
683 self
684 }
685
686 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
688 self.hscroll = Some(scroll.clone().override_horizontal());
689 self.vscroll = Some(scroll.override_vertical());
690 self
691 }
692
693 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
695 self.hscroll = Some(scroll.override_horizontal());
696 self
697 }
698
699 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
701 self.vscroll = Some(scroll.override_vertical());
702 self
703 }
704
705 #[inline]
707 pub fn styles(mut self, styles: TableStyle) -> Self {
708 self.style = styles.style;
709 if styles.header.is_some() {
710 self.header_style = styles.header;
711 }
712 if styles.footer.is_some() {
713 self.footer_style = styles.footer;
714 }
715 if styles.select_row.is_some() {
716 self.select_row_style = styles.select_row;
717 }
718 self.show_row_focus = styles.show_row_focus;
719 if styles.select_column.is_some() {
720 self.select_column_style = styles.select_column;
721 }
722 self.show_column_focus = styles.show_column_focus;
723 if styles.select_cell.is_some() {
724 self.select_cell_style = styles.select_cell;
725 }
726 self.show_cell_focus = styles.show_cell_focus;
727 if styles.select_header.is_some() {
728 self.select_header_style = styles.select_header;
729 }
730 self.show_header_focus = styles.show_header_focus;
731 if styles.select_footer.is_some() {
732 self.select_footer_style = styles.select_footer;
733 }
734 self.show_footer_focus = styles.show_footer_focus;
735 if styles.focus_style.is_some() {
736 self.focus_style = styles.focus_style;
737 }
738 if let Some(border_style) = styles.border_style {
739 self.block = self.block.map(|v| v.border_style(border_style));
740 }
741 self.block = self.block.map(|v| v.style(self.style));
742 if styles.block.is_some() {
743 self.block = styles.block;
744 }
745 if let Some(styles) = styles.scroll {
746 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
747 self.vscroll = self.vscroll.map(|v| v.styles(styles));
748 }
749 self
750 }
751
752 #[inline]
754 pub fn style(mut self, style: Style) -> Self {
755 self.style = style;
756 self.block = self.block.map(|v| v.style(self.style));
757 self
758 }
759
760 #[inline]
762 pub fn header_style(mut self, style: Option<Style>) -> Self {
763 self.header_style = style;
764 self
765 }
766
767 #[inline]
769 pub fn footer_style(mut self, style: Option<Style>) -> Self {
770 self.footer_style = style;
771 self
772 }
773
774 #[inline]
780 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
781 self.auto_styles = auto_styles;
782 self
783 }
784
785 #[inline]
788 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
789 self.select_row_style = select_style;
790 self
791 }
792
793 #[inline]
795 pub fn show_row_focus(mut self, show: bool) -> Self {
796 self.show_row_focus = show;
797 self
798 }
799
800 #[inline]
803 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
804 self.select_column_style = select_style;
805 self
806 }
807
808 #[inline]
810 pub fn show_column_focus(mut self, show: bool) -> Self {
811 self.show_column_focus = show;
812 self
813 }
814
815 #[inline]
818 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
819 self.select_cell_style = select_style;
820 self
821 }
822
823 #[inline]
825 pub fn show_cell_focus(mut self, show: bool) -> Self {
826 self.show_cell_focus = show;
827 self
828 }
829
830 #[inline]
833 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
834 self.select_header_style = select_style;
835 self
836 }
837
838 #[inline]
840 pub fn show_header_focus(mut self, show: bool) -> Self {
841 self.show_header_focus = show;
842 self
843 }
844
845 #[inline]
848 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
849 self.select_footer_style = select_style;
850 self
851 }
852
853 #[inline]
855 pub fn show_footer_focus(mut self, show: bool) -> Self {
856 self.show_footer_focus = show;
857 self
858 }
859
860 #[inline]
866 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
867 self.focus_style = focus_style;
868 self
869 }
870
871 #[deprecated(since = "1.1.1", note = "not in use")]
872 pub fn debug(self, _: bool) -> Self {
873 self
874 }
875}
876
877impl<Selection> Table<'_, Selection> {
878 #[inline]
880 fn total_width(&self, area_width: u16) -> u16 {
881 if let Some(layout_width) = self.layout_width {
882 layout_width
883 } else {
884 area_width
885 }
886 }
887
888 #[inline]
890 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
891 let width = self.total_width(width);
892 let area = Rect::new(0, 0, width, 0);
893
894 let (layout, spacers) = Layout::horizontal(&self.widths)
895 .flex(self.flex)
896 .spacing(self.column_spacing)
897 .split_with_spacers(area);
898
899 (width, layout, spacers)
900 }
901
902 #[inline]
904 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
905 let heights = vec![
906 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
907 Constraint::Fill(1),
908 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
909 ];
910
911 Layout::vertical(heights).split(area)
912 }
913}
914
915impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
916where
917 Selection: TableSelection,
918{
919 type State = TableState<Selection>;
920
921 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
922 let iter = self.data.iter();
923 self.render_iter(iter, area, buf, state);
924 }
925}
926
927impl<Selection> StatefulWidget for Table<'_, Selection>
928where
929 Selection: TableSelection,
930{
931 type State = TableState<Selection>;
932
933 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
934 let iter = mem::take(&mut self.data).into_iter();
935 self.render_iter(iter, area, buf, state);
936 }
937}
938
939impl<'a, Selection> Table<'a, Selection>
940where
941 Selection: TableSelection,
942{
943 fn render_iter<'b>(
948 &self,
949 mut data: DataReprIter<'a, 'b>,
950 area: Rect,
951 buf: &mut Buffer,
952 state: &mut TableState<Selection>,
953 ) {
954 if let Some(rows) = data.rows() {
955 state.rows = rows;
956 }
957 state.columns = self.widths.len();
958 state.area = area;
959
960 let sa = ScrollArea::new()
961 .style(self.style)
962 .block(self.block.as_ref())
963 .h_scroll(self.hscroll.as_ref())
964 .v_scroll(self.vscroll.as_ref())
965 .ignore_scroll();
966 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
967
968 let l_rows = self.layout_areas(state.inner);
969 state.header_area = l_rows[0];
970 state.table_area = l_rows[1];
971 state.footer_area = l_rows[2];
972
973 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
975 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
976
977 sa.render(
979 area,
980 buf,
981 &mut ScrollAreaState::new()
982 .h_scroll(&mut state.hscroll)
983 .v_scroll(&mut state.vscroll),
984 );
985
986 self.render_header(
988 state.columns,
989 width,
990 l_columns.as_ref(),
991 l_spacers.as_ref(),
992 state.header_area,
993 buf,
994 state,
995 );
996 self.render_footer(
997 state.columns,
998 width,
999 l_columns.as_ref(),
1000 l_spacers.as_ref(),
1001 state.footer_area,
1002 buf,
1003 state,
1004 );
1005
1006 state.row_areas.clear();
1008 state.vscroll.set_page_len(0);
1009 state.hscroll.set_page_len(area.width as usize);
1010
1011 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1012 let mut row = None;
1013 let mut row_y = state.table_area.y;
1014 let mut row_heights = Vec::new();
1015 #[cfg(debug_assertions)]
1016 let mut insane_offset = false;
1017
1018 let mut ctx = TableContext {
1019 focus: state.focus.get(),
1020 selected_cell: false,
1021 selected_row: false,
1022 selected_column: false,
1023 style: self.style,
1024 row_style: None,
1025 select_style: None,
1026 space_area: Default::default(),
1027 row_area: Default::default(),
1028 non_exhaustive: NonExhaustive,
1029 };
1030
1031 if data.nth(state.vscroll.offset()) {
1032 row = Some(state.vscroll.offset());
1033 loop {
1034 ctx.row_style = data.row_style();
1035 let render_row_area = Rect::new(0, 0, width, data.row_height());
1039 ctx.row_area = render_row_area;
1040 row_buf.resize(render_row_area);
1041 if self.auto_styles {
1042 if let Some(row_style) = ctx.row_style {
1043 row_buf.set_style(render_row_area, row_style);
1044 } else {
1045 row_buf.set_style(render_row_area, self.style);
1046 }
1047 }
1048 row_heights.push(render_row_area.height);
1049
1050 let visible_row_area = Rect::new(
1052 state.table_area.x,
1053 row_y,
1054 state.table_area.width,
1055 render_row_area.height,
1056 )
1057 .intersection(state.table_area);
1058 state.row_areas.push(visible_row_area);
1059 if render_row_area.height == visible_row_area.height {
1061 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1062 }
1063
1064 if render_row_area.height > 0 {
1066 let mut col = 0;
1067 loop {
1068 if col >= state.columns {
1069 break;
1070 }
1071
1072 let render_cell_area = Rect::new(
1073 l_columns[col].x,
1074 0,
1075 l_columns[col].width,
1076 render_row_area.height,
1077 );
1078 ctx.space_area = Rect::new(
1079 l_spacers[col + 1].x,
1080 0,
1081 l_spacers[col + 1].width,
1082 render_row_area.height,
1083 );
1084
1085 if state.selection.is_selected_cell(col, row.expect("row")) {
1086 ctx.selected_cell = true;
1087 ctx.selected_row = false;
1088 ctx.selected_column = false;
1089 ctx.select_style = self.patch_select(
1090 self.select_cell_style,
1091 state.focus.get(),
1092 self.show_cell_focus,
1093 );
1094 } else if state.selection.is_selected_row(row.expect("row")) {
1095 ctx.selected_cell = false;
1096 ctx.selected_row = true;
1097 ctx.selected_column = false;
1098 ctx.select_style = if self.select_row_style.is_some() {
1100 self.patch_select(
1101 self.select_row_style,
1102 state.focus.get(),
1103 self.show_row_focus,
1104 )
1105 } else {
1106 self.patch_select(
1107 Some(self.style),
1108 state.focus.get(),
1109 self.show_row_focus,
1110 )
1111 };
1112 } else if state.selection.is_selected_column(col) {
1113 ctx.selected_cell = false;
1114 ctx.selected_row = false;
1115 ctx.selected_column = true;
1116 ctx.select_style = self.patch_select(
1117 self.select_column_style,
1118 state.focus.get(),
1119 self.show_column_focus,
1120 );
1121 } else {
1122 ctx.selected_cell = false;
1123 ctx.selected_row = false;
1124 ctx.selected_column = false;
1125 ctx.select_style = None;
1126 }
1127
1128 if render_cell_area.right() > state.hscroll.offset as u16
1130 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1131 {
1132 if self.auto_styles {
1133 if let Some(select_style) = ctx.select_style {
1134 row_buf.set_style(render_cell_area, select_style);
1135 row_buf.set_style(ctx.space_area, select_style);
1136 }
1137 }
1138 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1139 }
1140
1141 col += 1;
1142 }
1143
1144 transfer_buffer(
1146 &mut row_buf,
1147 state.hscroll.offset() as u16,
1148 visible_row_area,
1149 buf,
1150 );
1151 }
1152
1153 if visible_row_area.bottom() >= state.table_area.bottom() {
1154 break;
1155 }
1156 if !data.nth(0) {
1157 break;
1158 }
1159 row = Some(row.expect("row").saturating_add(1));
1160 row_y += render_row_area.height;
1161 }
1162 } else {
1163 if data.rows().is_none() || data.rows() == Some(0) {
1168 } else {
1170 #[cfg(debug_assertions)]
1171 {
1172 insane_offset = true;
1173 }
1174 }
1175 }
1176
1177 #[allow(unused_variables)]
1179 let algorithm;
1180 #[allow(unused_assignments)]
1181 {
1182 if let Some(rows) = data.rows() {
1183 algorithm = 0;
1184 let skip_rows = rows
1188 .saturating_sub(row.map_or(0, |v| v + 1))
1189 .saturating_sub(state.table_area.height as usize);
1190 if skip_rows > 0 {
1192 row_heights.clear();
1193 }
1194 let nth_row = skip_rows;
1195 if data.nth(nth_row) {
1197 let mut sum_height = row_heights.iter().sum::<u16>();
1198 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1199 loop {
1200 let row_height = data.row_height();
1201 row_heights.push(row_height);
1202
1203 sum_height += row_height;
1206 if sum_height
1207 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1208 > state.table_area.height
1209 {
1210 let lost_height = row_heights.remove(0);
1211 sum_height -= lost_height;
1212 }
1213
1214 if !data.nth(0) {
1215 break;
1216 }
1217
1218 row = Some(row.expect("row") + 1);
1219 if row.expect("row") > rows {
1221 break;
1222 }
1223 }
1224 while data.nth(0) {
1227 row = Some(row.expect("row") + 1);
1228 }
1229 } else {
1230 }
1233
1234 state.rows = rows;
1235 state._counted_rows = row.map_or(0, |v| v + 1);
1236
1237 if let Some(last_page) = state.calc_last_page(row_heights) {
1239 state.vscroll.set_max_offset(state.rows - last_page);
1240 } else {
1241 state.vscroll.set_max_offset(
1245 state.rows.saturating_sub(state.table_area.height as usize),
1246 );
1247 }
1248 } else if self.no_row_count {
1249 algorithm = 1;
1250
1251 if row.is_some() {
1255 if data.nth(0) {
1256 row = Some(row.expect("row").saturating_add(1));
1258 if data.nth(0) {
1259 row = Some(usize::MAX - 1);
1261 }
1262 }
1263 }
1264
1265 state.rows = row.map_or(0, |v| v + 1);
1266 state._counted_rows = row.map_or(0, |v| v + 1);
1267 state.vscroll.set_max_offset(usize::MAX - 1);
1269 if state.vscroll.page_len() == 0 {
1270 state.vscroll.set_page_len(state.table_area.height as usize);
1271 }
1272 } else {
1273 algorithm = 2;
1274
1275 let mut sum_height = row_heights.iter().sum::<u16>();
1277 while data.nth(0) {
1278 let row_height = data.row_height();
1279 row_heights.push(row_height);
1280
1281 sum_height += row_height;
1284 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1285 > state.table_area.height
1286 {
1287 let lost_height = row_heights.remove(0);
1288 sum_height -= lost_height;
1289 }
1290 row = Some(row.map_or(0, |v| v + 1));
1291 }
1292
1293 state.rows = row.map_or(0, |v| v + 1);
1294 state._counted_rows = row.map_or(0, |v| v + 1);
1295
1296 if let Some(last_page) = state.calc_last_page(row_heights) {
1298 state.vscroll.set_max_offset(state.rows - last_page);
1299 } else {
1300 state.vscroll.set_max_offset(0);
1301 }
1302 }
1303 }
1304 {
1305 state
1306 .hscroll
1307 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1308 }
1309
1310 ScrollArea::new()
1312 .style(self.style)
1313 .block(self.block.as_ref())
1314 .ignore_block()
1315 .h_scroll(self.hscroll.as_ref())
1316 .v_scroll(self.vscroll.as_ref())
1317 .render(
1318 area,
1319 buf,
1320 &mut ScrollAreaState::new()
1321 .h_scroll(&mut state.hscroll)
1322 .v_scroll(&mut state.vscroll),
1323 );
1324
1325 #[cfg(debug_assertions)]
1326 {
1327 use std::fmt::Write;
1328 let mut msg = String::new();
1329 if insane_offset {
1330 _ = write!(
1331 msg,
1332 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1333 state.vscroll.offset(),
1334 state.rows,
1335 state._counted_rows,
1336 algorithm
1337 );
1338 }
1339 if state.rows != state._counted_rows {
1340 _ = write!(
1341 msg,
1342 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1343 state.rows, state._counted_rows, algorithm
1344 );
1345 }
1346 if !msg.is_empty() {
1347 use log::warn;
1348 use ratatui::style::Stylize;
1349 use ratatui::text::Text;
1350
1351 warn!("{}", &msg);
1352 Text::from(msg)
1353 .white()
1354 .on_red()
1355 .render(state.table_area, buf);
1356 }
1357 }
1358 }
1359
1360 #[allow(clippy::too_many_arguments)]
1361 fn render_footer(
1362 &self,
1363 columns: usize,
1364 width: u16,
1365 l_columns: &[Rect],
1366 l_spacers: &[Rect],
1367 area: Rect,
1368 buf: &mut Buffer,
1369 state: &mut TableState<Selection>,
1370 ) {
1371 if let Some(footer) = &self.footer {
1372 let render_row_area = Rect::new(0, 0, width, footer.height);
1373 let mut row_buf = Buffer::empty(render_row_area);
1374
1375 row_buf.set_style(render_row_area, self.style);
1376 if let Some(footer_style) = footer.style {
1377 row_buf.set_style(render_row_area, footer_style);
1378 } else if let Some(footer_style) = self.footer_style {
1379 row_buf.set_style(render_row_area, footer_style);
1380 }
1381
1382 let mut col = 0;
1383 loop {
1384 if col >= columns {
1385 break;
1386 }
1387
1388 let render_cell_area =
1389 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1390 let render_space_area = Rect::new(
1391 l_spacers[col + 1].x,
1392 0,
1393 l_spacers[col + 1].width,
1394 area.height,
1395 );
1396
1397 if state.selection.is_selected_column(col) {
1398 if let Some(selected_style) = self.patch_select(
1399 self.select_footer_style,
1400 state.focus.get(),
1401 self.show_footer_focus,
1402 ) {
1403 row_buf.set_style(render_cell_area, selected_style);
1404 row_buf.set_style(render_space_area, selected_style);
1405 }
1406 };
1407
1408 if render_cell_area.right() > state.hscroll.offset as u16
1410 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1411 {
1412 if let Some(cell) = footer.cells.get(col) {
1413 if let Some(cell_style) = cell.style {
1414 row_buf.set_style(render_cell_area, cell_style);
1415 }
1416 cell.content.clone().render(render_cell_area, &mut row_buf);
1417 }
1418 }
1419
1420 col += 1;
1421 }
1422
1423 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1425 }
1426 }
1427
1428 #[allow(clippy::too_many_arguments)]
1429 fn render_header(
1430 &self,
1431 columns: usize,
1432 width: u16,
1433 l_columns: &[Rect],
1434 l_spacers: &[Rect],
1435 area: Rect,
1436 buf: &mut Buffer,
1437 state: &mut TableState<Selection>,
1438 ) {
1439 if let Some(header) = &self.header {
1440 let render_row_area = Rect::new(0, 0, width, header.height);
1441 let mut row_buf = Buffer::empty(render_row_area);
1442
1443 row_buf.set_style(render_row_area, self.style);
1444 if let Some(header_style) = header.style {
1445 row_buf.set_style(render_row_area, header_style);
1446 } else if let Some(header_style) = self.header_style {
1447 row_buf.set_style(render_row_area, header_style);
1448 }
1449
1450 let mut col = 0;
1451 loop {
1452 if col >= columns {
1453 break;
1454 }
1455
1456 let render_cell_area =
1457 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1458 let render_space_area = Rect::new(
1459 l_spacers[col + 1].x,
1460 0,
1461 l_spacers[col + 1].width,
1462 area.height,
1463 );
1464
1465 if state.selection.is_selected_column(col) {
1466 if let Some(selected_style) = self.patch_select(
1467 self.select_header_style,
1468 state.focus.get(),
1469 self.show_header_focus,
1470 ) {
1471 row_buf.set_style(render_cell_area, selected_style);
1472 row_buf.set_style(render_space_area, selected_style);
1473 }
1474 };
1475
1476 if render_cell_area.right() > state.hscroll.offset as u16
1478 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1479 {
1480 if let Some(cell) = header.cells.get(col) {
1481 if let Some(cell_style) = cell.style {
1482 row_buf.set_style(render_cell_area, cell_style);
1483 }
1484 cell.content.clone().render(render_cell_area, &mut row_buf);
1485 }
1486 }
1487
1488 col += 1;
1489 }
1490
1491 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1493 }
1494 }
1495
1496 fn calculate_column_areas(
1497 &self,
1498 columns: usize,
1499 l_columns: &[Rect],
1500 l_spacers: &[Rect],
1501 state: &mut TableState<Selection>,
1502 ) {
1503 state.column_areas.clear();
1504 state.column_layout.clear();
1505
1506 let mut col = 0;
1507 let shift = state.hscroll.offset() as isize;
1508 loop {
1509 if col >= columns {
1510 break;
1511 }
1512
1513 state.column_layout.push(Rect::new(
1514 l_columns[col].x,
1515 0,
1516 l_columns[col].width + l_spacers[col + 1].width,
1517 0,
1518 ));
1519
1520 let cell_x1 = l_columns[col].x as isize;
1521 let cell_x2 =
1522 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1523
1524 let squish_x1 = cell_x1.saturating_sub(shift);
1525 let squish_x2 = cell_x2.saturating_sub(shift);
1526
1527 let abs_x1 = max(0, squish_x1) as u16;
1528 let abs_x2 = max(0, squish_x2) as u16;
1529
1530 let v_area = Rect::new(
1531 state.table_area.x + abs_x1,
1532 state.table_area.y,
1533 abs_x2 - abs_x1,
1534 state.table_area.height,
1535 );
1536 state
1537 .column_areas
1538 .push(v_area.intersection(state.table_area));
1539
1540 col += 1;
1541 }
1542 }
1543
1544 #[expect(clippy::collapsible_else_if)]
1545 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1546 if let Some(style) = style {
1547 if let Some(focus_style) = self.focus_style {
1548 if focus && show {
1549 Some(style.patch(focus_style))
1550 } else {
1551 Some(fallback_select_style(style))
1552 }
1553 } else {
1554 if focus && show {
1555 Some(revert_style(style))
1556 } else {
1557 Some(fallback_select_style(style))
1558 }
1559 }
1560 } else {
1561 None
1562 }
1563 }
1564}
1565
1566impl Default for TableStyle {
1567 fn default() -> Self {
1568 Self {
1569 style: Default::default(),
1570 header: None,
1571 footer: None,
1572 select_row: None,
1573 select_column: None,
1574 select_cell: None,
1575 select_header: None,
1576 select_footer: None,
1577 show_row_focus: true, show_column_focus: false,
1579 show_cell_focus: false,
1580 show_header_focus: false,
1581 show_footer_focus: false,
1582 focus_style: None,
1583 block: None,
1584 border_style: None,
1585 scroll: None,
1586 non_exhaustive: NonExhaustive,
1587 }
1588 }
1589}
1590
1591impl<Selection: Clone> Clone for TableState<Selection> {
1592 fn clone(&self) -> Self {
1593 Self {
1594 focus: FocusFlag::named(self.focus.name()),
1595 area: self.area,
1596 inner: self.inner,
1597 header_area: self.header_area,
1598 table_area: self.table_area,
1599 row_areas: self.row_areas.clone(),
1600 column_areas: self.column_areas.clone(),
1601 column_layout: self.column_layout.clone(),
1602 footer_area: self.footer_area,
1603 rows: self.rows,
1604 _counted_rows: self._counted_rows,
1605 columns: self.columns,
1606 vscroll: self.vscroll.clone(),
1607 hscroll: self.hscroll.clone(),
1608 selection: self.selection.clone(),
1609 mouse: Default::default(),
1610 non_exhaustive: NonExhaustive,
1611 }
1612 }
1613}
1614
1615impl<Selection: Default> Default for TableState<Selection> {
1616 fn default() -> Self {
1617 Self {
1618 focus: Default::default(),
1619 area: Default::default(),
1620 inner: Default::default(),
1621 header_area: Default::default(),
1622 table_area: Default::default(),
1623 row_areas: Default::default(),
1624 column_areas: Default::default(),
1625 column_layout: Default::default(),
1626 footer_area: Default::default(),
1627 rows: Default::default(),
1628 _counted_rows: Default::default(),
1629 columns: Default::default(),
1630 vscroll: Default::default(),
1631 hscroll: Default::default(),
1632 selection: Default::default(),
1633 mouse: Default::default(),
1634 non_exhaustive: NonExhaustive,
1635 }
1636 }
1637}
1638
1639impl<Selection> HasFocus for TableState<Selection> {
1640 fn build(&self, builder: &mut FocusBuilder) {
1641 builder.leaf_widget(self);
1642 }
1643
1644 #[inline]
1645 fn focus(&self) -> FocusFlag {
1646 self.focus.clone()
1647 }
1648
1649 #[inline]
1650 fn area(&self) -> Rect {
1651 self.area
1652 }
1653}
1654
1655impl<Selection> RelocatableState for TableState<Selection> {
1656 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1657 self.area = relocate_area(self.area, shift, clip);
1658 self.inner = relocate_area(self.inner, shift, clip);
1659 self.table_area = relocate_area(self.table_area, shift, clip);
1660 self.footer_area = relocate_area(self.footer_area, shift, clip);
1661 self.header_area = relocate_area(self.header_area, shift, clip);
1662
1663 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1664 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1665 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1666
1667 self.hscroll.relocate(shift, clip);
1668 self.vscroll.relocate(shift, clip);
1669 }
1670}
1671
1672impl<Selection> TableState<Selection> {
1673 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1674 let mut sum_heights = 0;
1675 let mut n_rows = 0;
1676 while let Some(h) = row_heights.pop() {
1677 sum_heights += h;
1678 n_rows += 1;
1679 if sum_heights >= self.table_area.height {
1680 break;
1681 }
1682 }
1683
1684 if sum_heights < self.table_area.height {
1685 None
1686 } else {
1687 Some(n_rows)
1688 }
1689 }
1690}
1691
1692impl<Selection> TableState<Selection>
1694where
1695 Selection: Default,
1696{
1697 pub fn new() -> Self {
1698 Self::default()
1699 }
1700
1701 pub fn named(name: &str) -> Self {
1702 Self {
1703 focus: FocusFlag::named(name),
1704 ..TableState::default()
1705 }
1706 }
1707}
1708
1709impl<Selection> TableState<Selection> {
1711 #[inline]
1713 pub fn rows(&self) -> usize {
1714 self.rows
1715 }
1716
1717 pub fn rows_changed(&mut self, rows: usize) {
1729 self.rows = rows;
1730 self.vscroll
1731 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1732 }
1733
1734 #[inline]
1736 pub fn columns(&self) -> usize {
1737 self.columns
1738 }
1739}
1740
1741impl<Selection> TableState<Selection> {
1743 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1749 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1750 return None;
1751 }
1752
1753 let mut areas = Vec::new();
1754
1755 let r = self.row_areas[row - self.vscroll.offset()];
1756 for c in &self.column_areas {
1757 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1758 }
1759
1760 Some((r, areas))
1761 }
1762
1763 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1765 let col = self.column_at_clicked(pos);
1766 let row = self.row_at_clicked(pos);
1767
1768 match (col, row) {
1769 (Some(col), Some(row)) => Some((col, row)),
1770 _ => None,
1771 }
1772 }
1773
1774 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1776 self.mouse.column_at(&self.column_areas, pos.0)
1777 }
1778
1779 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1781 self.mouse
1782 .row_at(&self.row_areas, pos.1)
1783 .map(|v| self.vscroll.offset() + v)
1784 }
1785
1786 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1789 let col = self.column_at_drag(pos);
1790 let row = self.row_at_drag(pos);
1791
1792 (col, row)
1793 }
1794
1795 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1802 match self
1803 .mouse
1804 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1805 {
1806 Ok(v) => self.vscroll.offset() + v,
1807 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1808 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1809 }
1810 }
1811
1812 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1816 match self
1817 .mouse
1818 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1819 {
1820 Ok(v) => v,
1821 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1822 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1823 }
1824 }
1825}
1826
1827impl<Selection: TableSelection> TableState<Selection> {
1829 pub fn clear_offset(&mut self) {
1831 self.vscroll.set_offset(0);
1832 self.hscroll.set_offset(0);
1833 }
1834
1835 pub fn row_max_offset(&self) -> usize {
1840 self.vscroll.max_offset()
1841 }
1842
1843 pub fn row_offset(&self) -> usize {
1845 self.vscroll.offset()
1846 }
1847
1848 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1855 self.vscroll.set_offset(offset)
1856 }
1857
1858 pub fn page_len(&self) -> usize {
1860 self.vscroll.page_len()
1861 }
1862
1863 pub fn row_scroll_by(&self) -> usize {
1865 self.vscroll.scroll_by()
1866 }
1867
1868 pub fn x_max_offset(&self) -> usize {
1873 self.hscroll.max_offset()
1874 }
1875
1876 pub fn x_offset(&self) -> usize {
1878 self.hscroll.offset()
1879 }
1880
1881 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1888 self.hscroll.set_offset(offset)
1889 }
1890
1891 pub fn page_width(&self) -> usize {
1893 self.hscroll.page_len()
1894 }
1895
1896 pub fn x_scroll_by(&self) -> usize {
1898 self.hscroll.scroll_by()
1899 }
1900
1901 pub fn scroll_to_selected(&mut self) -> bool {
1905 if let Some(selected) = self.selection.lead_selection() {
1906 let c = self.scroll_to_col(selected.0);
1907 let r = self.scroll_to_row(selected.1);
1908 r || c
1909 } else {
1910 false
1911 }
1912 }
1913
1914 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1919 if pos >= self.rows {
1920 false
1921 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1922 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1924 if heights < self.table_area.height {
1925 false
1926 } else {
1927 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1928 }
1929 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1930 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1931 } else if pos < self.row_offset() {
1932 self.set_row_offset(pos)
1933 } else {
1934 false
1935 }
1936 }
1937
1938 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1940 if let Some(col) = self.column_layout.get(pos) {
1941 if (col.left() as usize) < self.x_offset() {
1942 self.set_x_offset(col.x as usize)
1943 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1944 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
1945 } else {
1946 false
1947 }
1948 } else {
1949 false
1950 }
1951 }
1952
1953 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
1955 if pos >= self.x_offset().saturating_add(self.page_width()) {
1956 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
1957 } else if pos < self.x_offset() {
1958 self.set_x_offset(pos)
1959 } else {
1960 false
1961 }
1962 }
1963
1964 pub fn scroll_up(&mut self, n: usize) -> bool {
1966 self.vscroll.scroll_up(n)
1967 }
1968
1969 pub fn scroll_down(&mut self, n: usize) -> bool {
1971 self.vscroll.scroll_down(n)
1972 }
1973
1974 pub fn scroll_left(&mut self, n: usize) -> bool {
1976 self.hscroll.scroll_left(n)
1977 }
1978
1979 pub fn scroll_right(&mut self, n: usize) -> bool {
1981 self.hscroll.scroll_right(n)
1982 }
1983}
1984
1985impl TableState<RowSelection> {
1986 pub fn items_added(&mut self, pos: usize, n: usize) {
1990 self.vscroll.items_added(pos, n);
1991 self.selection.items_added(pos, n);
1992 self.rows += n;
1993 }
1994
1995 pub fn items_removed(&mut self, pos: usize, n: usize) {
1999 self.vscroll.items_removed(pos, n);
2000 self.selection
2001 .items_removed(pos, n, self.rows.saturating_sub(1));
2002 self.rows -= n;
2003 }
2004
2005 #[inline]
2008 pub fn set_scroll_selection(&mut self, scroll: bool) {
2009 self.selection.set_scroll_selected(scroll);
2010 }
2011
2012 #[inline]
2014 pub fn clear_selection(&mut self) {
2015 self.selection.clear();
2016 }
2017
2018 #[inline]
2020 pub fn has_selection(&mut self) -> bool {
2021 self.selection.has_selection()
2022 }
2023
2024 #[inline]
2027 pub fn selected(&self) -> Option<usize> {
2028 self.selection.selected()
2029 }
2030
2031 #[inline]
2034 #[allow(clippy::manual_filter)]
2035 pub fn selected_checked(&self) -> Option<usize> {
2036 if let Some(selected) = self.selection.selected() {
2037 if selected < self.rows {
2038 Some(selected)
2039 } else {
2040 None
2041 }
2042 } else {
2043 None
2044 }
2045 }
2046
2047 #[inline]
2050 pub fn select(&mut self, row: Option<usize>) -> bool {
2051 self.selection.select(row)
2052 }
2053
2054 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2058 if self.vscroll.max_offset() > 0 {
2059 (self.rows * offset) / self.vscroll.max_offset()
2060 } else {
2061 0 }
2063 }
2064
2065 #[inline]
2068 pub fn move_to(&mut self, row: usize) -> bool {
2069 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2070 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2071 r || s
2072 }
2073
2074 #[inline]
2077 pub fn move_up(&mut self, n: usize) -> bool {
2078 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2079 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2080 r || s
2081 }
2082
2083 #[inline]
2086 pub fn move_down(&mut self, n: usize) -> bool {
2087 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2088 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2089 r || s
2090 }
2091}
2092
2093impl TableState<RowSetSelection> {
2094 #[inline]
2096 pub fn clear_selection(&mut self) {
2097 self.selection.clear();
2098 }
2099
2100 #[inline]
2102 pub fn has_selection(&mut self) -> bool {
2103 self.selection.has_selection()
2104 }
2105
2106 #[inline]
2108 pub fn selected(&self) -> HashSet<usize> {
2109 self.selection.selected()
2110 }
2111
2112 #[inline]
2119 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2120 self.selection.set_lead(row, extend)
2121 }
2122
2123 #[inline]
2125 pub fn lead(&self) -> Option<usize> {
2126 self.selection.lead()
2127 }
2128
2129 #[inline]
2131 pub fn anchor(&self) -> Option<usize> {
2132 self.selection.anchor()
2133 }
2134
2135 #[inline]
2138 pub fn retire_selection(&mut self) {
2139 self.selection.retire_selection();
2140 }
2141
2142 #[inline]
2147 pub fn add_selected(&mut self, idx: usize) {
2148 self.selection.add(idx);
2149 }
2150
2151 #[inline]
2156 pub fn remove_selected(&mut self, idx: usize) {
2157 self.selection.remove(idx);
2158 }
2159
2160 #[inline]
2163 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2164 let r = self
2165 .selection
2166 .move_to(row, self.rows.saturating_sub(1), extend);
2167 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2168 r || s
2169 }
2170
2171 #[inline]
2174 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2175 let r = self
2176 .selection
2177 .move_up(n, self.rows.saturating_sub(1), extend);
2178 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2179 r || s
2180 }
2181
2182 #[inline]
2185 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2186 let r = self
2187 .selection
2188 .move_down(n, self.rows.saturating_sub(1), extend);
2189 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2190 r || s
2191 }
2192}
2193
2194impl TableState<CellSelection> {
2195 #[inline]
2196 pub fn clear_selection(&mut self) {
2197 self.selection.clear();
2198 }
2199
2200 #[inline]
2201 pub fn has_selection(&mut self) -> bool {
2202 self.selection.has_selection()
2203 }
2204
2205 #[inline]
2207 pub fn selected(&self) -> Option<(usize, usize)> {
2208 self.selection.selected()
2209 }
2210
2211 #[inline]
2213 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2214 self.selection.select_cell(select)
2215 }
2216
2217 #[inline]
2219 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2220 if let Some(row) = row {
2221 self.selection
2222 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2223 } else {
2224 self.selection.select_row(None)
2225 }
2226 }
2227
2228 #[inline]
2230 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2231 if let Some(column) = column {
2232 self.selection
2233 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2234 } else {
2235 self.selection.select_column(None)
2236 }
2237 }
2238
2239 #[inline]
2241 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2242 let r = self.selection.move_to(
2243 select,
2244 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2245 );
2246 let s = self.scroll_to_selected();
2247 r || s
2248 }
2249
2250 #[inline]
2252 pub fn move_to_row(&mut self, row: usize) -> bool {
2253 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2254 let s = self.scroll_to_selected();
2255 r || s
2256 }
2257
2258 #[inline]
2260 pub fn move_to_col(&mut self, col: usize) -> bool {
2261 let r = self
2262 .selection
2263 .move_to_col(col, self.columns.saturating_sub(1));
2264 let s = self.scroll_to_selected();
2265 r || s
2266 }
2267
2268 #[inline]
2271 pub fn move_up(&mut self, n: usize) -> bool {
2272 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2273 let s = self.scroll_to_selected();
2274 r || s
2275 }
2276
2277 #[inline]
2280 pub fn move_down(&mut self, n: usize) -> bool {
2281 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2282 let s = self.scroll_to_selected();
2283 r || s
2284 }
2285
2286 #[inline]
2289 pub fn move_left(&mut self, n: usize) -> bool {
2290 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2291 let s = self.scroll_to_selected();
2292 r || s
2293 }
2294
2295 #[inline]
2298 pub fn move_right(&mut self, n: usize) -> bool {
2299 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2300 let s = self.scroll_to_selected();
2301 r || s
2302 }
2303}
2304
2305impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2306 for TableState<Selection>
2307{
2308 fn handle(
2310 &mut self,
2311 event: &crossterm::event::Event,
2312 _keymap: DoubleClick,
2313 ) -> DoubleClickOutcome {
2314 match event {
2315 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2316 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2317 DoubleClickOutcome::ClickClick(col, row)
2318 } else {
2319 DoubleClickOutcome::Continue
2320 }
2321 }
2322 _ => DoubleClickOutcome::Continue,
2323 }
2324 }
2325}
2326
2327pub fn handle_doubleclick_events<Selection: TableSelection>(
2329 state: &mut TableState<Selection>,
2330 event: &crossterm::event::Event,
2331) -> DoubleClickOutcome {
2332 state.handle(event, DoubleClick)
2333}