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> {
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!(
211 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
212 );
213
214 buf.set_string(
215 area.x,
216 area.y,
217 "TableDataIter must implement a valid cloned() for this",
218 Style::default(),
219 );
220 }
221 }
222 DataReprIter::IterText(v, n) => {
223 v.render_cell(ctx, column, n.expect("row"), area, buf)
224 }
225 DataReprIter::IterData(v, n) => {
226 v.render_cell(ctx, column, n.expect("row"), area, buf)
227 }
228 DataReprIter::IterDataRef(v, n) => {
229 v.render_cell(ctx, column, n.expect("row"), area, buf)
230 }
231 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
232 }
233 }
234 }
235}
236
237#[derive(Debug)]
239pub struct TableStyle {
240 pub style: Style,
241 pub header: Option<Style>,
242 pub footer: Option<Style>,
243
244 pub select_row: Option<Style>,
245 pub select_column: Option<Style>,
246 pub select_cell: Option<Style>,
247 pub select_header: Option<Style>,
248 pub select_footer: Option<Style>,
249
250 pub show_row_focus: bool,
251 pub show_column_focus: bool,
252 pub show_cell_focus: bool,
253 pub show_header_focus: bool,
254 pub show_footer_focus: bool,
255
256 pub focus_style: Option<Style>,
257
258 pub block: Option<Block<'static>>,
259 pub border_style: Option<Style>,
260 pub scroll: Option<ScrollStyle>,
261
262 pub non_exhaustive: NonExhaustive,
263}
264
265#[derive(Debug)]
267pub struct TableState<Selection> {
268 pub focus: FocusFlag,
271
272 pub area: Rect,
275 pub inner: Rect,
278
279 pub header_area: Rect,
282 pub table_area: Rect,
285 pub row_areas: Vec<Rect>,
288 pub column_areas: Vec<Rect>,
292 pub column_layout: Vec<Rect>,
296 pub footer_area: Rect,
299
300 pub rows: usize,
303 pub _counted_rows: usize,
305 pub columns: usize,
308
309 pub vscroll: ScrollState,
312 pub hscroll: ScrollState,
315
316 pub selection: Selection,
319
320 pub mouse: MouseFlags,
322
323 pub non_exhaustive: NonExhaustive,
324}
325
326impl<Selection> Default for Table<'_, Selection> {
327 fn default() -> Self {
328 Self {
329 data: Default::default(),
330 no_row_count: Default::default(),
331 header: Default::default(),
332 footer: Default::default(),
333 widths: Default::default(),
334 flex: Default::default(),
335 column_spacing: Default::default(),
336 layout_width: Default::default(),
337 auto_layout_width: Default::default(),
338 block: Default::default(),
339 hscroll: Default::default(),
340 vscroll: Default::default(),
341 header_style: Default::default(),
342 footer_style: Default::default(),
343 style: Default::default(),
344 auto_styles: true,
345 select_row_style: Default::default(),
346 show_row_focus: true,
347 select_column_style: Default::default(),
348 show_column_focus: Default::default(),
349 select_cell_style: Default::default(),
350 show_cell_focus: Default::default(),
351 select_header_style: Default::default(),
352 show_header_focus: Default::default(),
353 select_footer_style: Default::default(),
354 show_footer_focus: Default::default(),
355 focus_style: Default::default(),
356 debug: Default::default(),
357 _phantom: Default::default(),
358 }
359 }
360}
361
362impl<'a, Selection> Table<'a, Selection> {
363 pub fn new() -> Self
365 where
366 Selection: Default,
367 {
368 Self::default()
369 }
370
371 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
376 where
377 R: IntoIterator,
378 R::Item: Into<Row<'a>>,
379 C: IntoIterator,
380 C::Item: Into<Constraint>,
381 Selection: Default,
382 {
383 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
384 let data = TextTableData {
385 rows: rows.into_iter().map(|v| v.into()).collect(),
386 };
387 Self {
388 data: DataRepr::Text(data),
389 widths,
390 ..Default::default()
391 }
392 }
393
394 pub fn rows<T>(mut self, rows: T) -> Self
398 where
399 T: IntoIterator<Item = Row<'a>>,
400 {
401 let rows = rows.into_iter().collect();
402 self.data = DataRepr::Text(TextTableData { rows });
403 self
404 }
405
406 #[inline]
473 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
474 self.widths = data.widths();
475 self.header = data.header();
476 self.footer = data.footer();
477 self.data = DataRepr::Data(Box::new(data));
478 self
479 }
480
481 #[inline]
590 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
591 #[cfg(debug_assertions)]
592 if data.rows().is_none() {
593 use log::warn;
594 warn!("Table::iter - rows is None, this will be slower");
595 }
596 self.header = data.header();
597 self.footer = data.footer();
598 self.widths = data.widths();
599 self.data = DataRepr::Iter(Box::new(data));
600 self
601 }
602
603 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
621 self.no_row_count = no_row_count;
622 self
623 }
624
625 #[inline]
627 pub fn header(mut self, header: Row<'a>) -> Self {
628 self.header = Some(header);
629 self
630 }
631
632 #[inline]
634 pub fn footer(mut self, footer: Row<'a>) -> Self {
635 self.footer = Some(footer);
636 self
637 }
638
639 pub fn widths<I>(mut self, widths: I) -> Self
641 where
642 I: IntoIterator,
643 I::Item: Into<Constraint>,
644 {
645 self.widths = widths.into_iter().map(|v| v.into()).collect();
646 self
647 }
648
649 #[inline]
651 pub fn flex(mut self, flex: Flex) -> Self {
652 self.flex = flex;
653 self
654 }
655
656 #[inline]
658 pub fn column_spacing(mut self, spacing: u16) -> Self {
659 self.column_spacing = spacing;
660 self
661 }
662
663 #[inline]
669 pub fn layout_width(mut self, width: u16) -> Self {
670 self.layout_width = Some(width);
671 self
672 }
673
674 #[inline]
681 pub fn auto_layout_width(mut self) -> Self {
682 self.auto_layout_width = true;
683 self
684 }
685
686 #[inline]
688 pub fn block(mut self, block: Block<'a>) -> Self {
689 self.block = Some(block);
690 self.block = self.block.map(|v| v.style(self.style));
691 self
692 }
693
694 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
696 self.hscroll = Some(scroll.clone().override_horizontal());
697 self.vscroll = Some(scroll.override_vertical());
698 self
699 }
700
701 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
703 self.hscroll = Some(scroll.override_horizontal());
704 self
705 }
706
707 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
709 self.vscroll = Some(scroll.override_vertical());
710 self
711 }
712
713 #[inline]
715 pub fn styles(mut self, styles: TableStyle) -> Self {
716 self.style = styles.style;
717 if styles.header.is_some() {
718 self.header_style = styles.header;
719 }
720 if styles.footer.is_some() {
721 self.footer_style = styles.footer;
722 }
723 if styles.select_row.is_some() {
724 self.select_row_style = styles.select_row;
725 }
726 self.show_row_focus = styles.show_row_focus;
727 if styles.select_column.is_some() {
728 self.select_column_style = styles.select_column;
729 }
730 self.show_column_focus = styles.show_column_focus;
731 if styles.select_cell.is_some() {
732 self.select_cell_style = styles.select_cell;
733 }
734 self.show_cell_focus = styles.show_cell_focus;
735 if styles.select_header.is_some() {
736 self.select_header_style = styles.select_header;
737 }
738 self.show_header_focus = styles.show_header_focus;
739 if styles.select_footer.is_some() {
740 self.select_footer_style = styles.select_footer;
741 }
742 self.show_footer_focus = styles.show_footer_focus;
743 if styles.focus_style.is_some() {
744 self.focus_style = styles.focus_style;
745 }
746 if let Some(border_style) = styles.border_style {
747 self.block = self.block.map(|v| v.border_style(border_style));
748 }
749 self.block = self.block.map(|v| v.style(self.style));
750 if styles.block.is_some() {
751 self.block = styles.block;
752 }
753 if let Some(styles) = styles.scroll {
754 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
755 self.vscroll = self.vscroll.map(|v| v.styles(styles));
756 }
757 self
758 }
759
760 #[inline]
762 pub fn style(mut self, style: Style) -> Self {
763 self.style = style;
764 self.block = self.block.map(|v| v.style(self.style));
765 self
766 }
767
768 #[inline]
770 pub fn header_style(mut self, style: Option<Style>) -> Self {
771 self.header_style = style;
772 self
773 }
774
775 #[inline]
777 pub fn footer_style(mut self, style: Option<Style>) -> Self {
778 self.footer_style = style;
779 self
780 }
781
782 #[inline]
788 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
789 self.auto_styles = auto_styles;
790 self
791 }
792
793 #[inline]
796 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
797 self.select_row_style = select_style;
798 self
799 }
800
801 #[inline]
803 pub fn show_row_focus(mut self, show: bool) -> Self {
804 self.show_row_focus = show;
805 self
806 }
807
808 #[inline]
811 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
812 self.select_column_style = select_style;
813 self
814 }
815
816 #[inline]
818 pub fn show_column_focus(mut self, show: bool) -> Self {
819 self.show_column_focus = show;
820 self
821 }
822
823 #[inline]
826 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
827 self.select_cell_style = select_style;
828 self
829 }
830
831 #[inline]
833 pub fn show_cell_focus(mut self, show: bool) -> Self {
834 self.show_cell_focus = show;
835 self
836 }
837
838 #[inline]
841 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
842 self.select_header_style = select_style;
843 self
844 }
845
846 #[inline]
848 pub fn show_header_focus(mut self, show: bool) -> Self {
849 self.show_header_focus = show;
850 self
851 }
852
853 #[inline]
856 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
857 self.select_footer_style = select_style;
858 self
859 }
860
861 #[inline]
863 pub fn show_footer_focus(mut self, show: bool) -> Self {
864 self.show_footer_focus = show;
865 self
866 }
867
868 #[inline]
874 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
875 self.focus_style = focus_style;
876 self
877 }
878
879 pub fn debug(mut self, debug: bool) -> Self {
881 self.debug = debug;
882 self
883 }
884}
885
886impl<Selection> Table<'_, Selection> {
887 #[inline]
889 fn total_width(&self, area_width: u16) -> u16 {
890 if let Some(layout_width) = self.layout_width {
891 layout_width
892 } else if self.auto_layout_width {
893 let mut width = 0;
894 for w in &self.widths {
895 match w {
896 Constraint::Min(v) => width += *v + self.column_spacing,
897 Constraint::Max(v) => width += *v + self.column_spacing,
898 Constraint::Length(v) => width += *v + self.column_spacing,
899 _ => unimplemented!("Invalid layout constraint."),
900 }
901 }
902 width
903 } else {
904 area_width
905 }
906 }
907
908 #[inline]
910 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
911 let width = self.total_width(width);
912 let area = Rect::new(0, 0, width, 0);
913
914 let (layout, spacers) = Layout::horizontal(&self.widths)
915 .flex(self.flex)
916 .spacing(self.column_spacing)
917 .split_with_spacers(area);
918
919 (width, layout, spacers)
920 }
921
922 #[inline]
924 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
925 let heights = vec![
926 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
927 Constraint::Fill(1),
928 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
929 ];
930
931 Layout::vertical(heights).split(area)
932 }
933}
934
935impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
936where
937 Selection: TableSelection,
938{
939 type State = TableState<Selection>;
940
941 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
942 let iter = self.data.iter();
943 self.render_iter(iter, area, buf, state);
944 }
945}
946
947impl<Selection> StatefulWidget for Table<'_, Selection>
948where
949 Selection: TableSelection,
950{
951 type State = TableState<Selection>;
952
953 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
954 let iter = mem::take(&mut self.data).into_iter();
955 self.render_iter(iter, area, buf, state);
956 }
957}
958
959impl<'a, Selection> Table<'a, Selection>
960where
961 Selection: TableSelection,
962{
963 fn render_iter<'b>(
968 &self,
969 mut data: DataReprIter<'a, 'b>,
970 area: Rect,
971 buf: &mut Buffer,
972 state: &mut TableState<Selection>,
973 ) {
974 if let Some(rows) = data.rows() {
975 state.rows = rows;
976 }
977 state.columns = self.widths.len();
978 state.area = area;
979
980 let sa = ScrollArea::new()
981 .style(self.style)
982 .block(self.block.as_ref())
983 .h_scroll(self.hscroll.as_ref())
984 .v_scroll(self.vscroll.as_ref());
985 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
986
987 let l_rows = self.layout_areas(state.inner);
988 state.header_area = l_rows[0];
989 state.table_area = l_rows[1];
990 state.footer_area = l_rows[2];
991
992 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
994 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
995
996 sa.render(
998 area,
999 buf,
1000 &mut ScrollAreaState::new()
1001 .h_scroll(&mut state.hscroll)
1002 .v_scroll(&mut state.vscroll),
1003 );
1004
1005 self.render_header(
1007 state.columns,
1008 width,
1009 l_columns.as_ref(),
1010 l_spacers.as_ref(),
1011 state.header_area,
1012 buf,
1013 state,
1014 );
1015 self.render_footer(
1016 state.columns,
1017 width,
1018 l_columns.as_ref(),
1019 l_spacers.as_ref(),
1020 state.footer_area,
1021 buf,
1022 state,
1023 );
1024
1025 state.row_areas.clear();
1027 state.vscroll.set_page_len(0);
1028 state.hscroll.set_page_len(area.width as usize);
1029
1030 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1031 let mut row = None;
1032 let mut row_y = state.table_area.y;
1033 let mut row_heights = Vec::new();
1034 #[cfg(debug_assertions)]
1035 let mut insane_offset = false;
1036
1037 let mut ctx = TableContext {
1038 focus: state.focus.get(),
1039 selected_cell: false,
1040 selected_row: false,
1041 selected_column: false,
1042 style: self.style,
1043 row_style: None,
1044 select_style: None,
1045 space_area: Default::default(),
1046 row_area: Default::default(),
1047 non_exhaustive: NonExhaustive,
1048 };
1049
1050 if data.nth(state.vscroll.offset()) {
1051 row = Some(state.vscroll.offset());
1052 loop {
1053 ctx.row_style = data.row_style();
1054 let render_row_area = Rect::new(0, 0, width, data.row_height());
1058 ctx.row_area = render_row_area;
1059 row_buf.resize(render_row_area);
1060 if self.auto_styles {
1061 if let Some(row_style) = ctx.row_style {
1062 row_buf.set_style(render_row_area, row_style);
1063 } else {
1064 row_buf.set_style(render_row_area, self.style);
1065 }
1066 }
1067 row_heights.push(render_row_area.height);
1068
1069 let visible_row_area = Rect::new(
1071 state.table_area.x,
1072 row_y,
1073 state.table_area.width,
1074 render_row_area.height,
1075 )
1076 .intersection(state.table_area);
1077 state.row_areas.push(visible_row_area);
1078 if render_row_area.height == visible_row_area.height {
1080 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1081 }
1082
1083 if render_row_area.height > 0 {
1085 let mut col = 0;
1086 loop {
1087 if col >= state.columns {
1088 break;
1089 }
1090
1091 let render_cell_area = Rect::new(
1092 l_columns[col].x,
1093 0,
1094 l_columns[col].width,
1095 render_row_area.height,
1096 );
1097 ctx.space_area = Rect::new(
1098 l_spacers[col + 1].x,
1099 0,
1100 l_spacers[col + 1].width,
1101 render_row_area.height,
1102 );
1103
1104 if state.selection.is_selected_cell(col, row.expect("row")) {
1105 ctx.selected_cell = true;
1106 ctx.selected_row = false;
1107 ctx.selected_column = false;
1108 ctx.select_style = self.patch_select(
1109 self.select_cell_style,
1110 state.focus.get(),
1111 self.show_cell_focus,
1112 );
1113 } else if state.selection.is_selected_row(row.expect("row")) {
1114 ctx.selected_cell = false;
1115 ctx.selected_row = true;
1116 ctx.selected_column = false;
1117 ctx.select_style = if self.select_row_style.is_some() {
1119 self.patch_select(
1120 self.select_row_style,
1121 state.focus.get(),
1122 self.show_row_focus,
1123 )
1124 } else {
1125 self.patch_select(
1126 Some(self.style),
1127 state.focus.get(),
1128 self.show_row_focus,
1129 )
1130 };
1131 } else if state.selection.is_selected_column(col) {
1132 ctx.selected_cell = false;
1133 ctx.selected_row = false;
1134 ctx.selected_column = true;
1135 ctx.select_style = self.patch_select(
1136 self.select_column_style,
1137 state.focus.get(),
1138 self.show_column_focus,
1139 );
1140 } else {
1141 ctx.selected_cell = false;
1142 ctx.selected_row = false;
1143 ctx.selected_column = false;
1144 ctx.select_style = None;
1145 }
1146
1147 if render_cell_area.right() > state.hscroll.offset as u16
1149 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1150 {
1151 if self.auto_styles {
1152 if let Some(select_style) = ctx.select_style {
1153 row_buf.set_style(render_cell_area, select_style);
1154 row_buf.set_style(ctx.space_area, select_style);
1155 }
1156 }
1157 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1158 }
1159
1160 col += 1;
1161 }
1162
1163 transfer_buffer(
1165 &mut row_buf,
1166 state.hscroll.offset() as u16,
1167 visible_row_area,
1168 buf,
1169 );
1170 }
1171
1172 if visible_row_area.bottom() >= state.table_area.bottom() {
1173 break;
1174 }
1175 if !data.nth(0) {
1176 break;
1177 }
1178 row = Some(row.expect("row").saturating_add(1));
1179 row_y += render_row_area.height;
1180 }
1181 } else {
1182 if data.rows().is_none() || data.rows() == Some(0) {
1187 } else {
1189 #[cfg(debug_assertions)]
1190 {
1191 insane_offset = true;
1192 }
1193 }
1194 }
1195
1196 #[allow(unused_variables)]
1198 let algorithm;
1199 #[allow(unused_assignments)]
1200 {
1201 if let Some(rows) = data.rows() {
1202 algorithm = 0;
1203 let skip_rows = rows
1207 .saturating_sub(row.map_or(0, |v| v + 1))
1208 .saturating_sub(state.table_area.height as usize);
1209 if skip_rows > 0 {
1211 row_heights.clear();
1212 }
1213 let nth_row = skip_rows;
1214 if data.nth(nth_row) {
1216 let mut sum_height = row_heights.iter().sum::<u16>();
1217 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1218 loop {
1219 let row_height = data.row_height();
1220 row_heights.push(row_height);
1221
1222 sum_height += row_height;
1225 if sum_height
1226 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1227 > state.table_area.height
1228 {
1229 let lost_height = row_heights.remove(0);
1230 sum_height -= lost_height;
1231 }
1232
1233 if !data.nth(0) {
1234 break;
1235 }
1236
1237 row = Some(row.expect("row") + 1);
1238 if row.expect("row") > rows {
1240 break;
1241 }
1242 }
1243 while data.nth(0) {
1246 row = Some(row.expect("row") + 1);
1247 }
1248 } else {
1249 }
1252
1253 state.rows = rows;
1254 state._counted_rows = row.map_or(0, |v| v + 1);
1255
1256 if let Some(last_page) = state.calc_last_page(row_heights) {
1258 state.vscroll.set_max_offset(state.rows - last_page);
1259 } else {
1260 state.vscroll.set_max_offset(
1264 state.rows.saturating_sub(state.table_area.height as usize),
1265 );
1266 }
1267 } else if self.no_row_count {
1268 algorithm = 1;
1269
1270 if row.is_some() {
1274 if data.nth(0) {
1275 row = Some(row.expect("row").saturating_add(1));
1277 if data.nth(0) {
1278 row = Some(usize::MAX - 1);
1280 }
1281 }
1282 }
1283
1284 state.rows = row.map_or(0, |v| v + 1);
1285 state._counted_rows = row.map_or(0, |v| v + 1);
1286 state.vscroll.set_max_offset(usize::MAX - 1);
1288 if state.vscroll.page_len() == 0 {
1289 state.vscroll.set_page_len(state.table_area.height as usize);
1290 }
1291 } else {
1292 algorithm = 2;
1293
1294 let mut sum_height = row_heights.iter().sum::<u16>();
1296 while data.nth(0) {
1297 let row_height = data.row_height();
1298 row_heights.push(row_height);
1299
1300 sum_height += row_height;
1303 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1304 > state.table_area.height
1305 {
1306 let lost_height = row_heights.remove(0);
1307 sum_height -= lost_height;
1308 }
1309 row = Some(row.map_or(0, |v| v + 1));
1310 }
1311
1312 state.rows = row.map_or(0, |v| v + 1);
1313 state._counted_rows = row.map_or(0, |v| v + 1);
1314
1315 if let Some(last_page) = state.calc_last_page(row_heights) {
1317 state.vscroll.set_max_offset(state.rows - last_page);
1318 } else {
1319 state.vscroll.set_max_offset(0);
1320 }
1321 }
1322 }
1323 {
1324 state
1325 .hscroll
1326 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1327 }
1328
1329 #[cfg(debug_assertions)]
1330 {
1331 use std::fmt::Write;
1332 let mut msg = String::new();
1333 if insane_offset {
1334 _ = write!(
1335 msg,
1336 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1337 state.vscroll.offset(),
1338 state.rows,
1339 state._counted_rows,
1340 algorithm
1341 );
1342 }
1343 if state.rows != state._counted_rows {
1344 _ = write!(
1345 msg,
1346 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1347 state.rows, state._counted_rows, algorithm
1348 );
1349 }
1350 if !msg.is_empty() {
1351 use log::warn;
1352 use ratatui::style::Stylize;
1353 use ratatui::text::Text;
1354
1355 warn!("{}", &msg);
1356 Text::from(msg)
1357 .white()
1358 .on_red()
1359 .render(state.table_area, buf);
1360 }
1361 }
1362 }
1363
1364 #[allow(clippy::too_many_arguments)]
1365 fn render_footer(
1366 &self,
1367 columns: usize,
1368 width: u16,
1369 l_columns: &[Rect],
1370 l_spacers: &[Rect],
1371 area: Rect,
1372 buf: &mut Buffer,
1373 state: &mut TableState<Selection>,
1374 ) {
1375 if let Some(footer) = &self.footer {
1376 let render_row_area = Rect::new(0, 0, width, footer.height);
1377 let mut row_buf = Buffer::empty(render_row_area);
1378
1379 row_buf.set_style(render_row_area, self.style);
1380 if let Some(footer_style) = footer.style {
1381 row_buf.set_style(render_row_area, footer_style);
1382 } else if let Some(footer_style) = self.footer_style {
1383 row_buf.set_style(render_row_area, footer_style);
1384 }
1385
1386 let mut col = 0;
1387 loop {
1388 if col >= columns {
1389 break;
1390 }
1391
1392 let render_cell_area =
1393 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1394 let render_space_area = Rect::new(
1395 l_spacers[col + 1].x,
1396 0,
1397 l_spacers[col + 1].width,
1398 area.height,
1399 );
1400
1401 if state.selection.is_selected_column(col) {
1402 if let Some(selected_style) = self.patch_select(
1403 self.select_footer_style,
1404 state.focus.get(),
1405 self.show_footer_focus,
1406 ) {
1407 row_buf.set_style(render_cell_area, selected_style);
1408 row_buf.set_style(render_space_area, selected_style);
1409 }
1410 };
1411
1412 if render_cell_area.right() > state.hscroll.offset as u16
1414 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1415 {
1416 if let Some(cell) = footer.cells.get(col) {
1417 if let Some(cell_style) = cell.style {
1418 row_buf.set_style(render_cell_area, cell_style);
1419 }
1420 cell.content.clone().render(render_cell_area, &mut row_buf);
1421 }
1422 }
1423
1424 col += 1;
1425 }
1426
1427 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1429 }
1430 }
1431
1432 #[allow(clippy::too_many_arguments)]
1433 fn render_header(
1434 &self,
1435 columns: usize,
1436 width: u16,
1437 l_columns: &[Rect],
1438 l_spacers: &[Rect],
1439 area: Rect,
1440 buf: &mut Buffer,
1441 state: &mut TableState<Selection>,
1442 ) {
1443 if let Some(header) = &self.header {
1444 let render_row_area = Rect::new(0, 0, width, header.height);
1445 let mut row_buf = Buffer::empty(render_row_area);
1446
1447 row_buf.set_style(render_row_area, self.style);
1448 if let Some(header_style) = header.style {
1449 row_buf.set_style(render_row_area, header_style);
1450 } else if let Some(header_style) = self.header_style {
1451 row_buf.set_style(render_row_area, header_style);
1452 }
1453
1454 let mut col = 0;
1455 loop {
1456 if col >= columns {
1457 break;
1458 }
1459
1460 let render_cell_area =
1461 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1462 let render_space_area = Rect::new(
1463 l_spacers[col + 1].x,
1464 0,
1465 l_spacers[col + 1].width,
1466 area.height,
1467 );
1468
1469 if state.selection.is_selected_column(col) {
1470 if let Some(selected_style) = self.patch_select(
1471 self.select_header_style,
1472 state.focus.get(),
1473 self.show_header_focus,
1474 ) {
1475 row_buf.set_style(render_cell_area, selected_style);
1476 row_buf.set_style(render_space_area, selected_style);
1477 }
1478 };
1479
1480 if render_cell_area.right() > state.hscroll.offset as u16
1482 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1483 {
1484 if let Some(cell) = header.cells.get(col) {
1485 if let Some(cell_style) = cell.style {
1486 row_buf.set_style(render_cell_area, cell_style);
1487 }
1488 cell.content.clone().render(render_cell_area, &mut row_buf);
1489 }
1490 }
1491
1492 col += 1;
1493 }
1494
1495 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1497 }
1498 }
1499
1500 fn calculate_column_areas(
1501 &self,
1502 columns: usize,
1503 l_columns: &[Rect],
1504 l_spacers: &[Rect],
1505 state: &mut TableState<Selection>,
1506 ) {
1507 state.column_areas.clear();
1508 state.column_layout.clear();
1509
1510 let mut col = 0;
1511 let shift = state.hscroll.offset() as isize;
1512 loop {
1513 if col >= columns {
1514 break;
1515 }
1516
1517 state.column_layout.push(Rect::new(
1518 l_columns[col].x,
1519 0,
1520 l_columns[col].width + l_spacers[col + 1].width,
1521 0,
1522 ));
1523
1524 let cell_x1 = l_columns[col].x as isize;
1525 let cell_x2 =
1526 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1527
1528 let squish_x1 = cell_x1.saturating_sub(shift);
1529 let squish_x2 = cell_x2.saturating_sub(shift);
1530
1531 let abs_x1 = max(0, squish_x1) as u16;
1532 let abs_x2 = max(0, squish_x2) as u16;
1533
1534 let v_area = Rect::new(
1535 state.table_area.x + abs_x1,
1536 state.table_area.y,
1537 abs_x2 - abs_x1,
1538 state.table_area.height,
1539 );
1540 state
1541 .column_areas
1542 .push(v_area.intersection(state.table_area));
1543
1544 col += 1;
1545 }
1546 }
1547
1548 #[expect(clippy::collapsible_else_if)]
1549 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1550 if let Some(style) = style {
1551 if let Some(focus_style) = self.focus_style {
1552 if focus && show {
1553 Some(style.patch(focus_style))
1554 } else {
1555 Some(fallback_select_style(style))
1556 }
1557 } else {
1558 if focus && show {
1559 Some(revert_style(style))
1560 } else {
1561 Some(fallback_select_style(style))
1562 }
1563 }
1564 } else {
1565 None
1566 }
1567 }
1568}
1569
1570impl Default for TableStyle {
1571 fn default() -> Self {
1572 Self {
1573 style: Default::default(),
1574 header: None,
1575 footer: None,
1576 select_row: None,
1577 select_column: None,
1578 select_cell: None,
1579 select_header: None,
1580 select_footer: None,
1581 show_row_focus: true, show_column_focus: false,
1583 show_cell_focus: false,
1584 show_header_focus: false,
1585 show_footer_focus: false,
1586 focus_style: None,
1587 block: None,
1588 border_style: None,
1589 scroll: None,
1590 non_exhaustive: NonExhaustive,
1591 }
1592 }
1593}
1594
1595impl<Selection: Clone> Clone for TableState<Selection> {
1596 fn clone(&self) -> Self {
1597 Self {
1598 focus: FocusFlag::named(self.focus.name()),
1599 area: self.area,
1600 inner: self.inner,
1601 header_area: self.header_area,
1602 table_area: self.table_area,
1603 row_areas: self.row_areas.clone(),
1604 column_areas: self.column_areas.clone(),
1605 column_layout: self.column_layout.clone(),
1606 footer_area: self.footer_area,
1607 rows: self.rows,
1608 _counted_rows: self._counted_rows,
1609 columns: self.columns,
1610 vscroll: self.vscroll.clone(),
1611 hscroll: self.hscroll.clone(),
1612 selection: self.selection.clone(),
1613 mouse: Default::default(),
1614 non_exhaustive: NonExhaustive,
1615 }
1616 }
1617}
1618
1619impl<Selection: Default> Default for TableState<Selection> {
1620 fn default() -> Self {
1621 Self {
1622 focus: Default::default(),
1623 area: Default::default(),
1624 inner: Default::default(),
1625 header_area: Default::default(),
1626 table_area: Default::default(),
1627 row_areas: Default::default(),
1628 column_areas: Default::default(),
1629 column_layout: Default::default(),
1630 footer_area: Default::default(),
1631 rows: Default::default(),
1632 _counted_rows: Default::default(),
1633 columns: Default::default(),
1634 vscroll: Default::default(),
1635 hscroll: Default::default(),
1636 selection: Default::default(),
1637 mouse: Default::default(),
1638 non_exhaustive: NonExhaustive,
1639 }
1640 }
1641}
1642
1643impl<Selection> HasFocus for TableState<Selection> {
1644 fn build(&self, builder: &mut FocusBuilder) {
1645 builder.leaf_widget(self);
1646 }
1647
1648 #[inline]
1649 fn focus(&self) -> FocusFlag {
1650 self.focus.clone()
1651 }
1652
1653 #[inline]
1654 fn area(&self) -> Rect {
1655 self.area
1656 }
1657}
1658
1659impl<Selection> RelocatableState for TableState<Selection> {
1660 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1661 self.area = relocate_area(self.area, shift, clip);
1662 self.inner = relocate_area(self.inner, shift, clip);
1663 self.table_area = relocate_area(self.table_area, shift, clip);
1664 self.footer_area = relocate_area(self.footer_area, shift, clip);
1665 self.header_area = relocate_area(self.header_area, shift, clip);
1666
1667 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1668 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1669 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1670
1671 self.hscroll.relocate(shift, clip);
1672 self.vscroll.relocate(shift, clip);
1673 }
1674}
1675
1676impl<Selection> TableState<Selection> {
1677 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1678 let mut sum_heights = 0;
1679 let mut n_rows = 0;
1680 while let Some(h) = row_heights.pop() {
1681 sum_heights += h;
1682 n_rows += 1;
1683 if sum_heights >= self.table_area.height {
1684 break;
1685 }
1686 }
1687
1688 if sum_heights < self.table_area.height {
1689 None
1690 } else {
1691 Some(n_rows)
1692 }
1693 }
1694}
1695
1696impl<Selection> TableState<Selection>
1698where
1699 Selection: Default,
1700{
1701 pub fn new() -> Self {
1702 Self::default()
1703 }
1704
1705 pub fn named(name: &str) -> Self {
1706 Self {
1707 focus: FocusFlag::named(name),
1708 ..TableState::default()
1709 }
1710 }
1711}
1712
1713impl<Selection> TableState<Selection> {
1715 #[inline]
1717 pub fn rows(&self) -> usize {
1718 self.rows
1719 }
1720
1721 #[inline]
1723 pub fn columns(&self) -> usize {
1724 self.columns
1725 }
1726}
1727
1728impl<Selection> TableState<Selection> {
1730 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1736 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1737 return None;
1738 }
1739
1740 let mut areas = Vec::new();
1741
1742 let r = self.row_areas[row - self.vscroll.offset()];
1743 for c in &self.column_areas {
1744 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1745 }
1746
1747 Some((r, areas))
1748 }
1749
1750 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1752 let col = self.column_at_clicked(pos);
1753 let row = self.row_at_clicked(pos);
1754
1755 match (col, row) {
1756 (Some(col), Some(row)) => Some((col, row)),
1757 _ => None,
1758 }
1759 }
1760
1761 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1763 self.mouse.column_at(&self.column_areas, pos.0)
1764 }
1765
1766 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1768 self.mouse
1769 .row_at(&self.row_areas, pos.1)
1770 .map(|v| self.vscroll.offset() + v)
1771 }
1772
1773 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1776 let col = self.column_at_drag(pos);
1777 let row = self.row_at_drag(pos);
1778
1779 (col, row)
1780 }
1781
1782 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1789 match self
1790 .mouse
1791 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1792 {
1793 Ok(v) => self.vscroll.offset() + v,
1794 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1795 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1796 }
1797 }
1798
1799 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1803 match self
1804 .mouse
1805 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1806 {
1807 Ok(v) => v,
1808 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1809 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1810 }
1811 }
1812}
1813
1814impl<Selection: TableSelection> TableState<Selection> {
1816 pub fn clear_offset(&mut self) {
1818 self.vscroll.set_offset(0);
1819 self.hscroll.set_offset(0);
1820 }
1821
1822 pub fn row_max_offset(&self) -> usize {
1827 self.vscroll.max_offset()
1828 }
1829
1830 pub fn row_offset(&self) -> usize {
1832 self.vscroll.offset()
1833 }
1834
1835 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1842 self.vscroll.set_offset(offset)
1843 }
1844
1845 pub fn page_len(&self) -> usize {
1847 self.vscroll.page_len()
1848 }
1849
1850 pub fn row_scroll_by(&self) -> usize {
1852 self.vscroll.scroll_by()
1853 }
1854
1855 pub fn x_max_offset(&self) -> usize {
1860 self.hscroll.max_offset()
1861 }
1862
1863 pub fn x_offset(&self) -> usize {
1865 self.hscroll.offset()
1866 }
1867
1868 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1875 self.hscroll.set_offset(offset)
1876 }
1877
1878 pub fn page_width(&self) -> usize {
1880 self.hscroll.page_len()
1881 }
1882
1883 pub fn x_scroll_by(&self) -> usize {
1885 self.hscroll.scroll_by()
1886 }
1887
1888 pub fn scroll_to_selected(&mut self) -> bool {
1891 if let Some(selected) = self.selection.lead_selection() {
1892 let c = self.scroll_to_col(selected.0);
1893 let r = self.scroll_to_row(selected.1);
1894 r || c
1895 } else {
1896 false
1897 }
1898 }
1899
1900 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1903 if pos >= self.rows {
1904 false
1905 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1906 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1908 if heights < self.table_area.height {
1909 false
1910 } else {
1911 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1912 }
1913 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1914 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1915 } else if pos < self.row_offset() {
1916 self.set_row_offset(pos)
1917 } else {
1918 false
1919 }
1920 }
1921
1922 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1924 if let Some(col) = self.column_layout.get(pos) {
1925 if (col.left() as usize) < self.x_offset() {
1926 self.set_x_offset(col.x as usize)
1927 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1928 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
1929 } else {
1930 false
1931 }
1932 } else {
1933 false
1934 }
1935 }
1936
1937 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
1939 if pos >= self.x_offset().saturating_add(self.page_width()) {
1940 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
1941 } else if pos < self.x_offset() {
1942 self.set_x_offset(pos)
1943 } else {
1944 false
1945 }
1946 }
1947
1948 pub fn scroll_up(&mut self, n: usize) -> bool {
1950 self.vscroll.scroll_up(n)
1951 }
1952
1953 pub fn scroll_down(&mut self, n: usize) -> bool {
1955 self.vscroll.scroll_down(n)
1956 }
1957
1958 pub fn scroll_left(&mut self, n: usize) -> bool {
1960 self.hscroll.scroll_left(n)
1961 }
1962
1963 pub fn scroll_right(&mut self, n: usize) -> bool {
1965 self.hscroll.scroll_right(n)
1966 }
1967}
1968
1969impl TableState<RowSelection> {
1970 pub fn items_added(&mut self, pos: usize, n: usize) {
1973 self.vscroll.items_added(pos, n);
1974 self.selection.items_added(pos, n);
1975 self.rows += n;
1976 }
1977
1978 pub fn items_removed(&mut self, pos: usize, n: usize) {
1981 self.vscroll.items_removed(pos, n);
1982 self.selection
1983 .items_removed(pos, n, self.rows.saturating_sub(1));
1984 self.rows -= n;
1985 }
1986
1987 #[inline]
1989 pub fn set_scroll_selection(&mut self, scroll: bool) {
1990 self.selection.set_scroll_selected(scroll);
1991 }
1992
1993 #[inline]
1995 pub fn clear_selection(&mut self) {
1996 self.selection.clear();
1997 }
1998
1999 #[inline]
2001 pub fn has_selection(&mut self) -> bool {
2002 self.selection.has_selection()
2003 }
2004
2005 #[inline]
2008 pub fn selected(&self) -> Option<usize> {
2009 self.selection.selected()
2010 }
2011
2012 #[inline]
2015 #[allow(clippy::manual_filter)]
2016 pub fn selected_checked(&self) -> Option<usize> {
2017 if let Some(selected) = self.selection.selected() {
2018 if selected < self.rows {
2019 Some(selected)
2020 } else {
2021 None
2022 }
2023 } else {
2024 None
2025 }
2026 }
2027
2028 #[inline]
2031 pub fn select(&mut self, row: Option<usize>) -> bool {
2032 self.selection.select(row)
2033 }
2034
2035 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2039 if self.vscroll.max_offset() > 0 {
2040 (self.rows * offset) / self.vscroll.max_offset()
2041 } else {
2042 0 }
2044 }
2045
2046 #[inline]
2049 pub fn move_to(&mut self, row: usize) -> bool {
2050 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2051 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2052 r || s
2053 }
2054
2055 #[inline]
2058 pub fn move_up(&mut self, n: usize) -> bool {
2059 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2060 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2061 r || s
2062 }
2063
2064 #[inline]
2067 pub fn move_down(&mut self, n: usize) -> bool {
2068 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2069 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2070 r || s
2071 }
2072}
2073
2074impl TableState<RowSetSelection> {
2075 #[inline]
2077 pub fn clear_selection(&mut self) {
2078 self.selection.clear();
2079 }
2080
2081 #[inline]
2083 pub fn has_selection(&mut self) -> bool {
2084 self.selection.has_selection()
2085 }
2086
2087 #[inline]
2089 pub fn selected(&self) -> HashSet<usize> {
2090 self.selection.selected()
2091 }
2092
2093 #[inline]
2100 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2101 self.selection.set_lead(row, extend)
2102 }
2103
2104 #[inline]
2106 pub fn lead(&self) -> Option<usize> {
2107 self.selection.lead()
2108 }
2109
2110 #[inline]
2112 pub fn anchor(&self) -> Option<usize> {
2113 self.selection.anchor()
2114 }
2115
2116 #[inline]
2119 pub fn retire_selection(&mut self) {
2120 self.selection.retire_selection();
2121 }
2122
2123 #[inline]
2126 pub fn add_selected(&mut self, idx: usize) {
2127 self.selection.add(idx);
2128 }
2129
2130 #[inline]
2133 pub fn remove_selected(&mut self, idx: usize) {
2134 self.selection.remove(idx);
2135 }
2136
2137 #[inline]
2140 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2141 let r = self
2142 .selection
2143 .move_to(row, self.rows.saturating_sub(1), extend);
2144 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2145 r || s
2146 }
2147
2148 #[inline]
2151 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2152 let r = self
2153 .selection
2154 .move_up(n, self.rows.saturating_sub(1), extend);
2155 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2156 r || s
2157 }
2158
2159 #[inline]
2162 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2163 let r = self
2164 .selection
2165 .move_down(n, self.rows.saturating_sub(1), extend);
2166 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2167 r || s
2168 }
2169}
2170
2171impl TableState<CellSelection> {
2172 #[inline]
2173 pub fn clear_selection(&mut self) {
2174 self.selection.clear();
2175 }
2176
2177 #[inline]
2178 pub fn has_selection(&mut self) -> bool {
2179 self.selection.has_selection()
2180 }
2181
2182 #[inline]
2184 pub fn selected(&self) -> Option<(usize, usize)> {
2185 self.selection.selected()
2186 }
2187
2188 #[inline]
2190 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2191 self.selection.select_cell(select)
2192 }
2193
2194 #[inline]
2196 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2197 if let Some(row) = row {
2198 self.selection
2199 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2200 } else {
2201 self.selection.select_row(None)
2202 }
2203 }
2204
2205 #[inline]
2207 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2208 if let Some(column) = column {
2209 self.selection
2210 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2211 } else {
2212 self.selection.select_column(None)
2213 }
2214 }
2215
2216 #[inline]
2218 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2219 let r = self.selection.move_to(
2220 select,
2221 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2222 );
2223 let s = self.scroll_to_selected();
2224 r || s
2225 }
2226
2227 #[inline]
2229 pub fn move_to_row(&mut self, row: usize) -> bool {
2230 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2231 let s = self.scroll_to_selected();
2232 r || s
2233 }
2234
2235 #[inline]
2237 pub fn move_to_col(&mut self, col: usize) -> bool {
2238 let r = self
2239 .selection
2240 .move_to_col(col, self.columns.saturating_sub(1));
2241 let s = self.scroll_to_selected();
2242 r || s
2243 }
2244
2245 #[inline]
2248 pub fn move_up(&mut self, n: usize) -> bool {
2249 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2250 let s = self.scroll_to_selected();
2251 r || s
2252 }
2253
2254 #[inline]
2257 pub fn move_down(&mut self, n: usize) -> bool {
2258 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2259 let s = self.scroll_to_selected();
2260 r || s
2261 }
2262
2263 #[inline]
2266 pub fn move_left(&mut self, n: usize) -> bool {
2267 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2268 let s = self.scroll_to_selected();
2269 r || s
2270 }
2271
2272 #[inline]
2275 pub fn move_right(&mut self, n: usize) -> bool {
2276 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2277 let s = self.scroll_to_selected();
2278 r || s
2279 }
2280}
2281
2282impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2283 for TableState<Selection>
2284{
2285 fn handle(
2287 &mut self,
2288 event: &crossterm::event::Event,
2289 _keymap: DoubleClick,
2290 ) -> DoubleClickOutcome {
2291 match event {
2292 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2293 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2294 DoubleClickOutcome::ClickClick(col, row)
2295 } else {
2296 DoubleClickOutcome::Continue
2297 }
2298 }
2299 _ => DoubleClickOutcome::Continue,
2300 }
2301 }
2302}
2303
2304pub fn handle_doubleclick_events<Selection: TableSelection>(
2306 state: &mut TableState<Selection>,
2307 event: &crossterm::event::Event,
2308) -> DoubleClickOutcome {
2309 state.handle(event, DoubleClick)
2310}