1#![allow(clippy::collapsible_if)]
2
3use crate::_private::NonExhaustive;
4use crate::event::{DoubleClick, DoubleClickOutcome};
5use crate::selection::{CellSelection, RowSelection, RowSetSelection};
6use crate::table::data::{DataRepr, DataReprIter};
7use crate::textdata::{Row, TextTableData};
8use crate::util::{fallback_select_style, revert_style, transfer_buffer};
9use crate::{TableContext, TableData, TableDataIter, TableSelection};
10use rat_cursor::HasScreenCursor;
11use rat_event::util::MouseFlags;
12use rat_event::{HandleEvent, ct_event};
13use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
14use rat_reloc::{RelocatableState, relocate_areas};
15use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
16use ratatui::buffer::Buffer;
17use ratatui::layout::{Constraint, Flex, Layout, Rect};
18use ratatui::style::Style;
19use ratatui::widgets::{Block, StatefulWidget, Widget};
20use std::cmp::{max, min};
21use std::collections::HashSet;
22use std::fmt::Debug;
23use std::marker::PhantomData;
24use std::mem;
25use std::rc::Rc;
26
27#[derive(Debug)]
42pub struct Table<'a, Selection = RowSelection> {
43 data: DataRepr<'a>,
44 no_row_count: bool,
45
46 header: Option<Row<'a>>,
47 footer: Option<Row<'a>>,
48
49 widths: Vec<Constraint>,
50 flex: Flex,
51 column_spacing: u16,
52 layout_width: Option<u16>,
53 layout_column_widths: bool,
54
55 style: Style,
56 block: Option<Block<'a>>,
57 hscroll: Option<Scroll<'a>>,
58 vscroll: Option<Scroll<'a>>,
59 header_style: Option<Style>,
60 footer_style: Option<Style>,
61 focus_style: Option<Style>,
62
63 auto_styles: bool,
64 select_row_style: Option<Style>,
65 show_row_focus: bool,
66 select_column_style: Option<Style>,
67 show_column_focus: bool,
68 select_cell_style: Option<Style>,
69 show_cell_focus: bool,
70 select_header_style: Option<Style>,
71 show_header_focus: bool,
72 select_footer_style: Option<Style>,
73 show_footer_focus: bool,
74
75 _phantom: PhantomData<Selection>,
76}
77
78mod data {
79 use crate::textdata::TextTableData;
80 use crate::{TableContext, TableData, TableDataIter};
81 #[cfg(feature = "perf_warnings")]
82 use log::warn;
83 use ratatui::buffer::Buffer;
84 use ratatui::layout::Rect;
85 use ratatui::style::{Style, Stylize};
86 use std::fmt::{Debug, Formatter};
87
88 #[derive(Default)]
89 pub(super) enum DataRepr<'a> {
90 #[default]
91 None,
92 Text(TextTableData<'a>),
93 Data(Box<dyn TableData<'a> + 'a>),
94 Iter(Box<dyn TableDataIter<'a> + 'a>),
95 }
96
97 impl<'a> DataRepr<'a> {
98 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
99 match self {
100 DataRepr::None => DataReprIter::None,
101 DataRepr::Text(v) => DataReprIter::IterText(v, None),
102 DataRepr::Data(v) => DataReprIter::IterData(v, None),
103 DataRepr::Iter(v) => DataReprIter::IterIter(v),
104 }
105 }
106
107 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
108 match self {
109 DataRepr::None => DataReprIter::None,
110 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
111 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
112 DataRepr::Iter(v) => {
113 if let Some(v) = v.cloned() {
115 DataReprIter::IterIter(v)
116 } else {
117 DataReprIter::Invalid(None)
118 }
119 }
120 }
121 }
122 }
123
124 impl Debug for DataRepr<'_> {
125 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("Data").finish()
127 }
128 }
129
130 #[derive(Default)]
131 pub(super) enum DataReprIter<'a, 'b> {
132 #[default]
133 None,
134 #[allow(dead_code)]
135 Invalid(Option<usize>),
136 IterText(TextTableData<'a>, Option<usize>),
137 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
138 #[allow(dead_code)]
139 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
140 IterIter(Box<dyn TableDataIter<'a> + 'a>),
141 }
142
143 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
144 fn rows(&self) -> Option<usize> {
145 match self {
146 DataReprIter::None => Some(0),
147 DataReprIter::Invalid(_) => Some(1),
148 DataReprIter::IterText(v, _) => Some(v.rows.len()),
149 DataReprIter::IterData(v, _) => Some(v.rows()),
150 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
151 DataReprIter::IterIter(v) => v.rows(),
152 }
153 }
154
155 fn nth(&mut self, n: usize) -> bool {
156 let incr = |row: &mut Option<usize>, rows: usize| match *row {
157 None => {
158 *row = Some(n);
159 *row < Some(rows)
160 }
161 Some(w) => {
162 *row = Some(w.saturating_add(n).saturating_add(1));
163 *row < Some(rows)
164 }
165 };
166
167 match self {
168 DataReprIter::None => false,
169 DataReprIter::Invalid(row) => incr(row, 1),
170 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
171 DataReprIter::IterData(v, row) => incr(row, v.rows()),
172 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
173 DataReprIter::IterIter(v) => v.nth(n),
174 }
175 }
176
177 fn row_height(&self) -> u16 {
179 match self {
180 DataReprIter::None => 1,
181 DataReprIter::Invalid(_) => 1,
182 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
183 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
184 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
185 DataReprIter::IterIter(v) => v.row_height(),
186 }
187 }
188
189 fn row_style(&self) -> Option<Style> {
190 match self {
191 DataReprIter::None => None,
192 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
193 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
194 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
195 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
196 DataReprIter::IterIter(v) => v.row_style(),
197 }
198 }
199
200 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
202 match self {
203 DataReprIter::None => {}
204 DataReprIter::Invalid(_) => {
205 if column == 0 {
206 #[cfg(feature = "perf_warnings")]
207 warn!(
208 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
209 );
210
211 buf.set_string(
212 area.x,
213 area.y,
214 "TableDataIter must implement a valid cloned() for this",
215 Style::default(),
216 );
217 }
218 }
219 DataReprIter::IterText(v, n) => {
220 v.render_cell(ctx, column, n.expect("row"), area, buf)
221 }
222 DataReprIter::IterData(v, n) => {
223 v.render_cell(ctx, column, n.expect("row"), area, buf)
224 }
225 DataReprIter::IterDataRef(v, n) => {
226 v.render_cell(ctx, column, n.expect("row"), area, buf)
227 }
228 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
229 }
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct TableStyle {
237 pub style: Style,
238 pub block: Option<Block<'static>>,
239 pub border_style: Option<Style>,
240 pub title_style: Option<Style>,
241 pub scroll: Option<ScrollStyle>,
242 pub header: Option<Style>,
243 pub footer: Option<Style>,
244 pub focus_style: Option<Style>,
245
246 pub select_row: Option<Style>,
247 pub select_column: Option<Style>,
248 pub select_cell: Option<Style>,
249 pub select_header: Option<Style>,
250 pub select_footer: Option<Style>,
251
252 pub show_row_focus: bool,
253 pub show_column_focus: bool,
254 pub show_cell_focus: bool,
255 pub show_header_focus: bool,
256 pub show_footer_focus: bool,
257
258 pub non_exhaustive: NonExhaustive,
259}
260
261#[derive(Debug)]
263pub struct TableState<Selection = RowSelection> {
264 pub focus: FocusFlag,
267
268 pub area: Rect,
271 pub inner: Rect,
274
275 pub header_area: Rect,
278 pub table_area: Rect,
281 pub row_areas: Vec<Rect>,
284 pub column_areas: Vec<Rect>,
288 pub column_layout: Vec<Rect>,
292 pub footer_area: Rect,
295
296 pub rows: usize,
299 pub _counted_rows: usize,
301 pub columns: usize,
304
305 pub vscroll: ScrollState,
308 pub hscroll: ScrollState,
311
312 pub selection: Selection,
315
316 pub mouse: MouseFlags,
318
319 pub non_exhaustive: NonExhaustive,
320}
321
322impl<Selection> Default for Table<'_, Selection> {
323 fn default() -> Self {
324 Self {
325 data: Default::default(),
326 no_row_count: Default::default(),
327 header: Default::default(),
328 footer: Default::default(),
329 widths: Default::default(),
330 flex: Default::default(),
331 column_spacing: Default::default(),
332 layout_width: Default::default(),
333 layout_column_widths: true,
334 block: Default::default(),
335 hscroll: Default::default(),
336 vscroll: Default::default(),
337 header_style: Default::default(),
338 footer_style: Default::default(),
339 style: Default::default(),
340 auto_styles: true,
341 select_row_style: Default::default(),
342 show_row_focus: true,
343 select_column_style: Default::default(),
344 show_column_focus: Default::default(),
345 select_cell_style: Default::default(),
346 show_cell_focus: Default::default(),
347 select_header_style: Default::default(),
348 show_header_focus: Default::default(),
349 select_footer_style: Default::default(),
350 show_footer_focus: Default::default(),
351 focus_style: Default::default(),
352 _phantom: Default::default(),
353 }
354 }
355}
356
357impl<'a, Selection> Table<'a, Selection> {
358 pub fn new() -> Self
360 where
361 Selection: Default,
362 {
363 Self::default()
364 }
365
366 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
371 where
372 R: IntoIterator,
373 R::Item: Into<Row<'a>>,
374 C: IntoIterator,
375 C::Item: Into<Constraint>,
376 Selection: Default,
377 {
378 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
379 let data = TextTableData {
380 rows: rows.into_iter().map(|v| v.into()).collect(),
381 };
382 Self {
383 data: DataRepr::Text(data),
384 widths,
385 ..Default::default()
386 }
387 }
388
389 pub fn rows<T>(mut self, rows: T) -> Self
393 where
394 T: IntoIterator<Item = Row<'a>>,
395 {
396 let rows = rows.into_iter().collect();
397 self.data = DataRepr::Text(TextTableData { rows });
398 self
399 }
400
401 #[inline]
468 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
469 self.widths = data.widths();
470 self.header = data.header();
471 self.footer = data.footer();
472 self.data = DataRepr::Data(Box::new(data));
473 self
474 }
475
476 #[inline]
585 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
586 #[cfg(feature = "perf_warnings")]
587 if data.rows().is_none() {
588 use log::warn;
589 warn!("Table::iter - rows is None, this will be slower");
590 }
591 self.header = data.header();
592 self.footer = data.footer();
593 self.widths = data.widths();
594 self.data = DataRepr::Iter(Box::new(data));
595 self
596 }
597
598 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
616 self.no_row_count = no_row_count;
617 self
618 }
619
620 #[inline]
622 pub fn header(mut self, header: Row<'a>) -> Self {
623 self.header = Some(header);
624 self
625 }
626
627 #[inline]
629 pub fn footer(mut self, footer: Row<'a>) -> Self {
630 self.footer = Some(footer);
631 self
632 }
633
634 pub fn widths<I>(mut self, widths: I) -> Self
636 where
637 I: IntoIterator,
638 I::Item: Into<Constraint>,
639 {
640 self.widths = widths.into_iter().map(|v| v.into()).collect();
641 self
642 }
643
644 #[inline]
646 pub fn flex(mut self, flex: Flex) -> Self {
647 self.flex = flex;
648 self
649 }
650
651 #[inline]
653 pub fn column_spacing(mut self, spacing: u16) -> Self {
654 self.column_spacing = spacing;
655 self
656 }
657
658 #[inline]
661 pub fn layout_width(mut self, width: u16) -> Self {
662 self.layout_width = Some(width);
663 self
664 }
665
666 #[inline]
675 pub fn layout_column_widths(mut self) -> Self {
676 self.layout_column_widths = true;
677 self
678 }
679
680 #[deprecated(since = "1.1.1", note = "no longer supported")]
687 #[inline]
688 pub fn auto_layout_width(self) -> Self {
689 self
690 }
691
692 #[inline]
694 pub fn block(mut self, block: Block<'a>) -> Self {
695 self.block = Some(block);
696 self.block = self.block.map(|v| v.style(self.style));
697 self
698 }
699
700 pub fn border_style(mut self, style: Style) -> Self {
702 self.block = self.block.map(|v| v.border_style(style));
703 self
704 }
705
706 pub fn title_style(mut self, style: Style) -> Self {
708 self.block = self.block.map(|v| v.title_style(style));
709 self
710 }
711
712 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
714 self.hscroll = Some(scroll.clone().override_horizontal());
715 self.vscroll = Some(scroll.override_vertical());
716 self
717 }
718
719 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
721 self.hscroll = Some(scroll.override_horizontal());
722 self
723 }
724
725 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
727 self.vscroll = Some(scroll.override_vertical());
728 self
729 }
730
731 #[inline]
733 pub fn styles(mut self, styles: TableStyle) -> Self {
734 self.style = styles.style;
735 if styles.block.is_some() {
736 self.block = styles.block;
737 }
738 if let Some(border_style) = styles.border_style {
739 self.block = self.block.map(|v| v.border_style(border_style));
740 }
741 if let Some(title_style) = styles.title_style {
742 self.block = self.block.map(|v| v.title_style(title_style));
743 }
744 self.block = self.block.map(|v| v.style(self.style));
745
746 if let Some(styles) = styles.scroll {
747 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
748 self.vscroll = self.vscroll.map(|v| v.styles(styles));
749 }
750 if styles.header.is_some() {
751 self.header_style = styles.header;
752 }
753 if styles.footer.is_some() {
754 self.footer_style = styles.footer;
755 }
756 if styles.select_row.is_some() {
757 self.select_row_style = styles.select_row;
758 }
759 self.show_row_focus = styles.show_row_focus;
760 if styles.select_column.is_some() {
761 self.select_column_style = styles.select_column;
762 }
763 self.show_column_focus = styles.show_column_focus;
764 if styles.select_cell.is_some() {
765 self.select_cell_style = styles.select_cell;
766 }
767 self.show_cell_focus = styles.show_cell_focus;
768 if styles.select_header.is_some() {
769 self.select_header_style = styles.select_header;
770 }
771 self.show_header_focus = styles.show_header_focus;
772 if styles.select_footer.is_some() {
773 self.select_footer_style = styles.select_footer;
774 }
775 self.show_footer_focus = styles.show_footer_focus;
776 if styles.focus_style.is_some() {
777 self.focus_style = styles.focus_style;
778 }
779 self
780 }
781
782 #[inline]
784 pub fn style(mut self, style: Style) -> Self {
785 self.style = style;
786 self.block = self.block.map(|v| v.style(self.style));
787 self
788 }
789
790 #[inline]
792 pub fn header_style(mut self, style: Option<Style>) -> Self {
793 self.header_style = style;
794 self
795 }
796
797 #[inline]
799 pub fn footer_style(mut self, style: Option<Style>) -> Self {
800 self.footer_style = style;
801 self
802 }
803
804 #[inline]
810 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
811 self.auto_styles = auto_styles;
812 self
813 }
814
815 #[inline]
818 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
819 self.select_row_style = select_style;
820 self
821 }
822
823 #[inline]
825 pub fn show_row_focus(mut self, show: bool) -> Self {
826 self.show_row_focus = show;
827 self
828 }
829
830 #[inline]
833 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
834 self.select_column_style = select_style;
835 self
836 }
837
838 #[inline]
840 pub fn show_column_focus(mut self, show: bool) -> Self {
841 self.show_column_focus = show;
842 self
843 }
844
845 #[inline]
848 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
849 self.select_cell_style = select_style;
850 self
851 }
852
853 #[inline]
855 pub fn show_cell_focus(mut self, show: bool) -> Self {
856 self.show_cell_focus = show;
857 self
858 }
859
860 #[inline]
863 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
864 self.select_header_style = select_style;
865 self
866 }
867
868 #[inline]
870 pub fn show_header_focus(mut self, show: bool) -> Self {
871 self.show_header_focus = show;
872 self
873 }
874
875 #[inline]
878 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
879 self.select_footer_style = select_style;
880 self
881 }
882
883 #[inline]
885 pub fn show_footer_focus(mut self, show: bool) -> Self {
886 self.show_footer_focus = show;
887 self
888 }
889
890 #[inline]
896 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
897 self.focus_style = focus_style;
898 self
899 }
900
901 pub fn width(&self) -> u16 {
903 let sa = ScrollArea::new()
904 .style(self.style)
905 .block(self.block.as_ref())
906 .h_scroll(self.hscroll.as_ref())
907 .v_scroll(self.vscroll.as_ref());
908 let padding = sa.padding();
909 let width = self.total_width(0);
910
911 width + padding.left + padding.right
912 }
913
914 pub fn heigth(&self) -> u16 {
916 let sa = ScrollArea::new()
917 .style(self.style)
918 .block(self.block.as_ref())
919 .h_scroll(self.hscroll.as_ref())
920 .v_scroll(self.vscroll.as_ref());
921 let padding = sa.padding();
922 let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
923 let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
924
925 1 + header + footer + padding.top + padding.bottom
926 }
927
928 #[deprecated(since = "1.1.1", note = "not in use")]
929 pub fn debug(self, _: bool) -> Self {
930 self
931 }
932}
933
934impl<Selection> Table<'_, Selection> {
935 #[inline]
937 fn total_width(&self, area_width: u16) -> u16 {
938 if let Some(layout_width) = self.layout_width {
939 layout_width
940 } else if self.layout_column_widths {
941 let mut width = 0;
942 for w in self.widths.iter().copied() {
943 match w {
944 Constraint::Min(v) => width += v + self.column_spacing,
945 Constraint::Max(v) => width += v + self.column_spacing,
946 Constraint::Length(v) => width += v + self.column_spacing,
947 Constraint::Percentage(p) => {
948 width += (((area_width as u32) * (p as u32)) / 100) as u16;
949 }
950 Constraint::Ratio(n, d) => {
951 width += (((area_width as u32) * n) / d) as u16;
952 }
953 Constraint::Fill(_) => {
954 width += 10;
956 }
957 }
958 }
959 max(width, area_width)
960 } else {
961 area_width
962 }
963 }
964
965 #[inline]
967 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
968 let width = self.total_width(width);
969 let area = Rect::new(0, 0, width, 0);
970
971 let (layout, spacers) = Layout::horizontal(&self.widths)
972 .flex(self.flex)
973 .spacing(self.column_spacing)
974 .split_with_spacers(area);
975
976 (width, layout, spacers)
977 }
978
979 #[inline]
981 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
982 let heights = vec![
983 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
984 Constraint::Fill(1),
985 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
986 ];
987
988 Layout::vertical(heights).split(area)
989 }
990}
991
992impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
993where
994 Selection: TableSelection,
995{
996 type State = TableState<Selection>;
997
998 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
999 let iter = self.data.iter();
1000 self.render_iter(iter, area, buf, state);
1001 }
1002}
1003
1004impl<Selection> StatefulWidget for Table<'_, Selection>
1005where
1006 Selection: TableSelection,
1007{
1008 type State = TableState<Selection>;
1009
1010 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1011 let iter = mem::take(&mut self.data).into_iter();
1012 self.render_iter(iter, area, buf, state);
1013 }
1014}
1015
1016impl<'a, Selection> Table<'a, Selection>
1017where
1018 Selection: TableSelection,
1019{
1020 fn render_iter<'b>(
1025 &self,
1026 mut data: DataReprIter<'a, 'b>,
1027 area: Rect,
1028 buf: &mut Buffer,
1029 state: &mut TableState<Selection>,
1030 ) {
1031 if let Some(rows) = data.rows() {
1032 state.rows = rows;
1033 }
1034 state.columns = self.widths.len();
1035 state.area = area;
1036
1037 let sa = ScrollArea::new()
1038 .style(self.style)
1039 .block(self.block.as_ref())
1040 .h_scroll(self.hscroll.as_ref())
1041 .v_scroll(self.vscroll.as_ref());
1042 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1043
1044 let l_rows = self.layout_areas(state.inner);
1045 state.header_area = l_rows[0];
1046 state.table_area = l_rows[1];
1047 state.footer_area = l_rows[2];
1048
1049 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1051 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1052
1053 sa.render_block(area, buf);
1055
1056 self.render_header(
1058 state.columns,
1059 width,
1060 l_columns.as_ref(),
1061 l_spacers.as_ref(),
1062 state.header_area,
1063 buf,
1064 state,
1065 );
1066 self.render_footer(
1067 state.columns,
1068 width,
1069 l_columns.as_ref(),
1070 l_spacers.as_ref(),
1071 state.footer_area,
1072 buf,
1073 state,
1074 );
1075
1076 state.row_areas.clear();
1078 state.vscroll.set_page_len(0);
1079 state.hscroll.set_page_len(area.width as usize);
1080
1081 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1082 let mut row = None;
1083 let mut row_y = state.table_area.y;
1084 let mut row_heights = Vec::new();
1085 #[cfg(feature = "perf_warnings")]
1086 let mut insane_offset = false;
1087
1088 let mut ctx = TableContext {
1089 focus: state.focus.get(),
1090 selected_cell: false,
1091 selected_row: false,
1092 selected_column: false,
1093 style: self.style,
1094 row_style: None,
1095 select_style: None,
1096 space_area: Default::default(),
1097 row_area: Default::default(),
1098 non_exhaustive: NonExhaustive,
1099 };
1100
1101 if data.nth(state.vscroll.offset()) {
1102 row = Some(state.vscroll.offset());
1103 loop {
1104 ctx.row_style = data.row_style();
1105 let render_row_area = Rect::new(0, 0, width, data.row_height());
1109 ctx.row_area = render_row_area;
1110 row_buf.resize(render_row_area);
1111 if self.auto_styles {
1112 if let Some(row_style) = ctx.row_style {
1113 row_buf.set_style(render_row_area, row_style);
1114 } else {
1115 row_buf.set_style(render_row_area, self.style);
1116 }
1117 }
1118 row_heights.push(render_row_area.height);
1119
1120 let visible_row_area = Rect::new(
1122 state.table_area.x,
1123 row_y,
1124 state.table_area.width,
1125 render_row_area.height,
1126 )
1127 .intersection(state.table_area);
1128 state.row_areas.push(visible_row_area);
1129 if render_row_area.height == visible_row_area.height {
1131 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1132 }
1133
1134 if render_row_area.height > 0 {
1136 let mut col = 0;
1137 loop {
1138 if col >= state.columns {
1139 break;
1140 }
1141
1142 let render_cell_area = Rect::new(
1143 l_columns[col].x,
1144 0,
1145 l_columns[col].width,
1146 render_row_area.height,
1147 );
1148 ctx.space_area = Rect::new(
1149 l_spacers[col + 1].x,
1150 0,
1151 l_spacers[col + 1].width,
1152 render_row_area.height,
1153 );
1154
1155 if state.selection.is_selected_cell(col, row.expect("row")) {
1156 ctx.selected_cell = true;
1157 ctx.selected_row = false;
1158 ctx.selected_column = false;
1159 ctx.select_style = self.patch_select(
1160 self.select_cell_style,
1161 state.focus.get(),
1162 self.show_cell_focus,
1163 );
1164 } else if state.selection.is_selected_row(row.expect("row")) {
1165 ctx.selected_cell = false;
1166 ctx.selected_row = true;
1167 ctx.selected_column = false;
1168 ctx.select_style = if self.select_row_style.is_some() {
1170 self.patch_select(
1171 self.select_row_style,
1172 state.focus.get(),
1173 self.show_row_focus,
1174 )
1175 } else {
1176 self.patch_select(
1177 Some(self.style),
1178 state.focus.get(),
1179 self.show_row_focus,
1180 )
1181 };
1182 } else if state.selection.is_selected_column(col) {
1183 ctx.selected_cell = false;
1184 ctx.selected_row = false;
1185 ctx.selected_column = true;
1186 ctx.select_style = self.patch_select(
1187 self.select_column_style,
1188 state.focus.get(),
1189 self.show_column_focus,
1190 );
1191 } else {
1192 ctx.selected_cell = false;
1193 ctx.selected_row = false;
1194 ctx.selected_column = false;
1195 ctx.select_style = None;
1196 }
1197
1198 if render_cell_area.right() > state.hscroll.offset as u16
1200 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1201 {
1202 if self.auto_styles {
1203 if let Some(select_style) = ctx.select_style {
1204 row_buf.set_style(render_cell_area, select_style);
1205 row_buf.set_style(ctx.space_area, select_style);
1206 }
1207 }
1208 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1209 }
1210
1211 col += 1;
1212 }
1213
1214 transfer_buffer(
1216 &mut row_buf,
1217 state.hscroll.offset() as u16,
1218 visible_row_area,
1219 buf,
1220 );
1221 }
1222
1223 if visible_row_area.bottom() >= state.table_area.bottom() {
1224 break;
1225 }
1226 if !data.nth(0) {
1227 break;
1228 }
1229 row = Some(row.expect("row").saturating_add(1));
1230 row_y += render_row_area.height;
1231 }
1232 } else {
1233 if data.rows().is_none() || data.rows() == Some(0) {
1238 } else {
1240 #[cfg(feature = "perf_warnings")]
1241 {
1242 insane_offset = true;
1243 }
1244 }
1245 }
1246
1247 #[allow(unused_variables)]
1249 let algorithm;
1250 #[allow(unused_assignments)]
1251 {
1252 if let Some(rows) = data.rows() {
1253 algorithm = 0;
1254 let skip_rows = rows
1258 .saturating_sub(row.map_or(0, |v| v + 1))
1259 .saturating_sub(state.table_area.height as usize);
1260 if skip_rows > 0 {
1262 row_heights.clear();
1263 }
1264 let nth_row = skip_rows;
1265 if data.nth(nth_row) {
1267 let mut sum_height = row_heights.iter().sum::<u16>();
1268 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1269 loop {
1270 let row_height = data.row_height();
1271 row_heights.push(row_height);
1272
1273 sum_height += row_height;
1276 if sum_height
1277 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1278 > state.table_area.height
1279 {
1280 let lost_height = row_heights.remove(0);
1281 sum_height -= lost_height;
1282 }
1283
1284 if !data.nth(0) {
1285 break;
1286 }
1287
1288 row = Some(row.expect("row") + 1);
1289 if row.expect("row") > rows {
1291 break;
1292 }
1293 }
1294 while data.nth(0) {
1297 row = Some(row.expect("row") + 1);
1298 }
1299 } else {
1300 }
1303
1304 state.rows = rows;
1305 state._counted_rows = row.map_or(0, |v| v + 1);
1306
1307 if let Some(last_page) = state.calc_last_page(row_heights) {
1309 state.vscroll.set_max_offset(state.rows - last_page);
1310 } else {
1311 state.vscroll.set_max_offset(
1315 state.rows.saturating_sub(state.table_area.height as usize),
1316 );
1317 }
1318 } else if self.no_row_count {
1319 algorithm = 1;
1320
1321 if row.is_some() {
1325 if data.nth(0) {
1326 row = Some(row.expect("row").saturating_add(1));
1328 if data.nth(0) {
1329 row = Some(usize::MAX - 1);
1331 }
1332 }
1333 }
1334
1335 state.rows = row.map_or(0, |v| v + 1);
1336 state._counted_rows = row.map_or(0, |v| v + 1);
1337 state.vscroll.set_max_offset(usize::MAX - 1);
1339 if state.vscroll.page_len() == 0 {
1340 state.vscroll.set_page_len(state.table_area.height as usize);
1341 }
1342 } else {
1343 algorithm = 2;
1344
1345 let mut sum_height = row_heights.iter().sum::<u16>();
1347 while data.nth(0) {
1348 let row_height = data.row_height();
1349 row_heights.push(row_height);
1350
1351 sum_height += row_height;
1354 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1355 > state.table_area.height
1356 {
1357 let lost_height = row_heights.remove(0);
1358 sum_height -= lost_height;
1359 }
1360 row = Some(row.map_or(0, |v| v + 1));
1361 }
1362
1363 state.rows = row.map_or(0, |v| v + 1);
1364 state._counted_rows = row.map_or(0, |v| v + 1);
1365
1366 if let Some(last_page) = state.calc_last_page(row_heights) {
1368 state.vscroll.set_max_offset(state.rows - last_page);
1369 } else {
1370 state.vscroll.set_max_offset(0);
1371 }
1372 }
1373 }
1374 {
1375 state
1376 .hscroll
1377 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1378 }
1379
1380 ScrollArea::new()
1382 .style(self.style)
1383 .block(self.block.as_ref())
1384 .h_scroll(self.hscroll.as_ref())
1385 .v_scroll(self.vscroll.as_ref())
1386 .render_scrollbars(
1387 area,
1388 buf,
1389 &mut ScrollAreaState::new()
1390 .h_scroll(&mut state.hscroll)
1391 .v_scroll(&mut state.vscroll),
1392 );
1393
1394 #[cfg(feature = "perf_warnings")]
1395 {
1396 use std::fmt::Write;
1397 let mut msg = String::new();
1398 if insane_offset {
1399 _ = write!(
1400 msg,
1401 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1402 state.vscroll.offset(),
1403 state.rows,
1404 state._counted_rows,
1405 algorithm
1406 );
1407 }
1408 if state.rows != state._counted_rows {
1409 _ = write!(
1410 msg,
1411 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1412 state.rows, state._counted_rows, algorithm
1413 );
1414 }
1415 if !msg.is_empty() {
1416 use log::warn;
1417 use ratatui::style::Stylize;
1418 use ratatui::text::Text;
1419
1420 warn!("{}", &msg);
1421 Text::from(msg)
1422 .white()
1423 .on_red()
1424 .render(state.table_area, buf);
1425 }
1426 }
1427 }
1428
1429 #[allow(clippy::too_many_arguments)]
1430 fn render_footer(
1431 &self,
1432 columns: usize,
1433 width: u16,
1434 l_columns: &[Rect],
1435 l_spacers: &[Rect],
1436 area: Rect,
1437 buf: &mut Buffer,
1438 state: &mut TableState<Selection>,
1439 ) {
1440 if let Some(footer) = &self.footer {
1441 let render_row_area = Rect::new(0, 0, width, footer.height);
1442 let mut row_buf = Buffer::empty(render_row_area);
1443
1444 row_buf.set_style(render_row_area, self.style);
1445 if let Some(footer_style) = footer.style {
1446 row_buf.set_style(render_row_area, footer_style);
1447 } else if let Some(footer_style) = self.footer_style {
1448 row_buf.set_style(render_row_area, footer_style);
1449 }
1450
1451 let mut col = 0;
1452 loop {
1453 if col >= columns {
1454 break;
1455 }
1456
1457 let render_cell_area =
1458 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1459 let render_space_area = Rect::new(
1460 l_spacers[col + 1].x,
1461 0,
1462 l_spacers[col + 1].width,
1463 area.height,
1464 );
1465
1466 if state.selection.is_selected_column(col) {
1467 if let Some(selected_style) = self.patch_select(
1468 self.select_footer_style,
1469 state.focus.get(),
1470 self.show_footer_focus,
1471 ) {
1472 row_buf.set_style(render_cell_area, selected_style);
1473 row_buf.set_style(render_space_area, selected_style);
1474 }
1475 };
1476
1477 if render_cell_area.right() > state.hscroll.offset as u16
1479 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1480 {
1481 if let Some(cell) = footer.cells.get(col) {
1482 if let Some(cell_style) = cell.style {
1483 row_buf.set_style(render_cell_area, cell_style);
1484 }
1485 cell.content.clone().render(render_cell_area, &mut row_buf);
1486 }
1487 }
1488
1489 col += 1;
1490 }
1491
1492 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1494 }
1495 }
1496
1497 #[allow(clippy::too_many_arguments)]
1498 fn render_header(
1499 &self,
1500 columns: usize,
1501 width: u16,
1502 l_columns: &[Rect],
1503 l_spacers: &[Rect],
1504 area: Rect,
1505 buf: &mut Buffer,
1506 state: &mut TableState<Selection>,
1507 ) {
1508 if let Some(header) = &self.header {
1509 let render_row_area = Rect::new(0, 0, width, header.height);
1510 let mut row_buf = Buffer::empty(render_row_area);
1511
1512 row_buf.set_style(render_row_area, self.style);
1513 if let Some(header_style) = header.style {
1514 row_buf.set_style(render_row_area, header_style);
1515 } else if let Some(header_style) = self.header_style {
1516 row_buf.set_style(render_row_area, header_style);
1517 }
1518
1519 let mut col = 0;
1520 loop {
1521 if col >= columns {
1522 break;
1523 }
1524
1525 let render_cell_area =
1526 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1527 let render_space_area = Rect::new(
1528 l_spacers[col + 1].x,
1529 0,
1530 l_spacers[col + 1].width,
1531 area.height,
1532 );
1533
1534 if state.selection.is_selected_column(col) {
1535 if let Some(selected_style) = self.patch_select(
1536 self.select_header_style,
1537 state.focus.get(),
1538 self.show_header_focus,
1539 ) {
1540 row_buf.set_style(render_cell_area, selected_style);
1541 row_buf.set_style(render_space_area, selected_style);
1542 }
1543 };
1544
1545 if render_cell_area.right() > state.hscroll.offset as u16
1547 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1548 {
1549 if let Some(cell) = header.cells.get(col) {
1550 if let Some(cell_style) = cell.style {
1551 row_buf.set_style(render_cell_area, cell_style);
1552 }
1553 cell.content.clone().render(render_cell_area, &mut row_buf);
1554 }
1555 }
1556
1557 col += 1;
1558 }
1559
1560 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1562 }
1563 }
1564
1565 fn calculate_column_areas(
1566 &self,
1567 columns: usize,
1568 l_columns: &[Rect],
1569 l_spacers: &[Rect],
1570 state: &mut TableState<Selection>,
1571 ) {
1572 state.column_areas.clear();
1573 state.column_layout.clear();
1574
1575 let mut col = 0;
1576 let shift = state.hscroll.offset() as isize;
1577 loop {
1578 if col >= columns {
1579 break;
1580 }
1581
1582 state.column_layout.push(Rect::new(
1583 l_columns[col].x,
1584 0,
1585 l_columns[col].width + l_spacers[col + 1].width,
1586 0,
1587 ));
1588
1589 let cell_x1 = l_columns[col].x as isize;
1590 let cell_x2 =
1591 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1592
1593 let squish_x1 = cell_x1.saturating_sub(shift);
1594 let squish_x2 = cell_x2.saturating_sub(shift);
1595
1596 let abs_x1 = max(0, squish_x1) as u16;
1597 let abs_x2 = max(0, squish_x2) as u16;
1598
1599 let v_area = Rect::new(
1600 state.table_area.x + abs_x1,
1601 state.table_area.y,
1602 abs_x2 - abs_x1,
1603 state.table_area.height,
1604 );
1605 state
1606 .column_areas
1607 .push(v_area.intersection(state.table_area));
1608
1609 col += 1;
1610 }
1611 }
1612
1613 #[expect(clippy::collapsible_else_if)]
1614 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1615 if let Some(style) = style {
1616 if let Some(focus_style) = self.focus_style {
1617 if focus && show {
1618 Some(style.patch(focus_style))
1619 } else {
1620 Some(fallback_select_style(style))
1621 }
1622 } else {
1623 if focus && show {
1624 Some(revert_style(style))
1625 } else {
1626 Some(fallback_select_style(style))
1627 }
1628 }
1629 } else {
1630 None
1631 }
1632 }
1633}
1634
1635impl Default for TableStyle {
1636 fn default() -> Self {
1637 Self {
1638 style: Default::default(),
1639 header: None,
1640 footer: None,
1641 select_row: None,
1642 select_column: None,
1643 select_cell: None,
1644 select_header: None,
1645 select_footer: None,
1646 show_row_focus: true, show_column_focus: false,
1648 show_cell_focus: false,
1649 show_header_focus: false,
1650 show_footer_focus: false,
1651 focus_style: None,
1652 block: None,
1653 border_style: None,
1654 title_style: None,
1655 scroll: None,
1656 non_exhaustive: NonExhaustive,
1657 }
1658 }
1659}
1660
1661impl<Selection: Clone> Clone for TableState<Selection> {
1662 fn clone(&self) -> Self {
1663 Self {
1664 focus: self.focus.new_instance(),
1665 area: self.area,
1666 inner: self.inner,
1667 header_area: self.header_area,
1668 table_area: self.table_area,
1669 row_areas: self.row_areas.clone(),
1670 column_areas: self.column_areas.clone(),
1671 column_layout: self.column_layout.clone(),
1672 footer_area: self.footer_area,
1673 rows: self.rows,
1674 _counted_rows: self._counted_rows,
1675 columns: self.columns,
1676 vscroll: self.vscroll.clone(),
1677 hscroll: self.hscroll.clone(),
1678 selection: self.selection.clone(),
1679 mouse: Default::default(),
1680 non_exhaustive: NonExhaustive,
1681 }
1682 }
1683}
1684
1685impl<Selection: Default> Default for TableState<Selection> {
1686 fn default() -> Self {
1687 Self {
1688 focus: Default::default(),
1689 area: Default::default(),
1690 inner: Default::default(),
1691 header_area: Default::default(),
1692 table_area: Default::default(),
1693 row_areas: Default::default(),
1694 column_areas: Default::default(),
1695 column_layout: Default::default(),
1696 footer_area: Default::default(),
1697 rows: Default::default(),
1698 _counted_rows: Default::default(),
1699 columns: Default::default(),
1700 vscroll: Default::default(),
1701 hscroll: Default::default(),
1702 selection: Default::default(),
1703 mouse: Default::default(),
1704 non_exhaustive: NonExhaustive,
1705 }
1706 }
1707}
1708
1709impl<Selection> HasFocus for TableState<Selection> {
1710 fn build(&self, builder: &mut FocusBuilder) {
1711 builder.leaf_widget(self);
1712 }
1713
1714 #[inline]
1715 fn focus(&self) -> FocusFlag {
1716 self.focus.clone()
1717 }
1718
1719 #[inline]
1720 fn area(&self) -> Rect {
1721 self.area
1722 }
1723}
1724
1725impl<Selection> HasScreenCursor for TableState<Selection> {
1726 fn screen_cursor(&self) -> Option<(u16, u16)> {
1727 None
1728 }
1729}
1730
1731impl<Selection> RelocatableState for TableState<Selection> {
1732 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1733 self.area.relocate(shift, clip);
1734 self.inner.relocate(shift, clip);
1735 self.header_area.relocate(shift, clip);
1736 self.table_area.relocate(shift, clip);
1737 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1738 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1739 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1740 self.footer_area.relocate(shift, clip);
1741 self.hscroll.relocate(shift, clip);
1742 self.vscroll.relocate(shift, clip);
1743 }
1744}
1745
1746impl<Selection> TableState<Selection> {
1747 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1748 let mut sum_heights = 0;
1749 let mut n_rows = 0;
1750 while let Some(h) = row_heights.pop() {
1751 sum_heights += h;
1752 n_rows += 1;
1753 if sum_heights >= self.table_area.height {
1754 break;
1755 }
1756 }
1757
1758 if sum_heights < self.table_area.height {
1759 None
1760 } else {
1761 Some(n_rows)
1762 }
1763 }
1764}
1765
1766impl<Selection> TableState<Selection>
1768where
1769 Selection: Default,
1770{
1771 pub fn new() -> Self {
1772 Self::default()
1773 }
1774
1775 pub fn named(name: &str) -> Self {
1776 Self {
1777 focus: FocusFlag::new().with_name(name),
1778 ..TableState::default()
1779 }
1780 }
1781}
1782
1783impl<Selection> TableState<Selection> {
1785 #[inline]
1787 pub fn rows(&self) -> usize {
1788 self.rows
1789 }
1790
1791 pub fn rows_changed(&mut self, rows: usize) {
1803 self.rows = rows;
1804 self.vscroll
1805 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1806 }
1807
1808 #[inline]
1810 pub fn columns(&self) -> usize {
1811 self.columns
1812 }
1813}
1814
1815impl<Selection> TableState<Selection> {
1817 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1823 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1824 return None;
1825 }
1826
1827 let mut areas = Vec::new();
1828
1829 let r = self.row_areas[row - self.vscroll.offset()];
1830 for c in &self.column_areas {
1831 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1832 }
1833
1834 Some((r, areas))
1835 }
1836
1837 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1839 let col = self.column_at_clicked(pos);
1840 let row = self.row_at_clicked(pos);
1841
1842 match (col, row) {
1843 (Some(col), Some(row)) => Some((col, row)),
1844 _ => None,
1845 }
1846 }
1847
1848 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1850 self.mouse.column_at(&self.column_areas, pos.0)
1851 }
1852
1853 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1855 self.mouse
1856 .row_at(&self.row_areas, pos.1)
1857 .map(|v| self.vscroll.offset() + v)
1858 }
1859
1860 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1863 let col = self.column_at_drag(pos);
1864 let row = self.row_at_drag(pos);
1865
1866 (col, row)
1867 }
1868
1869 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1876 match self
1877 .mouse
1878 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1879 {
1880 Ok(v) => self.vscroll.offset() + v,
1881 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1882 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1883 }
1884 }
1885
1886 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1890 match self
1891 .mouse
1892 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1893 {
1894 Ok(v) => v,
1895 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1896 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1897 }
1898 }
1899}
1900
1901impl<Selection: TableSelection> TableState<Selection> {
1903 pub fn clear_offset(&mut self) {
1905 self.vscroll.set_offset(0);
1906 self.hscroll.set_offset(0);
1907 }
1908
1909 pub fn row_max_offset(&self) -> usize {
1914 self.vscroll.max_offset()
1915 }
1916
1917 pub fn row_offset(&self) -> usize {
1919 self.vscroll.offset()
1920 }
1921
1922 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1929 self.vscroll.set_offset(offset)
1930 }
1931
1932 pub fn page_len(&self) -> usize {
1934 self.vscroll.page_len()
1935 }
1936
1937 pub fn row_scroll_by(&self) -> usize {
1939 self.vscroll.scroll_by()
1940 }
1941
1942 pub fn x_max_offset(&self) -> usize {
1947 self.hscroll.max_offset()
1948 }
1949
1950 pub fn x_offset(&self) -> usize {
1952 self.hscroll.offset()
1953 }
1954
1955 pub fn set_x_offset(&mut self, offset: usize) -> bool {
1962 self.hscroll.set_offset(offset)
1963 }
1964
1965 pub fn page_width(&self) -> usize {
1967 self.hscroll.page_len()
1968 }
1969
1970 pub fn x_scroll_by(&self) -> usize {
1972 self.hscroll.scroll_by()
1973 }
1974
1975 pub fn scroll_to_selected(&mut self) -> bool {
1979 if let Some(selected) = self.selection.lead_selection() {
1980 let c = self.scroll_to_col(selected.0);
1981 let r = self.scroll_to_row(selected.1);
1982 r || c
1983 } else {
1984 false
1985 }
1986 }
1987
1988 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
1993 if pos >= self.rows {
1994 false
1995 } else if pos == self.row_offset().saturating_add(self.page_len()) {
1996 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
1998 if heights < self.table_area.height {
1999 false
2000 } else {
2001 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2002 }
2003 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2004 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2005 } else if pos < self.row_offset() {
2006 self.set_row_offset(pos)
2007 } else {
2008 false
2009 }
2010 }
2011
2012 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2014 if let Some(col) = self.column_layout.get(pos) {
2015 if (col.left() as usize) < self.x_offset() {
2016 self.set_x_offset(col.x as usize)
2017 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2018 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2019 } else {
2020 false
2021 }
2022 } else {
2023 false
2024 }
2025 }
2026
2027 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2029 if pos >= self.x_offset().saturating_add(self.page_width()) {
2030 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2031 } else if pos < self.x_offset() {
2032 self.set_x_offset(pos)
2033 } else {
2034 false
2035 }
2036 }
2037
2038 pub fn scroll_up(&mut self, n: usize) -> bool {
2040 self.vscroll.scroll_up(n)
2041 }
2042
2043 pub fn scroll_down(&mut self, n: usize) -> bool {
2045 self.vscroll.scroll_down(n)
2046 }
2047
2048 pub fn scroll_left(&mut self, n: usize) -> bool {
2050 self.hscroll.scroll_left(n)
2051 }
2052
2053 pub fn scroll_right(&mut self, n: usize) -> bool {
2055 self.hscroll.scroll_right(n)
2056 }
2057}
2058
2059impl TableState<RowSelection> {
2060 pub fn items_added(&mut self, pos: usize, n: usize) {
2064 self.vscroll.items_added(pos, n);
2065 self.selection.items_added(pos, n);
2066 self.rows += n;
2067 }
2068
2069 pub fn items_removed(&mut self, pos: usize, n: usize) {
2073 self.vscroll.items_removed(pos, n);
2074 self.selection
2075 .items_removed(pos, n, self.rows.saturating_sub(1));
2076 self.rows -= n;
2077 }
2078
2079 #[inline]
2082 pub fn set_scroll_selection(&mut self, scroll: bool) {
2083 self.selection.set_scroll_selected(scroll);
2084 }
2085
2086 #[inline]
2088 pub fn clear_selection(&mut self) {
2089 self.selection.clear();
2090 }
2091
2092 #[inline]
2094 pub fn has_selection(&mut self) -> bool {
2095 self.selection.has_selection()
2096 }
2097
2098 #[inline]
2101 pub fn selected(&self) -> Option<usize> {
2102 self.selection.selected()
2103 }
2104
2105 #[inline]
2108 #[allow(clippy::manual_filter)]
2109 pub fn selected_checked(&self) -> Option<usize> {
2110 if let Some(selected) = self.selection.selected() {
2111 if selected < self.rows {
2112 Some(selected)
2113 } else {
2114 None
2115 }
2116 } else {
2117 None
2118 }
2119 }
2120
2121 #[inline]
2124 pub fn select(&mut self, row: Option<usize>) -> bool {
2125 self.selection.select(row)
2126 }
2127
2128 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2132 if self.vscroll.max_offset() > 0 {
2133 (self.rows * offset) / self.vscroll.max_offset()
2134 } else {
2135 0 }
2137 }
2138
2139 #[inline]
2141 pub fn move_deselect(&mut self) -> bool {
2142 let r = self.selection.select(None);
2143 let s = self.set_row_offset(0);
2144 r || s
2145 }
2146
2147 #[inline]
2150 pub fn move_to(&mut self, row: usize) -> bool {
2151 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2152 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2153 r || s
2154 }
2155
2156 #[inline]
2159 pub fn move_up(&mut self, n: usize) -> bool {
2160 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2161 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2162 r || s
2163 }
2164
2165 #[inline]
2168 pub fn move_down(&mut self, n: usize) -> bool {
2169 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2170 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2171 r || s
2172 }
2173}
2174
2175impl TableState<RowSetSelection> {
2176 #[inline]
2178 pub fn clear_selection(&mut self) {
2179 self.selection.clear();
2180 }
2181
2182 #[inline]
2184 pub fn has_selection(&mut self) -> bool {
2185 self.selection.has_selection()
2186 }
2187
2188 #[inline]
2190 pub fn selected(&self) -> HashSet<usize> {
2191 self.selection.selected()
2192 }
2193
2194 #[inline]
2201 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2202 self.selection.set_lead(row, extend)
2203 }
2204
2205 #[inline]
2207 pub fn lead(&self) -> Option<usize> {
2208 self.selection.lead()
2209 }
2210
2211 #[inline]
2213 pub fn anchor(&self) -> Option<usize> {
2214 self.selection.anchor()
2215 }
2216
2217 #[inline]
2220 pub fn retire_selection(&mut self) {
2221 self.selection.retire_selection();
2222 }
2223
2224 #[inline]
2229 pub fn add_selected(&mut self, idx: usize) {
2230 self.selection.add(idx);
2231 }
2232
2233 #[inline]
2238 pub fn remove_selected(&mut self, idx: usize) {
2239 self.selection.remove(idx);
2240 }
2241
2242 #[inline]
2245 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2246 let r = self
2247 .selection
2248 .move_to(row, self.rows.saturating_sub(1), extend);
2249 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2250 r || s
2251 }
2252
2253 #[inline]
2256 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2257 let r = self
2258 .selection
2259 .move_up(n, self.rows.saturating_sub(1), extend);
2260 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2261 r || s
2262 }
2263
2264 #[inline]
2267 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2268 let r = self
2269 .selection
2270 .move_down(n, self.rows.saturating_sub(1), extend);
2271 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2272 r || s
2273 }
2274}
2275
2276impl TableState<CellSelection> {
2277 #[inline]
2278 pub fn clear_selection(&mut self) {
2279 self.selection.clear();
2280 }
2281
2282 #[inline]
2283 pub fn has_selection(&mut self) -> bool {
2284 self.selection.has_selection()
2285 }
2286
2287 #[inline]
2289 pub fn selected(&self) -> Option<(usize, usize)> {
2290 self.selection.selected()
2291 }
2292
2293 #[inline]
2295 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2296 self.selection.select_cell(select)
2297 }
2298
2299 #[inline]
2301 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2302 if let Some(row) = row {
2303 self.selection
2304 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2305 } else {
2306 self.selection.select_row(None)
2307 }
2308 }
2309
2310 #[inline]
2312 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2313 if let Some(column) = column {
2314 self.selection
2315 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2316 } else {
2317 self.selection.select_column(None)
2318 }
2319 }
2320
2321 #[inline]
2323 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2324 let r = self.selection.move_to(
2325 select,
2326 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2327 );
2328 let s = self.scroll_to_selected();
2329 r || s
2330 }
2331
2332 #[inline]
2334 pub fn move_to_row(&mut self, row: usize) -> bool {
2335 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2336 let s = self.scroll_to_selected();
2337 r || s
2338 }
2339
2340 #[inline]
2342 pub fn move_to_col(&mut self, col: usize) -> bool {
2343 let r = self
2344 .selection
2345 .move_to_col(col, self.columns.saturating_sub(1));
2346 let s = self.scroll_to_selected();
2347 r || s
2348 }
2349
2350 #[inline]
2353 pub fn move_up(&mut self, n: usize) -> bool {
2354 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2355 let s = self.scroll_to_selected();
2356 r || s
2357 }
2358
2359 #[inline]
2362 pub fn move_down(&mut self, n: usize) -> bool {
2363 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2364 let s = self.scroll_to_selected();
2365 r || s
2366 }
2367
2368 #[inline]
2371 pub fn move_left(&mut self, n: usize) -> bool {
2372 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2373 let s = self.scroll_to_selected();
2374 r || s
2375 }
2376
2377 #[inline]
2380 pub fn move_right(&mut self, n: usize) -> bool {
2381 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2382 let s = self.scroll_to_selected();
2383 r || s
2384 }
2385}
2386
2387impl<Selection> HandleEvent<crossterm::event::Event, DoubleClick, DoubleClickOutcome>
2388 for TableState<Selection>
2389{
2390 fn handle(
2392 &mut self,
2393 event: &crossterm::event::Event,
2394 _keymap: DoubleClick,
2395 ) -> DoubleClickOutcome {
2396 match event {
2397 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2398 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2399 DoubleClickOutcome::ClickClick(col, row)
2400 } else {
2401 DoubleClickOutcome::Continue
2402 }
2403 }
2404 _ => DoubleClickOutcome::Continue,
2405 }
2406 }
2407}
2408
2409pub fn handle_doubleclick_events<Selection: TableSelection>(
2411 state: &mut TableState<Selection>,
2412 event: &crossterm::event::Event,
2413) -> DoubleClickOutcome {
2414 state.handle(event, DoubleClick)
2415}