1#![allow(clippy::collapsible_if)]
2
3use crate::_private::NonExhaustive;
4use crate::event::{DoubleClick, DoubleClickOutcome};
5use crate::selection::{CellSelection, RowSelection, RowSetSelection};
6use crate::table::data::{DataRepr, DataReprIter};
7use crate::textdata::{Row, TextTableData};
8use crate::util::{fallback_select_style, revert_style, transfer_buffer};
9use crate::{TableContext, TableData, TableDataIter, TableSelection};
10use rat_event::util::MouseFlags;
11use rat_event::{ct_event, HandleEvent};
12use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
13use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
14use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
15use ratatui::buffer::Buffer;
16use ratatui::layout::{Constraint, Flex, Layout, Rect};
17use ratatui::style::Style;
18#[cfg(feature = "unstable-widget-ref")]
19use ratatui::widgets::StatefulWidgetRef;
20use ratatui::widgets::{Block, StatefulWidget, Widget};
21use std::cmp::{max, min};
22use std::collections::HashSet;
23use std::fmt::Debug;
24use std::marker::PhantomData;
25use std::mem;
26use std::rc::Rc;
27
28#[derive(Debug)]
43pub struct Table<'a, Selection> {
44 data: DataRepr<'a>,
45 no_row_count: bool,
46
47 header: Option<Row<'a>>,
48 footer: Option<Row<'a>>,
49
50 widths: Vec<Constraint>,
51 flex: Flex,
52 column_spacing: u16,
53 layout_width: Option<u16>,
54 auto_layout_width: bool,
55
56 block: Option<Block<'a>>,
57 hscroll: Option<Scroll<'a>>,
58 vscroll: Option<Scroll<'a>>,
59
60 header_style: Option<Style>,
61 footer_style: Option<Style>,
62 style: Style,
63
64 select_row_style: Option<Style>,
65 show_row_focus: bool,
66 select_column_style: Option<Style>,
67 show_column_focus: bool,
68 select_cell_style: Option<Style>,
69 show_cell_focus: bool,
70 select_header_style: Option<Style>,
71 show_header_focus: bool,
72 select_footer_style: Option<Style>,
73 show_footer_focus: bool,
74
75 focus_style: Option<Style>,
76
77 debug: bool,
78
79 _phantom: PhantomData<Selection>,
80}
81
82mod data {
83 use crate::textdata::TextTableData;
84 use crate::{TableContext, TableData, TableDataIter};
85 #[cfg(debug_assertions)]
86 use log::warn;
87 use ratatui::buffer::Buffer;
88 use ratatui::layout::Rect;
89 use ratatui::style::{Style, Stylize};
90 use std::fmt::{Debug, Formatter};
91
92 #[derive(Default)]
93 pub(super) enum DataRepr<'a> {
94 #[default]
95 None,
96 Text(TextTableData<'a>),
97 Data(Box<dyn TableData<'a> + 'a>),
98 Iter(Box<dyn TableDataIter<'a> + 'a>),
99 }
100
101 impl<'a> DataRepr<'a> {
102 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
103 match self {
104 DataRepr::None => DataReprIter::None,
105 DataRepr::Text(v) => DataReprIter::IterText(v, None),
106 DataRepr::Data(v) => DataReprIter::IterData(v, None),
107 DataRepr::Iter(v) => DataReprIter::IterIter(v),
108 }
109 }
110
111 #[cfg(feature = "unstable-widget-ref")]
112 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
113 match self {
114 DataRepr::None => DataReprIter::None,
115 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
116 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
117 DataRepr::Iter(v) => {
118 if let Some(v) = v.cloned() {
120 DataReprIter::IterIter(v)
121 } else {
122 DataReprIter::Invalid(None)
123 }
124 }
125 }
126 }
127 }
128
129 impl Debug for DataRepr<'_> {
130 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("Data").finish()
132 }
133 }
134
135 #[derive(Default)]
136 pub(super) enum DataReprIter<'a, 'b> {
137 #[default]
138 None,
139 #[allow(dead_code)]
140 Invalid(Option<usize>),
141 IterText(TextTableData<'a>, Option<usize>),
142 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
143 #[allow(dead_code)]
144 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
145 IterIter(Box<dyn TableDataIter<'a> + 'a>),
146 }
147
148 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
149 fn rows(&self) -> Option<usize> {
150 match self {
151 DataReprIter::None => Some(0),
152 DataReprIter::Invalid(_) => Some(1),
153 DataReprIter::IterText(v, _) => Some(v.rows.len()),
154 DataReprIter::IterData(v, _) => Some(v.rows()),
155 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
156 DataReprIter::IterIter(v) => v.rows(),
157 }
158 }
159
160 fn nth(&mut self, n: usize) -> bool {
161 let incr = |row: &mut Option<usize>, rows: usize| match *row {
162 None => {
163 *row = Some(n);
164 *row < Some(rows)
165 }
166 Some(w) => {
167 *row = Some(w.saturating_add(n).saturating_add(1));
168 *row < Some(rows)
169 }
170 };
171
172 match self {
173 DataReprIter::None => false,
174 DataReprIter::Invalid(row) => incr(row, 1),
175 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
176 DataReprIter::IterData(v, row) => incr(row, v.rows()),
177 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
178 DataReprIter::IterIter(v) => v.nth(n),
179 }
180 }
181
182 fn row_height(&self) -> u16 {
184 match self {
185 DataReprIter::None => 1,
186 DataReprIter::Invalid(_) => 1,
187 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
188 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
189 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
190 DataReprIter::IterIter(v) => v.row_height(),
191 }
192 }
193
194 fn row_style(&self) -> Option<Style> {
195 match self {
196 DataReprIter::None => None,
197 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
198 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
199 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
200 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
201 DataReprIter::IterIter(v) => v.row_style(),
202 }
203 }
204
205 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
207 match self {
208 DataReprIter::None => {}
209 DataReprIter::Invalid(_) => {
210 if column == 0 {
211 #[cfg(debug_assertions)]
212 warn!("Table::render_ref - TableDataIter must implement a valid cloned() for this to work.");
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 select_row_style: Default::default(),
345 show_row_focus: true,
346 select_column_style: Default::default(),
347 show_column_focus: Default::default(),
348 select_cell_style: Default::default(),
349 show_cell_focus: Default::default(),
350 select_header_style: Default::default(),
351 show_header_focus: Default::default(),
352 select_footer_style: Default::default(),
353 show_footer_focus: Default::default(),
354 focus_style: Default::default(),
355 debug: Default::default(),
356 _phantom: Default::default(),
357 }
358 }
359}
360
361impl<'a, Selection> Table<'a, Selection> {
362 pub fn new() -> Self
364 where
365 Selection: Default,
366 {
367 Self::default()
368 }
369
370 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
375 where
376 R: IntoIterator,
377 R::Item: Into<Row<'a>>,
378 C: IntoIterator,
379 C::Item: Into<Constraint>,
380 Selection: Default,
381 {
382 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
383 let data = TextTableData {
384 rows: rows.into_iter().map(|v| v.into()).collect(),
385 };
386 Self {
387 data: DataRepr::Text(data),
388 widths,
389 ..Default::default()
390 }
391 }
392
393 pub fn rows<T>(mut self, rows: T) -> Self
397 where
398 T: IntoIterator<Item = Row<'a>>,
399 {
400 let rows = rows.into_iter().collect();
401 self.data = DataRepr::Text(TextTableData { rows });
402 self
403 }
404
405 #[inline]
472 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
473 self.widths = data.widths();
474 self.header = data.header();
475 self.footer = data.footer();
476 self.data = DataRepr::Data(Box::new(data));
477 self
478 }
479
480 #[inline]
589 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
590 #[cfg(debug_assertions)]
591 if data.rows().is_none() {
592 use log::warn;
593 warn!("Table::iter - rows is None, this will be slower");
594 }
595 self.header = data.header();
596 self.footer = data.footer();
597 self.widths = data.widths();
598 self.data = DataRepr::Iter(Box::new(data));
599 self
600 }
601
602 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
620 self.no_row_count = no_row_count;
621 self
622 }
623
624 #[inline]
626 pub fn header(mut self, header: Row<'a>) -> Self {
627 self.header = Some(header);
628 self
629 }
630
631 #[inline]
633 pub fn footer(mut self, footer: Row<'a>) -> Self {
634 self.footer = Some(footer);
635 self
636 }
637
638 pub fn widths<I>(mut self, widths: I) -> Self
640 where
641 I: IntoIterator,
642 I::Item: Into<Constraint>,
643 {
644 self.widths = widths.into_iter().map(|v| v.into()).collect();
645 self
646 }
647
648 #[inline]
650 pub fn flex(mut self, flex: Flex) -> Self {
651 self.flex = flex;
652 self
653 }
654
655 #[inline]
657 pub fn column_spacing(mut self, spacing: u16) -> Self {
658 self.column_spacing = spacing;
659 self
660 }
661
662 #[inline]
666 pub fn layout_width(mut self, width: u16) -> Self {
667 self.layout_width = Some(width);
668 self
669 }
670
671 #[inline]
678 pub fn auto_layout_width(mut self) -> Self {
679 self.auto_layout_width = true;
680 self
681 }
682
683 #[inline]
685 pub fn block(mut self, block: Block<'a>) -> Self {
686 self.block = Some(block);
687 self.block = self.block.map(|v| v.style(self.style));
688 self
689 }
690
691 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
693 self.hscroll = Some(scroll.clone().override_horizontal());
694 self.vscroll = Some(scroll.override_vertical());
695 self
696 }
697
698 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
700 self.hscroll = Some(scroll.override_horizontal());
701 self
702 }
703
704 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
706 self.vscroll = Some(scroll.override_vertical());
707 self
708 }
709
710 #[inline]
712 pub fn styles(mut self, styles: TableStyle) -> Self {
713 self.style = styles.style;
714 if styles.header.is_some() {
715 self.header_style = styles.header;
716 }
717 if styles.footer.is_some() {
718 self.footer_style = styles.footer;
719 }
720 if styles.select_row.is_some() {
721 self.select_row_style = styles.select_row;
722 }
723 self.show_row_focus = styles.show_row_focus;
724 if styles.select_column.is_some() {
725 self.select_column_style = styles.select_column;
726 }
727 self.show_column_focus = styles.show_column_focus;
728 if styles.select_cell.is_some() {
729 self.select_cell_style = styles.select_cell;
730 }
731 self.show_cell_focus = styles.show_cell_focus;
732 if styles.select_header.is_some() {
733 self.select_header_style = styles.select_header;
734 }
735 self.show_header_focus = styles.show_header_focus;
736 if styles.select_footer.is_some() {
737 self.select_footer_style = styles.select_footer;
738 }
739 self.show_footer_focus = styles.show_footer_focus;
740 if styles.focus_style.is_some() {
741 self.focus_style = styles.focus_style;
742 }
743 if let Some(border_style) = styles.border_style {
744 self.block = self.block.map(|v| v.border_style(border_style));
745 }
746 self.block = self.block.map(|v| v.style(self.style));
747 if styles.block.is_some() {
748 self.block = styles.block;
749 }
750 if let Some(styles) = styles.scroll {
751 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
752 self.vscroll = self.vscroll.map(|v| v.styles(styles));
753 }
754 self
755 }
756
757 #[inline]
759 pub fn style(mut self, style: Style) -> Self {
760 self.style = style;
761 self.block = self.block.map(|v| v.style(self.style));
762 self
763 }
764
765 #[inline]
767 pub fn header_style(mut self, style: Option<Style>) -> Self {
768 self.header_style = style;
769 self
770 }
771
772 #[inline]
774 pub fn footer_style(mut self, style: Option<Style>) -> Self {
775 self.footer_style = style;
776 self
777 }
778
779 #[inline]
782 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
783 self.select_row_style = select_style;
784 self
785 }
786
787 #[inline]
789 pub fn show_row_focus(mut self, show: bool) -> Self {
790 self.show_row_focus = show;
791 self
792 }
793
794 #[inline]
797 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
798 self.select_column_style = select_style;
799 self
800 }
801
802 #[inline]
804 pub fn show_column_focus(mut self, show: bool) -> Self {
805 self.show_column_focus = show;
806 self
807 }
808
809 #[inline]
812 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
813 self.select_cell_style = select_style;
814 self
815 }
816
817 #[inline]
819 pub fn show_cell_focus(mut self, show: bool) -> Self {
820 self.show_cell_focus = show;
821 self
822 }
823
824 #[inline]
827 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
828 self.select_header_style = select_style;
829 self
830 }
831
832 #[inline]
834 pub fn show_header_focus(mut self, show: bool) -> Self {
835 self.show_header_focus = show;
836 self
837 }
838
839 #[inline]
842 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
843 self.select_footer_style = select_style;
844 self
845 }
846
847 #[inline]
849 pub fn show_footer_focus(mut self, show: bool) -> Self {
850 self.show_footer_focus = show;
851 self
852 }
853
854 #[inline]
860 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
861 self.focus_style = focus_style;
862 self
863 }
864
865 pub fn debug(mut self, debug: bool) -> Self {
867 self.debug = debug;
868 self
869 }
870}
871
872impl<Selection> Table<'_, Selection> {
873 #[inline]
875 fn total_width(&self, area_width: u16) -> u16 {
876 if let Some(layout_width) = self.layout_width {
877 layout_width
878 } else if self.auto_layout_width {
879 let mut width = 0;
880 for w in &self.widths {
881 match w {
882 Constraint::Min(v) => width += *v + self.column_spacing,
883 Constraint::Max(v) => width += *v + self.column_spacing,
884 Constraint::Length(v) => width += *v + self.column_spacing,
885 _ => unimplemented!("Invalid layout constraint."),
886 }
887 }
888 width
889 } else {
890 area_width
891 }
892 }
893
894 #[inline]
896 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
897 let width = self.total_width(width);
898 let area = Rect::new(0, 0, width, 0);
899
900 let (layout, spacers) = Layout::horizontal(&self.widths)
901 .flex(self.flex)
902 .spacing(self.column_spacing)
903 .split_with_spacers(area);
904
905 (width, layout, spacers)
906 }
907
908 #[inline]
910 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
911 let heights = vec![
912 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
913 Constraint::Fill(1),
914 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
915 ];
916
917 Layout::vertical(heights).split(area)
918 }
919}
920
921#[cfg(feature = "unstable-widget-ref")]
922impl<'a, Selection> StatefulWidgetRef for Table<'a, Selection>
923where
924 Selection: TableSelection,
925{
926 type State = TableState<Selection>;
927
928 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
929 let iter = self.data.iter();
930 self.render_iter(iter, area, buf, state);
931 }
932}
933
934impl<Selection> StatefulWidget for Table<'_, Selection>
935where
936 Selection: TableSelection,
937{
938 type State = TableState<Selection>;
939
940 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
941 let iter = mem::take(&mut self.data).into_iter();
942 self.render_iter(iter, area, buf, state);
943 }
944}
945
946impl<'a, Selection> Table<'a, Selection>
947where
948 Selection: TableSelection,
949{
950 fn render_iter<'b>(
955 &self,
956 mut data: DataReprIter<'a, 'b>,
957 area: Rect,
958 buf: &mut Buffer,
959 state: &mut TableState<Selection>,
960 ) {
961 if let Some(rows) = data.rows() {
962 state.rows = rows;
963 }
964 state.columns = self.widths.len();
965 state.area = area;
966
967 let sa = ScrollArea::new()
968 .style(self.style)
969 .block(self.block.as_ref())
970 .h_scroll(self.hscroll.as_ref())
971 .v_scroll(self.vscroll.as_ref());
972 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
973
974 let l_rows = self.layout_areas(state.inner);
975 state.header_area = l_rows[0];
976 state.table_area = l_rows[1];
977 state.footer_area = l_rows[2];
978
979 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
981 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
982
983 sa.render(
985 area,
986 buf,
987 &mut ScrollAreaState::new()
988 .h_scroll(&mut state.hscroll)
989 .v_scroll(&mut state.vscroll),
990 );
991
992 self.render_header(
994 state.columns,
995 width,
996 l_columns.as_ref(),
997 l_spacers.as_ref(),
998 state.header_area,
999 buf,
1000 state,
1001 );
1002 self.render_footer(
1003 state.columns,
1004 width,
1005 l_columns.as_ref(),
1006 l_spacers.as_ref(),
1007 state.footer_area,
1008 buf,
1009 state,
1010 );
1011
1012 state.row_areas.clear();
1014 state.vscroll.set_page_len(0);
1015 state.hscroll.set_page_len(area.width as usize);
1016
1017 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1018 let mut row = None;
1019 let mut row_y = state.table_area.y;
1020 let mut row_heights = Vec::new();
1021 #[cfg(debug_assertions)]
1022 let mut insane_offset = false;
1023
1024 let mut ctx = TableContext {
1025 focus: state.focus.get(),
1026 selected_cell: false,
1027 selected_row: false,
1028 selected_column: false,
1029 style: self.style,
1030 row_style: None,
1031 select_style: None,
1032 space_area: Default::default(),
1033 row_area: Default::default(),
1034 non_exhaustive: NonExhaustive,
1035 };
1036
1037 if data.nth(state.vscroll.offset()) {
1038 row = Some(state.vscroll.offset());
1039 loop {
1040 ctx.row_style = data.row_style();
1041 let render_row_area = Rect::new(0, 0, width, data.row_height());
1045 ctx.row_area = render_row_area;
1046 row_buf.resize(render_row_area);
1047 if let Some(row_style) = ctx.row_style {
1048 row_buf.set_style(render_row_area, row_style);
1049 } else {
1050 row_buf.set_style(render_row_area, self.style);
1051 }
1052 row_heights.push(render_row_area.height);
1053
1054 let visible_row_area = Rect::new(
1056 state.table_area.x,
1057 row_y,
1058 state.table_area.width,
1059 render_row_area.height,
1060 )
1061 .intersection(state.table_area);
1062 state.row_areas.push(visible_row_area);
1063 if render_row_area.height == visible_row_area.height {
1065 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1066 }
1067
1068 if render_row_area.height > 0 {
1070 let mut col = 0;
1071 loop {
1072 if col >= state.columns {
1073 break;
1074 }
1075
1076 let render_cell_area = Rect::new(
1077 l_columns[col].x,
1078 0,
1079 l_columns[col].width,
1080 render_row_area.height,
1081 );
1082 ctx.space_area = Rect::new(
1083 l_spacers[col + 1].x,
1084 0,
1085 l_spacers[col + 1].width,
1086 render_row_area.height,
1087 );
1088
1089 if state.selection.is_selected_cell(col, row.expect("row")) {
1090 ctx.selected_cell = true;
1091 ctx.selected_row = false;
1092 ctx.selected_column = false;
1093 ctx.select_style = self.patch_select(
1094 self.select_cell_style,
1095 state.focus.get(),
1096 self.show_cell_focus,
1097 );
1098 } else if state.selection.is_selected_row(row.expect("row")) {
1099 ctx.selected_cell = false;
1100 ctx.selected_row = true;
1101 ctx.selected_column = false;
1102 ctx.select_style = if self.select_row_style.is_some() {
1104 self.patch_select(
1105 self.select_row_style,
1106 state.focus.get(),
1107 self.show_row_focus,
1108 )
1109 } else {
1110 self.patch_select(
1111 Some(self.style),
1112 state.focus.get(),
1113 self.show_row_focus,
1114 )
1115 };
1116 } else if state.selection.is_selected_column(col) {
1117 ctx.selected_cell = false;
1118 ctx.selected_row = false;
1119 ctx.selected_column = true;
1120 ctx.select_style = self.patch_select(
1121 self.select_column_style,
1122 state.focus.get(),
1123 self.show_column_focus,
1124 );
1125 } else {
1126 ctx.selected_cell = false;
1127 ctx.selected_row = false;
1128 ctx.selected_column = false;
1129 ctx.select_style = None;
1130 }
1131
1132 if render_cell_area.right() > state.hscroll.offset as u16
1134 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1135 {
1136 if let Some(select_style) = ctx.select_style {
1137 row_buf.set_style(render_cell_area, select_style);
1138 row_buf.set_style(ctx.space_area, select_style);
1139 }
1140 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1141 }
1142
1143 col += 1;
1144 }
1145
1146 transfer_buffer(
1148 &mut row_buf,
1149 state.hscroll.offset() as u16,
1150 visible_row_area,
1151 buf,
1152 );
1153 }
1154
1155 if visible_row_area.bottom() >= state.table_area.bottom() {
1156 break;
1157 }
1158 if !data.nth(0) {
1159 break;
1160 }
1161 row = Some(row.expect("row").saturating_add(1));
1162 row_y += render_row_area.height;
1163 }
1164 } else {
1165 if data.rows().is_none() || data.rows() == Some(0) {
1170 } else {
1172 #[cfg(debug_assertions)]
1173 {
1174 insane_offset = true;
1175 }
1176 }
1177 }
1178
1179 #[allow(unused_variables)]
1181 let algorithm;
1182 #[allow(unused_assignments)]
1183 {
1184 if let Some(rows) = data.rows() {
1185 algorithm = 0;
1186 let skip_rows = rows
1190 .saturating_sub(row.map_or(0, |v| v + 1))
1191 .saturating_sub(state.table_area.height as usize);
1192 if skip_rows > 0 {
1194 row_heights.clear();
1195 }
1196 let nth_row = skip_rows;
1197 if data.nth(nth_row) {
1199 let mut sum_height = row_heights.iter().sum::<u16>();
1200 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1201 loop {
1202 let row_height = data.row_height();
1203 row_heights.push(row_height);
1204
1205 sum_height += row_height;
1208 if sum_height
1209 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1210 > state.table_area.height
1211 {
1212 let lost_height = row_heights.remove(0);
1213 sum_height -= lost_height;
1214 }
1215
1216 if !data.nth(0) {
1217 break;
1218 }
1219
1220 row = Some(row.expect("row") + 1);
1221 if row.expect("row") > rows {
1223 break;
1224 }
1225 }
1226 while data.nth(0) {
1229 row = Some(row.expect("row") + 1);
1230 }
1231 } else {
1232 }
1235
1236 state.rows = rows;
1237 state._counted_rows = row.map_or(0, |v| v + 1);
1238
1239 if let Some(last_page) = state.calc_last_page(row_heights) {
1241 state.vscroll.set_max_offset(state.rows - last_page);
1242 } else {
1243 state.vscroll.set_max_offset(
1247 state.rows.saturating_sub(state.table_area.height as usize),
1248 );
1249 }
1250 } else if self.no_row_count {
1251 algorithm = 1;
1252
1253 if row.is_some() {
1257 if data.nth(0) {
1258 row = Some(row.expect("row").saturating_add(1));
1260 if data.nth(0) {
1261 row = Some(usize::MAX - 1);
1263 }
1264 }
1265 }
1266
1267 state.rows = row.map_or(0, |v| v + 1);
1268 state._counted_rows = row.map_or(0, |v| v + 1);
1269 state.vscroll.set_max_offset(usize::MAX - 1);
1271 if state.vscroll.page_len() == 0 {
1272 state.vscroll.set_page_len(state.table_area.height as usize);
1273 }
1274 } else {
1275 algorithm = 2;
1276
1277 let mut sum_height = row_heights.iter().sum::<u16>();
1279 while data.nth(0) {
1280 let row_height = data.row_height();
1281 row_heights.push(row_height);
1282
1283 sum_height += row_height;
1286 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1287 > state.table_area.height
1288 {
1289 let lost_height = row_heights.remove(0);
1290 sum_height -= lost_height;
1291 }
1292 row = Some(row.map_or(0, |v| v + 1));
1293 }
1294
1295 state.rows = row.map_or(0, |v| v + 1);
1296 state._counted_rows = row.map_or(0, |v| v + 1);
1297
1298 if let Some(last_page) = state.calc_last_page(row_heights) {
1300 state.vscroll.set_max_offset(state.rows - last_page);
1301 } else {
1302 state.vscroll.set_max_offset(0);
1303 }
1304 }
1305 }
1306 {
1307 state
1308 .hscroll
1309 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1310 }
1311
1312 #[cfg(debug_assertions)]
1313 {
1314 use std::fmt::Write;
1315 let mut msg = String::new();
1316 if insane_offset {
1317 _= write!(msg,
1318 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1319 state.vscroll.offset(), state.rows, state._counted_rows, algorithm
1320 );
1321 }
1322 if state.rows != state._counted_rows {
1323 _ = write!(
1324 msg,
1325 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1326 state.rows, state._counted_rows, algorithm
1327 );
1328 }
1329 if !msg.is_empty() {
1330 use log::warn;
1331 use ratatui::style::Stylize;
1332 use ratatui::text::Text;
1333
1334 warn!("{}", &msg);
1335 Text::from(msg)
1336 .white()
1337 .on_red()
1338 .render(state.table_area, buf);
1339 }
1340 }
1341 }
1342
1343 #[allow(clippy::too_many_arguments)]
1344 fn render_footer(
1345 &self,
1346 columns: usize,
1347 width: u16,
1348 l_columns: &[Rect],
1349 l_spacers: &[Rect],
1350 area: Rect,
1351 buf: &mut Buffer,
1352 state: &mut TableState<Selection>,
1353 ) {
1354 if let Some(footer) = &self.footer {
1355 let render_row_area = Rect::new(0, 0, width, footer.height);
1356 let mut row_buf = Buffer::empty(render_row_area);
1357
1358 row_buf.set_style(render_row_area, self.style);
1359 if let Some(footer_style) = footer.style {
1360 row_buf.set_style(render_row_area, footer_style);
1361 } else if let Some(footer_style) = self.footer_style {
1362 row_buf.set_style(render_row_area, footer_style);
1363 }
1364
1365 let mut col = 0;
1366 loop {
1367 if col >= columns {
1368 break;
1369 }
1370
1371 let render_cell_area =
1372 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1373 let render_space_area = Rect::new(
1374 l_spacers[col + 1].x,
1375 0,
1376 l_spacers[col + 1].width,
1377 area.height,
1378 );
1379
1380 if state.selection.is_selected_column(col) {
1381 if let Some(selected_style) = self.patch_select(
1382 self.select_footer_style,
1383 state.focus.get(),
1384 self.show_footer_focus,
1385 ) {
1386 row_buf.set_style(render_cell_area, selected_style);
1387 row_buf.set_style(render_space_area, selected_style);
1388 }
1389 };
1390
1391 if render_cell_area.right() > state.hscroll.offset as u16
1393 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1394 {
1395 if let Some(cell) = footer.cells.get(col) {
1396 if let Some(cell_style) = cell.style {
1397 row_buf.set_style(render_cell_area, cell_style);
1398 }
1399 cell.content.clone().render(render_cell_area, &mut row_buf);
1400 }
1401 }
1402
1403 col += 1;
1404 }
1405
1406 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1408 }
1409 }
1410
1411 #[allow(clippy::too_many_arguments)]
1412 fn render_header(
1413 &self,
1414 columns: usize,
1415 width: u16,
1416 l_columns: &[Rect],
1417 l_spacers: &[Rect],
1418 area: Rect,
1419 buf: &mut Buffer,
1420 state: &mut TableState<Selection>,
1421 ) {
1422 if let Some(header) = &self.header {
1423 let render_row_area = Rect::new(0, 0, width, header.height);
1424 let mut row_buf = Buffer::empty(render_row_area);
1425
1426 row_buf.set_style(render_row_area, self.style);
1427 if let Some(header_style) = header.style {
1428 row_buf.set_style(render_row_area, header_style);
1429 } else if let Some(header_style) = self.header_style {
1430 row_buf.set_style(render_row_area, header_style);
1431 }
1432
1433 let mut col = 0;
1434 loop {
1435 if col >= columns {
1436 break;
1437 }
1438
1439 let render_cell_area =
1440 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1441 let render_space_area = Rect::new(
1442 l_spacers[col + 1].x,
1443 0,
1444 l_spacers[col + 1].width,
1445 area.height,
1446 );
1447
1448 if state.selection.is_selected_column(col) {
1449 if let Some(selected_style) = self.patch_select(
1450 self.select_header_style,
1451 state.focus.get(),
1452 self.show_header_focus,
1453 ) {
1454 row_buf.set_style(render_cell_area, selected_style);
1455 row_buf.set_style(render_space_area, selected_style);
1456 }
1457 };
1458
1459 if render_cell_area.right() > state.hscroll.offset as u16
1461 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1462 {
1463 if let Some(cell) = header.cells.get(col) {
1464 if let Some(cell_style) = cell.style {
1465 row_buf.set_style(render_cell_area, cell_style);
1466 }
1467 cell.content.clone().render(render_cell_area, &mut row_buf);
1468 }
1469 }
1470
1471 col += 1;
1472 }
1473
1474 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1476 }
1477 }
1478
1479 fn calculate_column_areas(
1480 &self,
1481 columns: usize,
1482 l_columns: &[Rect],
1483 l_spacers: &[Rect],
1484 state: &mut TableState<Selection>,
1485 ) {
1486 state.column_areas.clear();
1487 state.column_layout.clear();
1488
1489 let mut col = 0;
1490 let shift = state.hscroll.offset() as isize;
1491 loop {
1492 if col >= columns {
1493 break;
1494 }
1495
1496 state.column_layout.push(Rect::new(
1497 l_columns[col].x,
1498 0,
1499 l_columns[col].width + l_spacers[col + 1].width,
1500 0,
1501 ));
1502
1503 let cell_x1 = l_columns[col].x as isize;
1504 let cell_x2 =
1505 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1506
1507 let squish_x1 = cell_x1.saturating_sub(shift);
1508 let squish_x2 = cell_x2.saturating_sub(shift);
1509
1510 let abs_x1 = max(0, squish_x1) as u16;
1511 let abs_x2 = max(0, squish_x2) as u16;
1512
1513 let v_area = Rect::new(
1514 state.table_area.x + abs_x1,
1515 state.table_area.y,
1516 abs_x2 - abs_x1,
1517 state.table_area.height,
1518 );
1519 state
1520 .column_areas
1521 .push(v_area.intersection(state.table_area));
1522
1523 col += 1;
1524 }
1525 }
1526
1527 #[expect(clippy::collapsible_else_if)]
1528 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1529 if let Some(style) = style {
1530 if let Some(focus_style) = self.focus_style {
1531 if focus && show {
1532 Some(style.patch(focus_style))
1533 } else {
1534 Some(fallback_select_style(style))
1535 }
1536 } else {
1537 if focus && show {
1538 Some(revert_style(style))
1539 } else {
1540 Some(fallback_select_style(style))
1541 }
1542 }
1543 } else {
1544 None
1545 }
1546 }
1547}
1548
1549impl Default for TableStyle {
1550 fn default() -> Self {
1551 Self {
1552 style: Default::default(),
1553 header: None,
1554 footer: None,
1555 select_row: None,
1556 select_column: None,
1557 select_cell: None,
1558 select_header: None,
1559 select_footer: None,
1560 show_row_focus: true, show_column_focus: false,
1562 show_cell_focus: false,
1563 show_header_focus: false,
1564 show_footer_focus: false,
1565 focus_style: None,
1566 block: None,
1567 border_style: None,
1568 scroll: None,
1569 non_exhaustive: NonExhaustive,
1570 }
1571 }
1572}
1573
1574impl<Selection: Clone> Clone for TableState<Selection> {
1575 fn clone(&self) -> Self {
1576 Self {
1577 focus: FocusFlag::named(self.focus.name()),
1578 area: self.area,
1579 inner: self.inner,
1580 header_area: self.header_area,
1581 table_area: self.table_area,
1582 row_areas: self.row_areas.clone(),
1583 column_areas: self.column_areas.clone(),
1584 column_layout: self.column_layout.clone(),
1585 footer_area: self.footer_area,
1586 rows: self.rows,
1587 _counted_rows: self._counted_rows,
1588 columns: self.columns,
1589 vscroll: self.vscroll.clone(),
1590 hscroll: self.hscroll.clone(),
1591 selection: self.selection.clone(),
1592 mouse: Default::default(),
1593 non_exhaustive: NonExhaustive,
1594 }
1595 }
1596}
1597
1598impl<Selection: Default> Default for TableState<Selection> {
1599 fn default() -> Self {
1600 Self {
1601 focus: Default::default(),
1602 area: Default::default(),
1603 inner: Default::default(),
1604 header_area: Default::default(),
1605 table_area: Default::default(),
1606 row_areas: Default::default(),
1607 column_areas: Default::default(),
1608 column_layout: Default::default(),
1609 footer_area: Default::default(),
1610 rows: Default::default(),
1611 _counted_rows: Default::default(),
1612 columns: Default::default(),
1613 vscroll: Default::default(),
1614 hscroll: Default::default(),
1615 selection: Default::default(),
1616 mouse: Default::default(),
1617 non_exhaustive: NonExhaustive,
1618 }
1619 }
1620}
1621
1622impl<Selection> HasFocus for TableState<Selection> {
1623 fn build(&self, builder: &mut FocusBuilder) {
1624 builder.leaf_widget(self);
1625 }
1626
1627 #[inline]
1628 fn focus(&self) -> FocusFlag {
1629 self.focus.clone()
1630 }
1631
1632 #[inline]
1633 fn area(&self) -> Rect {
1634 self.area
1635 }
1636}
1637
1638impl<Selection> RelocatableState for TableState<Selection> {
1639 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1640 self.area = relocate_area(self.area, shift, clip);
1641 self.inner = relocate_area(self.inner, shift, clip);
1642 self.table_area = relocate_area(self.table_area, shift, clip);
1643 self.footer_area = relocate_area(self.footer_area, shift, clip);
1644 self.header_area = relocate_area(self.header_area, shift, clip);
1645
1646 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1647 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1648 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1649
1650 self.hscroll.relocate(shift, clip);
1651 self.vscroll.relocate(shift, clip);
1652 }
1653}
1654
1655impl<Selection> TableState<Selection> {
1656 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1657 let mut sum_heights = 0;
1658 let mut n_rows = 0;
1659 while let Some(h) = row_heights.pop() {
1660 sum_heights += h;
1661 n_rows += 1;
1662 if sum_heights >= self.table_area.height {
1663 break;
1664 }
1665 }
1666
1667 if sum_heights < self.table_area.height {
1668 None
1669 } else {
1670 Some(n_rows)
1671 }
1672 }
1673}
1674
1675impl<Selection> TableState<Selection>
1677where
1678 Selection: Default,
1679{
1680 pub fn new() -> Self {
1681 Self::default()
1682 }
1683
1684 pub fn named(name: &str) -> Self {
1685 Self {
1686 focus: FocusFlag::named(name),
1687 ..TableState::default()
1688 }
1689 }
1690}
1691
1692impl<Selection> TableState<Selection> {
1694 #[inline]
1696 pub fn rows(&self) -> usize {
1697 self.rows
1698 }
1699
1700 #[inline]
1702 pub fn columns(&self) -> usize {
1703 self.columns
1704 }
1705}
1706
1707impl<Selection> TableState<Selection> {
1709 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1715 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1716 return None;
1717 }
1718
1719 let mut areas = Vec::new();
1720
1721 let r = self.row_areas[row - self.vscroll.offset()];
1722 for c in &self.column_areas {
1723 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1724 }
1725
1726 Some((r, areas))
1727 }
1728
1729 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1731 let col = self.column_at_clicked(pos);
1732 let row = self.row_at_clicked(pos);
1733
1734 match (col, row) {
1735 (Some(col), Some(row)) => Some((col, row)),
1736 _ => None,
1737 }
1738 }
1739
1740 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1742 self.mouse.column_at(&self.column_areas, pos.0)
1743 }
1744
1745 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1747 self.mouse
1748 .row_at(&self.row_areas, pos.1)
1749 .map(|v| self.vscroll.offset() + v)
1750 }
1751
1752 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1755 let col = self.column_at_drag(pos);
1756 let row = self.row_at_drag(pos);
1757
1758 (col, row)
1759 }
1760
1761 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1768 match self
1769 .mouse
1770 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1771 {
1772 Ok(v) => self.vscroll.offset() + v,
1773 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1774 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1775 }
1776 }
1777
1778 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1782 match self
1783 .mouse
1784 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1785 {
1786 Ok(v) => v,
1787 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1788 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1789 }
1790 }
1791}
1792
1793impl<Selection: TableSelection> TableState<Selection> {
1795 pub fn clear_offset(&mut self) {
1797 self.vscroll.set_offset(0);
1798 self.hscroll.set_offset(0);
1799 }
1800
1801 pub fn row_max_offset(&self) -> usize {
1806 self.vscroll.max_offset()
1807 }
1808
1809 pub fn row_offset(&self) -> usize {
1811 self.vscroll.offset()
1812 }
1813
1814 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1821 self.vscroll.set_offset(offset)
1822 }
1823
1824 pub fn page_len(&self) -> usize {
1826 self.vscroll.page_len()
1827 }
1828
1829 pub fn row_scroll_by(&self) -> usize {
1831 self.vscroll.scroll_by()
1832 }
1833
1834 pub fn x_max_offset(&self) -> usize {
1839 self.hscroll.max_offset()
1840 }
1841
1842 pub fn x_offset(&self) -> usize {
1844 self.hscroll.offset()
1845 }
1846
1847 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1854 self.hscroll.set_offset(offset)
1855 }
1856
1857 pub fn page_width(&self) -> usize {
1859 self.hscroll.page_len()
1860 }
1861
1862 pub fn x_scroll_by(&self) -> usize {
1864 self.hscroll.scroll_by()
1865 }
1866
1867 pub fn scroll_to_selected(&mut self) -> bool {
1870 if let Some(selected) = self.selection.lead_selection() {
1871 let c = self.scroll_to_x(selected.0);
1872 let r = self.scroll_to_row(selected.1);
1873 r || c
1874 } else {
1875 false
1876 }
1877 }
1878
1879 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1882 if pos >= self.rows {
1883 false
1884 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1885 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1887 if heights < self.table_area.height {
1888 false
1889 } else {
1890 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1891 }
1892 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
1893 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
1894 } else if pos < self.row_offset() {
1895 self.set_row_offset(pos)
1896 } else {
1897 false
1898 }
1899 }
1900
1901 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
1903 if let Some(col) = self.column_layout.get(pos) {
1904 if (col.left() as usize) < self.x_offset() {
1905 self.set_x_offset(col.x as usize)
1906 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
1907 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
1908 } else {
1909 false
1910 }
1911 } else {
1912 false
1913 }
1914 }
1915
1916 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
1918 if pos >= self.x_offset().saturating_add(self.page_width()) {
1919 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
1920 } else if pos < self.x_offset() {
1921 self.set_x_offset(pos)
1922 } else {
1923 false
1924 }
1925 }
1926
1927 pub fn scroll_up(&mut self, n: usize) -> bool {
1929 self.vscroll.scroll_up(n)
1930 }
1931
1932 pub fn scroll_down(&mut self, n: usize) -> bool {
1934 self.vscroll.scroll_down(n)
1935 }
1936
1937 pub fn scroll_left(&mut self, n: usize) -> bool {
1939 self.hscroll.scroll_left(n)
1940 }
1941
1942 pub fn scroll_right(&mut self, n: usize) -> bool {
1944 self.hscroll.scroll_right(n)
1945 }
1946}
1947
1948impl TableState<RowSelection> {
1949 pub fn items_added(&mut self, pos: usize, n: usize) {
1952 self.vscroll.items_added(pos, n);
1953 self.selection.items_added(pos, n);
1954 self.rows += n;
1955 }
1956
1957 pub fn items_removed(&mut self, pos: usize, n: usize) {
1960 self.vscroll.items_removed(pos, n);
1961 self.selection
1962 .items_removed(pos, n, self.rows.saturating_sub(1));
1963 self.rows -= n;
1964 }
1965
1966 #[inline]
1968 pub fn set_scroll_selection(&mut self, scroll: bool) {
1969 self.selection.set_scroll_selected(scroll);
1970 }
1971
1972 #[inline]
1974 pub fn clear_selection(&mut self) {
1975 self.selection.clear();
1976 }
1977
1978 #[inline]
1980 pub fn has_selection(&mut self) -> bool {
1981 self.selection.has_selection()
1982 }
1983
1984 #[inline]
1987 pub fn selected(&self) -> Option<usize> {
1988 self.selection.selected()
1989 }
1990
1991 #[inline]
1994 #[allow(clippy::manual_filter)]
1995 pub fn selected_checked(&self) -> Option<usize> {
1996 if let Some(selected) = self.selection.selected() {
1997 if selected < self.rows {
1998 Some(selected)
1999 } else {
2000 None
2001 }
2002 } else {
2003 None
2004 }
2005 }
2006
2007 #[inline]
2010 pub fn select(&mut self, row: Option<usize>) -> bool {
2011 self.selection.select(row)
2012 }
2013
2014 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2018 if self.vscroll.max_offset() > 0 {
2019 (self.rows * offset) / self.vscroll.max_offset()
2020 } else {
2021 0 }
2023 }
2024
2025 #[inline]
2028 pub fn move_to(&mut self, row: usize) -> bool {
2029 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2030 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2031 r || s
2032 }
2033
2034 #[inline]
2037 pub fn move_up(&mut self, n: usize) -> bool {
2038 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2039 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2040 r || s
2041 }
2042
2043 #[inline]
2046 pub fn move_down(&mut self, n: usize) -> bool {
2047 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2048 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2049 r || s
2050 }
2051}
2052
2053impl TableState<RowSetSelection> {
2054 #[inline]
2056 pub fn clear_selection(&mut self) {
2057 self.selection.clear();
2058 }
2059
2060 #[inline]
2062 pub fn has_selection(&mut self) -> bool {
2063 self.selection.has_selection()
2064 }
2065
2066 #[inline]
2068 pub fn selected(&self) -> HashSet<usize> {
2069 self.selection.selected()
2070 }
2071
2072 #[inline]
2079 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2080 self.selection.set_lead(row, extend)
2081 }
2082
2083 #[inline]
2085 pub fn lead(&self) -> Option<usize> {
2086 self.selection.lead()
2087 }
2088
2089 #[inline]
2091 pub fn anchor(&self) -> Option<usize> {
2092 self.selection.anchor()
2093 }
2094
2095 #[inline]
2098 pub fn retire_selection(&mut self) {
2099 self.selection.retire_selection();
2100 }
2101
2102 #[inline]
2105 pub fn add_selected(&mut self, idx: usize) {
2106 self.selection.add(idx);
2107 }
2108
2109 #[inline]
2112 pub fn remove_selected(&mut self, idx: usize) {
2113 self.selection.remove(idx);
2114 }
2115
2116 #[inline]
2119 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2120 let r = self
2121 .selection
2122 .move_to(row, self.rows.saturating_sub(1), extend);
2123 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2124 r || s
2125 }
2126
2127 #[inline]
2130 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2131 let r = self
2132 .selection
2133 .move_up(n, self.rows.saturating_sub(1), extend);
2134 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2135 r || s
2136 }
2137
2138 #[inline]
2141 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2142 let r = self
2143 .selection
2144 .move_down(n, self.rows.saturating_sub(1), extend);
2145 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2146 r || s
2147 }
2148}
2149
2150impl TableState<CellSelection> {
2151 #[inline]
2152 pub fn clear_selection(&mut self) {
2153 self.selection.clear();
2154 }
2155
2156 #[inline]
2157 pub fn has_selection(&mut self) -> bool {
2158 self.selection.has_selection()
2159 }
2160
2161 #[inline]
2163 pub fn selected(&self) -> Option<(usize, usize)> {
2164 self.selection.selected()
2165 }
2166
2167 #[inline]
2169 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2170 self.selection.select_cell(select)
2171 }
2172
2173 #[inline]
2175 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2176 if let Some(row) = row {
2177 self.selection
2178 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2179 } else {
2180 self.selection.select_row(None)
2181 }
2182 }
2183
2184 #[inline]
2186 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2187 if let Some(column) = column {
2188 self.selection
2189 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2190 } else {
2191 self.selection.select_column(None)
2192 }
2193 }
2194
2195 #[inline]
2197 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2198 let r = self.selection.move_to(
2199 select,
2200 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2201 );
2202 let s = self.scroll_to_selected();
2203 r || s
2204 }
2205
2206 #[inline]
2208 pub fn move_to_row(&mut self, row: usize) -> bool {
2209 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2210 let s = self.scroll_to_selected();
2211 r || s
2212 }
2213
2214 #[inline]
2216 pub fn move_to_col(&mut self, col: usize) -> bool {
2217 let r = self
2218 .selection
2219 .move_to_col(col, self.columns.saturating_sub(1));
2220 let s = self.scroll_to_selected();
2221 r || s
2222 }
2223
2224 #[inline]
2227 pub fn move_up(&mut self, n: usize) -> bool {
2228 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2229 let s = self.scroll_to_selected();
2230 r || s
2231 }
2232
2233 #[inline]
2236 pub fn move_down(&mut self, n: usize) -> bool {
2237 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2238 let s = self.scroll_to_selected();
2239 r || s
2240 }
2241
2242 #[inline]
2245 pub fn move_left(&mut self, n: usize) -> bool {
2246 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2247 let s = self.scroll_to_selected();
2248 r || s
2249 }
2250
2251 #[inline]
2254 pub fn move_right(&mut self, n: usize) -> bool {
2255 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2256 let s = self.scroll_to_selected();
2257 r || s
2258 }
2259}
2260
2261impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2262 for TableState<Selection>
2263{
2264 fn handle(
2266 &mut self,
2267 event: &crossterm::event::Event,
2268 _keymap: DoubleClick,
2269 ) -> DoubleClickOutcome {
2270 match event {
2271 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2272 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2273 DoubleClickOutcome::ClickClick(col, row)
2274 } else {
2275 DoubleClickOutcome::Continue
2276 }
2277 }
2278 _ => DoubleClickOutcome::Continue,
2279 }
2280 }
2281}
2282
2283pub fn handle_doubleclick_events<Selection: TableSelection>(
2285 state: &mut TableState<Selection>,
2286 event: &crossterm::event::Event,
2287) -> DoubleClickOutcome {
2288 state.handle(event, DoubleClick)
2289}