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_core::buffer::Buffer;
17use ratatui_core::layout::{Constraint, Flex, Layout, Rect};
18use ratatui_core::style::Style;
19use ratatui_core::text::Span;
20use ratatui_core::widgets::{StatefulWidget, Widget};
21use ratatui_crossterm::crossterm::event::Event;
22use ratatui_widgets::block::Block;
23use std::borrow::Cow;
24use std::cmp::{max, min};
25use std::collections::HashSet;
26use std::fmt::Debug;
27use std::marker::PhantomData;
28use std::mem;
29use std::rc::Rc;
30
31#[derive(Debug)]
46pub struct Table<'a, Selection = RowSelection> {
47 data: DataRepr<'a>,
48 no_row_count: bool,
49
50 header: Option<Row<'a>>,
51 footer: Option<Row<'a>>,
52
53 widths: Vec<Constraint>,
54 flex: Flex,
55 column_spacing: u16,
56 layout_width: Option<u16>,
57 layout_column_widths: bool,
58
59 style: Style,
60 block: Option<Block<'a>>,
61 hscroll: Option<Scroll<'a>>,
62 vscroll: Option<Scroll<'a>>,
63 header_style: Option<Style>,
64 footer_style: Option<Style>,
65 focus_style: Option<Style>,
66
67 auto_styles: bool,
68 select_row_style: Option<Style>,
69 show_row_focus: bool,
70 select_column_style: Option<Style>,
71 show_column_focus: bool,
72 select_cell_style: Option<Style>,
73 show_cell_focus: bool,
74 select_header_style: Option<Style>,
75 show_header_focus: bool,
76 select_footer_style: Option<Style>,
77 show_footer_focus: bool,
78
79 show_empty: bool,
80 empty_str: Cow<'a, str>,
81
82 _phantom: PhantomData<Selection>,
83}
84
85mod data {
86 use crate::textdata::TextTableData;
87 use crate::{TableContext, TableData, TableDataIter};
88 #[cfg(feature = "perf_warnings")]
89 use log::warn;
90 use ratatui_core::buffer::Buffer;
91 use ratatui_core::layout::Rect;
92 use ratatui_core::style::Style;
93 use std::fmt::{Debug, Formatter};
94
95 #[derive(Default)]
96 pub(super) enum DataRepr<'a> {
97 #[default]
98 None,
99 Text(TextTableData<'a>),
100 Data(Box<dyn TableData<'a> + 'a>),
101 Iter(Box<dyn TableDataIter<'a> + 'a>),
102 }
103
104 impl<'a> DataRepr<'a> {
105 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
106 match self {
107 DataRepr::None => DataReprIter::None,
108 DataRepr::Text(v) => DataReprIter::IterText(v, None),
109 DataRepr::Data(v) => DataReprIter::IterData(v, None),
110 DataRepr::Iter(v) => DataReprIter::IterIter(v),
111 }
112 }
113
114 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
115 match self {
116 DataRepr::None => DataReprIter::None,
117 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
118 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
119 DataRepr::Iter(v) => {
120 if let Some(v) = v.cloned() {
122 DataReprIter::IterIter(v)
123 } else {
124 DataReprIter::Invalid(None)
125 }
126 }
127 }
128 }
129 }
130
131 impl Debug for DataRepr<'_> {
132 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("Data").finish()
134 }
135 }
136
137 #[derive(Default)]
138 pub(super) enum DataReprIter<'a, 'b> {
139 #[default]
140 None,
141 #[allow(dead_code)]
142 Invalid(Option<usize>),
143 IterText(TextTableData<'a>, Option<usize>),
144 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
145 #[allow(dead_code)]
146 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
147 IterIter(Box<dyn TableDataIter<'a> + 'a>),
148 }
149
150 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
151 fn rows(&self) -> Option<usize> {
152 match self {
153 DataReprIter::None => Some(0),
154 DataReprIter::Invalid(_) => Some(1),
155 DataReprIter::IterText(v, _) => Some(v.rows.len()),
156 DataReprIter::IterData(v, _) => Some(v.rows()),
157 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
158 DataReprIter::IterIter(v) => v.rows(),
159 }
160 }
161
162 fn nth(&mut self, n: usize) -> bool {
163 let incr = |row: &mut Option<usize>, rows: usize| match *row {
164 None => {
165 *row = Some(n);
166 *row < Some(rows)
167 }
168 Some(w) => {
169 *row = Some(w.saturating_add(n).saturating_add(1));
170 *row < Some(rows)
171 }
172 };
173
174 match self {
175 DataReprIter::None => false,
176 DataReprIter::Invalid(row) => incr(row, 1),
177 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
178 DataReprIter::IterData(v, row) => incr(row, v.rows()),
179 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
180 DataReprIter::IterIter(v) => v.nth(n),
181 }
182 }
183
184 fn row_height(&self) -> u16 {
186 match self {
187 DataReprIter::None => 1,
188 DataReprIter::Invalid(_) => 1,
189 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
190 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
191 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
192 DataReprIter::IterIter(v) => v.row_height(),
193 }
194 }
195
196 fn row_style(&self) -> Option<Style> {
197 match self {
198 DataReprIter::None => None,
199 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
200 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
201 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
202 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
203 DataReprIter::IterIter(v) => v.row_style(),
204 }
205 }
206
207 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
209 match self {
210 DataReprIter::None => {}
211 DataReprIter::Invalid(_) => {
212 if column == 0 {
213 #[cfg(feature = "perf_warnings")]
214 warn!(
215 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
216 );
217
218 buf.set_string(
219 area.x,
220 area.y,
221 "TableDataIter must implement a valid cloned() for this",
222 Style::default(),
223 );
224 }
225 }
226 DataReprIter::IterText(v, n) => {
227 v.render_cell(ctx, column, n.expect("row"), area, buf)
228 }
229 DataReprIter::IterData(v, n) => {
230 v.render_cell(ctx, column, n.expect("row"), area, buf)
231 }
232 DataReprIter::IterDataRef(v, n) => {
233 v.render_cell(ctx, column, n.expect("row"), area, buf)
234 }
235 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
236 }
237 }
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct TableStyle {
244 pub style: Style,
245 pub block: Option<Block<'static>>,
246 pub border_style: Option<Style>,
247 pub title_style: Option<Style>,
248 pub scroll: Option<ScrollStyle>,
249 pub header: Option<Style>,
250 pub footer: Option<Style>,
251 pub focus_style: Option<Style>,
252
253 pub select_row: Option<Style>,
254 pub select_column: Option<Style>,
255 pub select_cell: Option<Style>,
256 pub select_header: Option<Style>,
257 pub select_footer: Option<Style>,
258
259 pub show_row_focus: bool,
260 pub show_column_focus: bool,
261 pub show_cell_focus: bool,
262 pub show_header_focus: bool,
263 pub show_footer_focus: bool,
264 pub show_empty: bool,
265 pub empty_str: Option<Cow<'static, str>>,
266
267 pub non_exhaustive: NonExhaustive,
268}
269
270#[derive(Debug)]
272pub struct TableState<Selection = RowSelection> {
273 pub focus: FocusFlag,
276
277 pub area: Rect,
280 pub inner: Rect,
283
284 pub header_area: Rect,
287 pub table_area: Rect,
290 pub row_areas: Vec<Rect>,
293 pub column_areas: Vec<Rect>,
297 pub column_layout: Vec<Rect>,
301 pub footer_area: Rect,
304
305 pub rows: usize,
308 pub _counted_rows: usize,
310 pub columns: usize,
313
314 pub vscroll: ScrollState,
317 pub hscroll: ScrollState,
320
321 pub selection: Selection,
324
325 pub mouse: MouseFlags,
327
328 pub non_exhaustive: NonExhaustive,
329}
330
331impl<Selection> Default for Table<'_, Selection> {
332 fn default() -> Self {
333 Self {
334 data: Default::default(),
335 no_row_count: Default::default(),
336 header: Default::default(),
337 footer: Default::default(),
338 widths: Default::default(),
339 flex: Default::default(),
340 column_spacing: Default::default(),
341 layout_width: Default::default(),
342 layout_column_widths: true,
343 block: Default::default(),
344 hscroll: Default::default(),
345 vscroll: Default::default(),
346 header_style: Default::default(),
347 footer_style: Default::default(),
348 style: Default::default(),
349 auto_styles: true,
350 select_row_style: Default::default(),
351 show_row_focus: true,
352 select_column_style: Default::default(),
353 show_column_focus: Default::default(),
354 select_cell_style: Default::default(),
355 show_cell_focus: Default::default(),
356 select_header_style: Default::default(),
357 show_header_focus: Default::default(),
358 select_footer_style: Default::default(),
359 show_footer_focus: Default::default(),
360 focus_style: Default::default(),
361 _phantom: Default::default(),
362 show_empty: Default::default(),
363 empty_str: Cow::Borrowed(" \u{2205} "),
364 }
365 }
366}
367
368impl<'a, Selection> Table<'a, Selection> {
369 pub fn new() -> Self
371 where
372 Selection: Default,
373 {
374 Self::default()
375 }
376
377 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
382 where
383 R: IntoIterator,
384 R::Item: Into<Row<'a>>,
385 C: IntoIterator,
386 C::Item: Into<Constraint>,
387 Selection: Default,
388 {
389 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
390 let data = TextTableData {
391 rows: rows.into_iter().map(|v| v.into()).collect(),
392 };
393 Self {
394 data: DataRepr::Text(data),
395 widths,
396 ..Default::default()
397 }
398 }
399
400 pub fn rows<T>(mut self, rows: T) -> Self
404 where
405 T: IntoIterator<Item = Row<'a>>,
406 {
407 let rows = rows.into_iter().collect();
408 self.data = DataRepr::Text(TextTableData { rows });
409 self
410 }
411
412 #[inline]
479 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
480 self.widths = data.widths();
481 self.header = data.header();
482 self.footer = data.footer();
483 self.data = DataRepr::Data(Box::new(data));
484 self
485 }
486
487 #[inline]
596 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
597 #[cfg(feature = "perf_warnings")]
598 if data.rows().is_none() {
599 use log::warn;
600 warn!("Table::iter - rows is None, this will be slower");
601 }
602 self.header = data.header();
603 self.footer = data.footer();
604 self.widths = data.widths();
605 self.data = DataRepr::Iter(Box::new(data));
606 self
607 }
608
609 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
627 self.no_row_count = no_row_count;
628 self
629 }
630
631 #[inline]
633 pub fn header(mut self, header: Row<'a>) -> Self {
634 self.header = Some(header);
635 self
636 }
637
638 #[inline]
640 pub fn footer(mut self, footer: Row<'a>) -> Self {
641 self.footer = Some(footer);
642 self
643 }
644
645 pub fn widths<I>(mut self, widths: I) -> Self
647 where
648 I: IntoIterator,
649 I::Item: Into<Constraint>,
650 {
651 self.widths = widths.into_iter().map(|v| v.into()).collect();
652 self
653 }
654
655 #[inline]
657 pub fn flex(mut self, flex: Flex) -> Self {
658 self.flex = flex;
659 self
660 }
661
662 #[inline]
664 pub fn column_spacing(mut self, spacing: u16) -> Self {
665 self.column_spacing = spacing;
666 self
667 }
668
669 #[inline]
672 pub fn layout_width(mut self, width: u16) -> Self {
673 self.layout_width = Some(width);
674 self
675 }
676
677 #[inline]
686 pub fn layout_column_widths(mut self) -> Self {
687 self.layout_column_widths = true;
688 self
689 }
690
691 #[deprecated(since = "1.1.1", note = "no longer supported")]
698 #[inline]
699 pub fn auto_layout_width(self) -> Self {
700 self
701 }
702
703 #[inline]
705 pub fn block(mut self, block: Block<'a>) -> Self {
706 self.block = Some(block);
707 self.block = self.block.map(|v| v.style(self.style));
708 self
709 }
710
711 pub fn border_style(mut self, style: Style) -> Self {
713 self.block = self.block.map(|v| v.border_style(style));
714 self
715 }
716
717 pub fn title_style(mut self, style: Style) -> Self {
719 self.block = self.block.map(|v| v.title_style(style));
720 self
721 }
722
723 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
725 self.hscroll = Some(scroll.clone().override_horizontal());
726 self.vscroll = Some(scroll.override_vertical());
727 self
728 }
729
730 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
732 self.hscroll = Some(scroll.override_horizontal());
733 self
734 }
735
736 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
738 self.vscroll = Some(scroll.override_vertical());
739 self
740 }
741
742 #[inline]
744 pub fn styles(mut self, styles: TableStyle) -> Self {
745 self.style = styles.style;
746 if styles.block.is_some() {
747 self.block = styles.block;
748 }
749 if let Some(border_style) = styles.border_style {
750 self.block = self.block.map(|v| v.border_style(border_style));
751 }
752 if let Some(title_style) = styles.title_style {
753 self.block = self.block.map(|v| v.title_style(title_style));
754 }
755 self.block = self.block.map(|v| v.style(self.style));
756
757 if let Some(styles) = styles.scroll {
758 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
759 self.vscroll = self.vscroll.map(|v| v.styles(styles));
760 }
761 if styles.header.is_some() {
762 self.header_style = styles.header;
763 }
764 if styles.footer.is_some() {
765 self.footer_style = styles.footer;
766 }
767 if styles.select_row.is_some() {
768 self.select_row_style = styles.select_row;
769 }
770 self.show_row_focus = styles.show_row_focus;
771 if styles.select_column.is_some() {
772 self.select_column_style = styles.select_column;
773 }
774 self.show_column_focus = styles.show_column_focus;
775 if styles.select_cell.is_some() {
776 self.select_cell_style = styles.select_cell;
777 }
778 self.show_cell_focus = styles.show_cell_focus;
779 if styles.select_header.is_some() {
780 self.select_header_style = styles.select_header;
781 }
782 self.show_header_focus = styles.show_header_focus;
783 if styles.select_footer.is_some() {
784 self.select_footer_style = styles.select_footer;
785 }
786 self.show_footer_focus = styles.show_footer_focus;
787 if styles.focus_style.is_some() {
788 self.focus_style = styles.focus_style;
789 }
790 self.show_empty = styles.show_empty;
791 if let Some(empty_str) = styles.empty_str {
792 self.empty_str = empty_str;
793 }
794 self
795 }
796
797 #[inline]
799 pub fn style(mut self, style: Style) -> Self {
800 self.style = style;
801 self.block = self.block.map(|v| v.style(self.style));
802 self
803 }
804
805 #[inline]
807 pub fn header_style(mut self, style: Option<Style>) -> Self {
808 self.header_style = style;
809 self
810 }
811
812 #[inline]
814 pub fn footer_style(mut self, style: Option<Style>) -> Self {
815 self.footer_style = style;
816 self
817 }
818
819 #[inline]
825 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
826 self.auto_styles = auto_styles;
827 self
828 }
829
830 #[inline]
833 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
834 self.select_row_style = select_style;
835 self
836 }
837
838 #[inline]
840 pub fn show_row_focus(mut self, show: bool) -> Self {
841 self.show_row_focus = show;
842 self
843 }
844
845 #[inline]
848 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
849 self.select_column_style = select_style;
850 self
851 }
852
853 #[inline]
855 pub fn show_column_focus(mut self, show: bool) -> Self {
856 self.show_column_focus = show;
857 self
858 }
859
860 #[inline]
863 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
864 self.select_cell_style = select_style;
865 self
866 }
867
868 #[inline]
870 pub fn show_cell_focus(mut self, show: bool) -> Self {
871 self.show_cell_focus = show;
872 self
873 }
874
875 #[inline]
878 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
879 self.select_header_style = select_style;
880 self
881 }
882
883 #[inline]
885 pub fn show_header_focus(mut self, show: bool) -> Self {
886 self.show_header_focus = show;
887 self
888 }
889
890 #[inline]
893 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
894 self.select_footer_style = select_style;
895 self
896 }
897
898 #[inline]
900 pub fn show_footer_focus(mut self, show: bool) -> Self {
901 self.show_footer_focus = show;
902 self
903 }
904
905 #[inline]
911 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
912 self.focus_style = focus_style;
913 self
914 }
915
916 #[inline]
918 pub fn show_empty(mut self, show: bool) -> Self {
919 self.show_empty = show;
920 self
921 }
922
923 #[inline]
924 pub fn show_empty_str(mut self, str: impl Into<Cow<'a, str>>) -> Self {
925 self.empty_str = str.into();
926 self
927 }
928
929 pub fn width(&self) -> u16 {
931 let sa = ScrollArea::new()
932 .style(self.style)
933 .block(self.block.as_ref())
934 .h_scroll(self.hscroll.as_ref())
935 .v_scroll(self.vscroll.as_ref());
936 let padding = sa.padding();
937 let width = self.total_width(0);
938
939 width + padding.left + padding.right
940 }
941
942 pub fn heigth(&self) -> u16 {
944 let sa = ScrollArea::new()
945 .style(self.style)
946 .block(self.block.as_ref())
947 .h_scroll(self.hscroll.as_ref())
948 .v_scroll(self.vscroll.as_ref());
949 let padding = sa.padding();
950 let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
951 let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
952
953 1 + header + footer + padding.top + padding.bottom
954 }
955
956 #[deprecated(since = "1.1.1", note = "not in use")]
957 pub fn debug(self, _: bool) -> Self {
958 self
959 }
960}
961
962impl<Selection> Table<'_, Selection> {
963 #[inline]
965 fn total_width(&self, area_width: u16) -> u16 {
966 if let Some(layout_width) = self.layout_width {
967 layout_width
968 } else if self.layout_column_widths {
969 let mut width = 0;
970 for w in self.widths.iter().copied() {
971 match w {
972 Constraint::Min(v) => width += v + self.column_spacing,
973 Constraint::Max(v) => width += v + self.column_spacing,
974 Constraint::Length(v) => width += v + self.column_spacing,
975 Constraint::Percentage(p) => {
976 width += (((area_width as u32) * (p as u32)) / 100) as u16;
977 }
978 Constraint::Ratio(n, d) => {
979 width += (((area_width as u32) * n) / d) as u16;
980 }
981 Constraint::Fill(_) => {
982 width += 10;
984 }
985 }
986 }
987 max(width, area_width)
988 } else {
989 area_width
990 }
991 }
992
993 #[inline]
995 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
996 let width = self.total_width(width);
997 let area = Rect::new(0, 0, width, 0);
998
999 let (layout, spacers) = Layout::horizontal(&self.widths)
1000 .flex(self.flex)
1001 .spacing(self.column_spacing)
1002 .split_with_spacers(area);
1003
1004 (width, layout, spacers)
1005 }
1006
1007 #[inline]
1009 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
1010 let heights = vec![
1011 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
1012 Constraint::Fill(1),
1013 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
1014 ];
1015
1016 Layout::vertical(heights).split(area)
1017 }
1018}
1019
1020impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
1021where
1022 Selection: TableSelection,
1023{
1024 type State = TableState<Selection>;
1025
1026 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1027 let iter = self.data.iter();
1028 self.render_iter(iter, area, buf, state);
1029 }
1030}
1031
1032impl<Selection> StatefulWidget for Table<'_, Selection>
1033where
1034 Selection: TableSelection,
1035{
1036 type State = TableState<Selection>;
1037
1038 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1039 let iter = mem::take(&mut self.data).into_iter();
1040 self.render_iter(iter, area, buf, state);
1041 }
1042}
1043
1044impl<'a, Selection> Table<'a, Selection>
1045where
1046 Selection: TableSelection,
1047{
1048 fn render_iter<'b>(
1053 &self,
1054 mut data: DataReprIter<'a, 'b>,
1055 area: Rect,
1056 buf: &mut Buffer,
1057 state: &mut TableState<Selection>,
1058 ) {
1059 if let Some(rows) = data.rows() {
1060 state.rows = rows;
1061 }
1062 state.columns = self.widths.len();
1063 state.area = area;
1064
1065 let sa = ScrollArea::new()
1066 .style(self.style)
1067 .block(self.block.as_ref())
1068 .h_scroll(self.hscroll.as_ref())
1069 .v_scroll(self.vscroll.as_ref());
1070 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1071
1072 let l_rows = self.layout_areas(state.inner);
1073 state.header_area = l_rows[0];
1074 state.table_area = l_rows[1];
1075 state.footer_area = l_rows[2];
1076
1077 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1079 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1080
1081 sa.render_block(area, buf);
1083
1084 self.render_header(
1086 state.columns,
1087 width,
1088 l_columns.as_ref(),
1089 l_spacers.as_ref(),
1090 state.header_area,
1091 buf,
1092 state,
1093 );
1094 self.render_footer(
1095 state.columns,
1096 width,
1097 l_columns.as_ref(),
1098 l_spacers.as_ref(),
1099 state.footer_area,
1100 buf,
1101 state,
1102 );
1103
1104 state.row_areas.clear();
1106 state.vscroll.set_page_len(0);
1107 state.hscroll.set_page_len(area.width as usize);
1108
1109 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1110 let mut row = None;
1111 let mut row_y = state.table_area.y;
1112 let mut row_heights = Vec::new();
1113 #[cfg(feature = "perf_warnings")]
1114 let mut insane_offset = false;
1115
1116 let mut ctx = TableContext {
1117 focus: state.focus.get(),
1118 selected_cell: false,
1119 selected_row: false,
1120 selected_column: false,
1121 style: self.style,
1122 row_style: None,
1123 select_style: None,
1124 space_area: Default::default(),
1125 row_area: Default::default(),
1126 non_exhaustive: NonExhaustive,
1127 };
1128
1129 if data.nth(state.vscroll.offset()) {
1130 row = Some(state.vscroll.offset());
1131 loop {
1132 ctx.row_style = data.row_style();
1133 let render_row_area = Rect::new(0, 0, width, data.row_height());
1137 ctx.row_area = render_row_area;
1138 row_buf.resize(render_row_area);
1139 if self.auto_styles {
1140 if let Some(row_style) = ctx.row_style {
1141 row_buf.set_style(render_row_area, row_style);
1142 } else {
1143 row_buf.set_style(render_row_area, self.style);
1144 }
1145 }
1146 row_heights.push(render_row_area.height);
1147
1148 let visible_row_area = Rect::new(
1150 state.table_area.x,
1151 row_y,
1152 state.table_area.width,
1153 render_row_area.height,
1154 )
1155 .intersection(state.table_area);
1156 state.row_areas.push(visible_row_area);
1157 if render_row_area.height == visible_row_area.height {
1159 state.vscroll.set_page_len(state.vscroll.page_len() + 1);
1160 }
1161
1162 if render_row_area.height > 0 {
1164 let mut col = 0;
1165 loop {
1166 if col >= state.columns {
1167 break;
1168 }
1169
1170 let render_cell_area = Rect::new(
1171 l_columns[col].x,
1172 0,
1173 l_columns[col].width,
1174 render_row_area.height,
1175 );
1176 ctx.space_area = Rect::new(
1177 l_spacers[col + 1].x,
1178 0,
1179 l_spacers[col + 1].width,
1180 render_row_area.height,
1181 );
1182
1183 if state.selection.is_selected_cell(col, row.expect("row")) {
1184 ctx.selected_cell = true;
1185 ctx.selected_row = false;
1186 ctx.selected_column = false;
1187 ctx.select_style = self.patch_select(
1188 self.select_cell_style,
1189 state.focus.get(),
1190 self.show_cell_focus,
1191 );
1192 } else if state.selection.is_selected_row(row.expect("row")) {
1193 ctx.selected_cell = false;
1194 ctx.selected_row = true;
1195 ctx.selected_column = false;
1196 ctx.select_style = if self.select_row_style.is_some() {
1198 self.patch_select(
1199 self.select_row_style,
1200 state.focus.get(),
1201 self.show_row_focus,
1202 )
1203 } else {
1204 self.patch_select(
1205 Some(self.style),
1206 state.focus.get(),
1207 self.show_row_focus,
1208 )
1209 };
1210 } else if state.selection.is_selected_column(col) {
1211 ctx.selected_cell = false;
1212 ctx.selected_row = false;
1213 ctx.selected_column = true;
1214 ctx.select_style = self.patch_select(
1215 self.select_column_style,
1216 state.focus.get(),
1217 self.show_column_focus,
1218 );
1219 } else {
1220 ctx.selected_cell = false;
1221 ctx.selected_row = false;
1222 ctx.selected_column = false;
1223 ctx.select_style = None;
1224 }
1225
1226 if render_cell_area.right() > state.hscroll.offset as u16
1228 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1229 {
1230 if self.auto_styles {
1231 if let Some(select_style) = ctx.select_style {
1232 row_buf.set_style(render_cell_area, select_style);
1233 row_buf.set_style(ctx.space_area, select_style);
1234 }
1235 }
1236 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1237 }
1238
1239 col += 1;
1240 }
1241
1242 transfer_buffer(
1244 &mut row_buf,
1245 state.hscroll.offset() as u16,
1246 visible_row_area,
1247 buf,
1248 );
1249 }
1250
1251 if visible_row_area.bottom() >= state.table_area.bottom() {
1252 break;
1253 }
1254 if !data.nth(0) {
1255 break;
1256 }
1257 row = Some(row.expect("row").saturating_add(1));
1258 row_y += render_row_area.height;
1259 }
1260 } else {
1261 if data.rows().is_none() || data.rows() == Some(0) {
1266 } else {
1268 #[cfg(feature = "perf_warnings")]
1269 {
1270 insane_offset = true;
1271 }
1272 }
1273 }
1274
1275 #[allow(unused_variables)]
1277 let algorithm;
1278 #[allow(unused_assignments)]
1279 {
1280 if let Some(rows) = data.rows() {
1281 algorithm = 0;
1282 let skip_rows = rows
1286 .saturating_sub(row.map_or(0, |v| v + 1))
1287 .saturating_sub(state.table_area.height as usize);
1288 if skip_rows > 0 {
1290 row_heights.clear();
1291 }
1292 let nth_row = skip_rows;
1293 if data.nth(nth_row) {
1295 let mut sum_height = row_heights.iter().sum::<u16>();
1296 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1297 loop {
1298 let row_height = data.row_height();
1299 row_heights.push(row_height);
1300
1301 sum_height += row_height;
1304 if sum_height
1305 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1306 > state.table_area.height
1307 {
1308 let lost_height = row_heights.remove(0);
1309 sum_height -= lost_height;
1310 }
1311
1312 if !data.nth(0) {
1313 break;
1314 }
1315
1316 row = Some(row.expect("row") + 1);
1317 if row.expect("row") > rows {
1319 break;
1320 }
1321 }
1322 while data.nth(0) {
1325 row = Some(row.expect("row") + 1);
1326 }
1327 } else {
1328 }
1331
1332 state.rows = rows;
1333 state._counted_rows = row.map_or(0, |v| v + 1);
1334
1335 if let Some(last_page) = state.calc_last_page(row_heights) {
1337 state.vscroll.set_max_offset(state.rows - last_page);
1338 } else {
1339 state.vscroll.set_max_offset(
1343 state.rows.saturating_sub(state.table_area.height as usize),
1344 );
1345 }
1346 } else if self.no_row_count {
1347 algorithm = 1;
1348
1349 if row.is_some() {
1353 if data.nth(0) {
1354 row = Some(row.expect("row").saturating_add(1));
1356 if data.nth(0) {
1357 row = Some(usize::MAX - 1);
1359 }
1360 }
1361 }
1362
1363 state.rows = row.map_or(0, |v| v + 1);
1364 state._counted_rows = row.map_or(0, |v| v + 1);
1365 state.vscroll.set_max_offset(usize::MAX - 1);
1367 if state.vscroll.page_len() == 0 {
1368 state.vscroll.set_page_len(state.table_area.height as usize);
1369 }
1370 } else {
1371 algorithm = 2;
1372
1373 let mut sum_height = row_heights.iter().sum::<u16>();
1375 while data.nth(0) {
1376 let row_height = data.row_height();
1377 row_heights.push(row_height);
1378
1379 sum_height += row_height;
1382 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1383 > state.table_area.height
1384 {
1385 let lost_height = row_heights.remove(0);
1386 sum_height -= lost_height;
1387 }
1388 row = Some(row.map_or(0, |v| v + 1));
1389 }
1390
1391 state.rows = row.map_or(0, |v| v + 1);
1392 state._counted_rows = row.map_or(0, |v| v + 1);
1393
1394 if let Some(last_page) = state.calc_last_page(row_heights) {
1396 state.vscroll.set_max_offset(state.rows - last_page);
1397 } else {
1398 state.vscroll.set_max_offset(0);
1399 }
1400 }
1401 }
1402 {
1403 state
1404 .hscroll
1405 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1406 }
1407
1408 if state.rows == 0 && self.show_empty {
1409 let area = Rect::new(state.inner.x, state.inner.y, 3, 1);
1410 let style = if state.is_focused() {
1411 self.focus_style.unwrap_or_default()
1412 } else {
1413 self.style
1414 };
1415 Span::from(self.empty_str.as_ref())
1416 .style(style)
1417 .render(area, buf);
1418 }
1419
1420 ScrollArea::new()
1422 .style(self.style)
1423 .block(self.block.as_ref())
1424 .h_scroll(self.hscroll.as_ref())
1425 .v_scroll(self.vscroll.as_ref())
1426 .render_scrollbars(
1427 area,
1428 buf,
1429 &mut ScrollAreaState::new()
1430 .h_scroll(&mut state.hscroll)
1431 .v_scroll(&mut state.vscroll),
1432 );
1433
1434 #[cfg(feature = "perf_warnings")]
1435 {
1436 use std::fmt::Write;
1437 let mut msg = String::new();
1438 if insane_offset {
1439 _ = write!(
1440 msg,
1441 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1442 state.vscroll.offset(),
1443 state.rows,
1444 state._counted_rows,
1445 algorithm
1446 );
1447 }
1448 if state.rows != state._counted_rows {
1449 _ = write!(
1450 msg,
1451 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1452 state.rows, state._counted_rows, algorithm
1453 );
1454 }
1455 if !msg.is_empty() {
1456 use log::warn;
1457 use ratatui_core::style::Stylize;
1458 use ratatui_core::text::Text;
1459
1460 warn!("{}", &msg);
1461 Text::from(msg)
1462 .white()
1463 .on_red()
1464 .render(state.table_area, buf);
1465 }
1466 }
1467 }
1468
1469 #[allow(clippy::too_many_arguments)]
1470 fn render_footer(
1471 &self,
1472 columns: usize,
1473 width: u16,
1474 l_columns: &[Rect],
1475 l_spacers: &[Rect],
1476 area: Rect,
1477 buf: &mut Buffer,
1478 state: &mut TableState<Selection>,
1479 ) {
1480 if let Some(footer) = &self.footer {
1481 let render_row_area = Rect::new(0, 0, width, footer.height);
1482 let mut row_buf = Buffer::empty(render_row_area);
1483
1484 row_buf.set_style(render_row_area, self.style);
1485 if let Some(footer_style) = footer.style {
1486 row_buf.set_style(render_row_area, footer_style);
1487 } else if let Some(footer_style) = self.footer_style {
1488 row_buf.set_style(render_row_area, footer_style);
1489 }
1490
1491 let mut col = 0;
1492 loop {
1493 if col >= columns {
1494 break;
1495 }
1496
1497 let render_cell_area =
1498 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1499 let render_space_area = Rect::new(
1500 l_spacers[col + 1].x,
1501 0,
1502 l_spacers[col + 1].width,
1503 area.height,
1504 );
1505
1506 if state.selection.is_selected_column(col) {
1507 if let Some(selected_style) = self.patch_select(
1508 self.select_footer_style,
1509 state.focus.get(),
1510 self.show_footer_focus,
1511 ) {
1512 row_buf.set_style(render_cell_area, selected_style);
1513 row_buf.set_style(render_space_area, selected_style);
1514 }
1515 };
1516
1517 if render_cell_area.right() > state.hscroll.offset as u16
1519 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1520 {
1521 if let Some(cell) = footer.cells.get(col) {
1522 if let Some(cell_style) = cell.style {
1523 row_buf.set_style(render_cell_area, cell_style);
1524 }
1525 cell.content.clone().render(render_cell_area, &mut row_buf);
1526 }
1527 }
1528
1529 col += 1;
1530 }
1531
1532 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1534 }
1535 }
1536
1537 #[allow(clippy::too_many_arguments)]
1538 fn render_header(
1539 &self,
1540 columns: usize,
1541 width: u16,
1542 l_columns: &[Rect],
1543 l_spacers: &[Rect],
1544 area: Rect,
1545 buf: &mut Buffer,
1546 state: &mut TableState<Selection>,
1547 ) {
1548 if let Some(header) = &self.header {
1549 let render_row_area = Rect::new(0, 0, width, header.height);
1550 let mut row_buf = Buffer::empty(render_row_area);
1551
1552 row_buf.set_style(render_row_area, self.style);
1553 if let Some(header_style) = header.style {
1554 row_buf.set_style(render_row_area, header_style);
1555 } else if let Some(header_style) = self.header_style {
1556 row_buf.set_style(render_row_area, header_style);
1557 }
1558
1559 let mut col = 0;
1560 loop {
1561 if col >= columns {
1562 break;
1563 }
1564
1565 let render_cell_area =
1566 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1567 let render_space_area = Rect::new(
1568 l_spacers[col + 1].x,
1569 0,
1570 l_spacers[col + 1].width,
1571 area.height,
1572 );
1573
1574 if state.selection.is_selected_column(col) {
1575 if let Some(selected_style) = self.patch_select(
1576 self.select_header_style,
1577 state.focus.get(),
1578 self.show_header_focus,
1579 ) {
1580 row_buf.set_style(render_cell_area, selected_style);
1581 row_buf.set_style(render_space_area, selected_style);
1582 }
1583 };
1584
1585 if render_cell_area.right() > state.hscroll.offset as u16
1587 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1588 {
1589 if let Some(cell) = header.cells.get(col) {
1590 if let Some(cell_style) = cell.style {
1591 row_buf.set_style(render_cell_area, cell_style);
1592 }
1593 cell.content.clone().render(render_cell_area, &mut row_buf);
1594 }
1595 }
1596
1597 col += 1;
1598 }
1599
1600 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1602 }
1603 }
1604
1605 fn calculate_column_areas(
1606 &self,
1607 columns: usize,
1608 l_columns: &[Rect],
1609 l_spacers: &[Rect],
1610 state: &mut TableState<Selection>,
1611 ) {
1612 state.column_areas.clear();
1613 state.column_layout.clear();
1614
1615 let mut col = 0;
1616 let shift = state.hscroll.offset() as isize;
1617 loop {
1618 if col >= columns {
1619 break;
1620 }
1621
1622 state.column_layout.push(Rect::new(
1623 l_columns[col].x,
1624 0,
1625 l_columns[col].width + l_spacers[col + 1].width,
1626 0,
1627 ));
1628
1629 let cell_x1 = l_columns[col].x as isize;
1630 let cell_x2 =
1631 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1632
1633 let squish_x1 = cell_x1.saturating_sub(shift);
1634 let squish_x2 = cell_x2.saturating_sub(shift);
1635
1636 let abs_x1 = max(0, squish_x1) as u16;
1637 let abs_x2 = max(0, squish_x2) as u16;
1638
1639 let v_area = Rect::new(
1640 state.table_area.x + abs_x1,
1641 state.table_area.y,
1642 abs_x2 - abs_x1,
1643 state.table_area.height,
1644 );
1645 state
1646 .column_areas
1647 .push(v_area.intersection(state.table_area));
1648
1649 col += 1;
1650 }
1651 }
1652
1653 #[expect(clippy::collapsible_else_if)]
1654 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1655 if let Some(style) = style {
1656 if let Some(focus_style) = self.focus_style {
1657 if focus && show {
1658 Some(style.patch(focus_style))
1659 } else {
1660 Some(fallback_select_style(style))
1661 }
1662 } else {
1663 if focus && show {
1664 Some(revert_style(style))
1665 } else {
1666 Some(fallback_select_style(style))
1667 }
1668 }
1669 } else {
1670 None
1671 }
1672 }
1673}
1674
1675impl Default for TableStyle {
1676 fn default() -> Self {
1677 Self {
1678 style: Default::default(),
1679 header: Default::default(),
1680 footer: Default::default(),
1681 select_row: Default::default(),
1682 select_column: Default::default(),
1683 select_cell: Default::default(),
1684 select_header: Default::default(),
1685 select_footer: Default::default(),
1686 show_row_focus: true,
1687 show_column_focus: Default::default(),
1688 show_cell_focus: Default::default(),
1689 show_header_focus: Default::default(),
1690 show_footer_focus: Default::default(),
1691 show_empty: Default::default(),
1692 empty_str: Default::default(),
1693 focus_style: Default::default(),
1694 block: Default::default(),
1695 border_style: Default::default(),
1696 title_style: Default::default(),
1697 scroll: Default::default(),
1698 non_exhaustive: NonExhaustive,
1699 }
1700 }
1701}
1702
1703impl<Selection: Clone> Clone for TableState<Selection> {
1704 fn clone(&self) -> Self {
1705 Self {
1706 focus: self.focus.new_instance(),
1707 area: self.area,
1708 inner: self.inner,
1709 header_area: self.header_area,
1710 table_area: self.table_area,
1711 row_areas: self.row_areas.clone(),
1712 column_areas: self.column_areas.clone(),
1713 column_layout: self.column_layout.clone(),
1714 footer_area: self.footer_area,
1715 rows: self.rows,
1716 _counted_rows: self._counted_rows,
1717 columns: self.columns,
1718 vscroll: self.vscroll.clone(),
1719 hscroll: self.hscroll.clone(),
1720 selection: self.selection.clone(),
1721 mouse: Default::default(),
1722 non_exhaustive: NonExhaustive,
1723 }
1724 }
1725}
1726
1727impl<Selection: Default> Default for TableState<Selection> {
1728 fn default() -> Self {
1729 Self {
1730 focus: Default::default(),
1731 area: Default::default(),
1732 inner: Default::default(),
1733 header_area: Default::default(),
1734 table_area: Default::default(),
1735 row_areas: Default::default(),
1736 column_areas: Default::default(),
1737 column_layout: Default::default(),
1738 footer_area: Default::default(),
1739 rows: Default::default(),
1740 _counted_rows: Default::default(),
1741 columns: Default::default(),
1742 vscroll: Default::default(),
1743 hscroll: Default::default(),
1744 selection: Default::default(),
1745 mouse: Default::default(),
1746 non_exhaustive: NonExhaustive,
1747 }
1748 }
1749}
1750
1751impl<Selection> HasFocus for TableState<Selection> {
1752 fn build(&self, builder: &mut FocusBuilder) {
1753 builder.leaf_widget(self);
1754 }
1755
1756 #[inline]
1757 fn focus(&self) -> FocusFlag {
1758 self.focus.clone()
1759 }
1760
1761 #[inline]
1762 fn area(&self) -> Rect {
1763 self.area
1764 }
1765}
1766
1767impl<Selection> HasScreenCursor for TableState<Selection> {
1768 fn screen_cursor(&self) -> Option<(u16, u16)> {
1769 None
1770 }
1771}
1772
1773impl<Selection> RelocatableState for TableState<Selection> {
1774 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1775 self.area.relocate(shift, clip);
1776 self.inner.relocate(shift, clip);
1777 self.header_area.relocate(shift, clip);
1778 self.table_area.relocate(shift, clip);
1779 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1780 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1781 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1782 self.footer_area.relocate(shift, clip);
1783 self.hscroll.relocate(shift, clip);
1784 self.vscroll.relocate(shift, clip);
1785 }
1786}
1787
1788impl<Selection> TableState<Selection> {
1789 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1790 let mut sum_heights = 0;
1791 let mut n_rows = 0;
1792 while let Some(h) = row_heights.pop() {
1793 sum_heights += h;
1794 n_rows += 1;
1795 if sum_heights >= self.table_area.height {
1796 break;
1797 }
1798 }
1799
1800 if sum_heights < self.table_area.height {
1801 None
1802 } else {
1803 Some(n_rows)
1804 }
1805 }
1806}
1807
1808impl<Selection> TableState<Selection>
1810where
1811 Selection: Default,
1812{
1813 pub fn new() -> Self {
1814 Self::default()
1815 }
1816
1817 pub fn named(name: &str) -> Self {
1818 Self {
1819 focus: FocusFlag::new().with_name(name),
1820 ..TableState::default()
1821 }
1822 }
1823}
1824
1825impl<Selection> TableState<Selection> {
1827 #[inline]
1829 pub fn rows(&self) -> usize {
1830 self.rows
1831 }
1832
1833 pub fn rows_changed(&mut self, rows: usize) {
1845 self.rows = rows;
1846 self.vscroll
1847 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize))
1848 }
1849
1850 #[inline]
1852 pub fn columns(&self) -> usize {
1853 self.columns
1854 }
1855}
1856
1857impl<Selection> TableState<Selection> {
1859 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1865 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1866 return None;
1867 }
1868
1869 let mut areas = Vec::new();
1870
1871 let r = self.row_areas[row - self.vscroll.offset()];
1872 for c in &self.column_areas {
1873 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1874 }
1875
1876 Some((r, areas))
1877 }
1878
1879 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1881 let col = self.column_at_clicked(pos);
1882 let row = self.row_at_clicked(pos);
1883
1884 match (col, row) {
1885 (Some(col), Some(row)) => Some((col, row)),
1886 _ => None,
1887 }
1888 }
1889
1890 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1892 self.mouse.column_at(&self.column_areas, pos.0)
1893 }
1894
1895 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1897 self.mouse
1898 .row_at(&self.row_areas, pos.1)
1899 .map(|v| self.vscroll.offset() + v)
1900 }
1901
1902 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1905 let col = self.column_at_drag(pos);
1906 let row = self.row_at_drag(pos);
1907
1908 (col, row)
1909 }
1910
1911 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1918 match self
1919 .mouse
1920 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1921 {
1922 Ok(v) => self.vscroll.offset() + v,
1923 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1924 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1925 }
1926 }
1927
1928 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1932 match self
1933 .mouse
1934 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1935 {
1936 Ok(v) => v,
1937 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1938 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1939 }
1940 }
1941}
1942
1943impl<Selection: TableSelection> TableState<Selection> {
1945 pub fn clear_offset(&mut self) {
1947 self.vscroll.set_offset(0);
1948 self.hscroll.set_offset(0);
1949 }
1950
1951 pub fn row_max_offset(&self) -> usize {
1956 self.vscroll.max_offset()
1957 }
1958
1959 pub fn row_offset(&self) -> usize {
1961 self.vscroll.offset()
1962 }
1963
1964 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1971 self.vscroll.set_offset(offset)
1972 }
1973
1974 pub fn page_len(&self) -> usize {
1976 self.vscroll.page_len()
1977 }
1978
1979 pub fn row_scroll_by(&self) -> usize {
1981 self.vscroll.scroll_by()
1982 }
1983
1984 pub fn x_max_offset(&self) -> usize {
1989 self.hscroll.max_offset()
1990 }
1991
1992 pub fn x_offset(&self) -> usize {
1994 self.hscroll.offset()
1995 }
1996
1997 pub fn set_x_offset(&mut self, offset: usize) -> bool {
2004 self.hscroll.set_offset(offset)
2005 }
2006
2007 pub fn page_width(&self) -> usize {
2009 self.hscroll.page_len()
2010 }
2011
2012 pub fn x_scroll_by(&self) -> usize {
2014 self.hscroll.scroll_by()
2015 }
2016
2017 pub fn scroll_to_selected(&mut self) -> bool {
2021 if let Some(selected) = self.selection.lead_selection() {
2022 let c = self.scroll_to_col(selected.0);
2023 let r = self.scroll_to_row(selected.1);
2024 r || c
2025 } else {
2026 false
2027 }
2028 }
2029
2030 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
2035 if pos >= self.rows {
2036 false
2037 } else if pos == self.row_offset().saturating_add(self.page_len()) {
2038 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
2040 if heights < self.table_area.height {
2041 false
2042 } else {
2043 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2044 }
2045 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2046 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2047 } else if pos < self.row_offset() {
2048 self.set_row_offset(pos)
2049 } else {
2050 false
2051 }
2052 }
2053
2054 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2056 if let Some(col) = self.column_layout.get(pos) {
2057 if (col.left() as usize) < self.x_offset() {
2058 self.set_x_offset(col.x as usize)
2059 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2060 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2061 } else {
2062 false
2063 }
2064 } else {
2065 false
2066 }
2067 }
2068
2069 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2071 if pos >= self.x_offset().saturating_add(self.page_width()) {
2072 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2073 } else if pos < self.x_offset() {
2074 self.set_x_offset(pos)
2075 } else {
2076 false
2077 }
2078 }
2079
2080 pub fn scroll_up(&mut self, n: usize) -> bool {
2082 self.vscroll.scroll_up(n)
2083 }
2084
2085 pub fn scroll_down(&mut self, n: usize) -> bool {
2087 self.vscroll.scroll_down(n)
2088 }
2089
2090 pub fn scroll_left(&mut self, n: usize) -> bool {
2092 self.hscroll.scroll_left(n)
2093 }
2094
2095 pub fn scroll_right(&mut self, n: usize) -> bool {
2097 self.hscroll.scroll_right(n)
2098 }
2099}
2100
2101impl TableState<RowSelection> {
2102 pub fn items_added(&mut self, pos: usize, n: usize) {
2106 self.vscroll.items_added(pos, n);
2107 self.selection.items_added(pos, n);
2108 self.rows += n;
2109 }
2110
2111 pub fn items_removed(&mut self, pos: usize, n: usize) {
2115 self.vscroll.items_removed(pos, n);
2116 self.selection
2117 .items_removed(pos, n, self.rows.saturating_sub(1));
2118 self.rows -= n;
2119 }
2120
2121 #[inline]
2124 pub fn set_scroll_selection(&mut self, scroll: bool) {
2125 self.selection.set_scroll_selected(scroll);
2126 }
2127
2128 #[inline]
2130 pub fn clear_selection(&mut self) {
2131 self.selection.clear();
2132 }
2133
2134 #[inline]
2136 pub fn has_selection(&mut self) -> bool {
2137 self.selection.has_selection()
2138 }
2139
2140 #[inline]
2143 pub fn selected(&self) -> Option<usize> {
2144 self.selection.selected()
2145 }
2146
2147 #[inline]
2150 #[allow(clippy::manual_filter)]
2151 pub fn selected_checked(&self) -> Option<usize> {
2152 if let Some(selected) = self.selection.selected() {
2153 if selected < self.rows {
2154 Some(selected)
2155 } else {
2156 None
2157 }
2158 } else {
2159 None
2160 }
2161 }
2162
2163 #[inline]
2166 pub fn select(&mut self, row: Option<usize>) -> bool {
2167 self.selection.select(row)
2168 }
2169
2170 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2174 if self.vscroll.max_offset() > 0 {
2175 (self.rows * offset) / self.vscroll.max_offset()
2176 } else {
2177 0 }
2179 }
2180
2181 #[inline]
2183 pub fn move_deselect(&mut self) -> bool {
2184 let r = self.selection.select(None);
2185 let s = self.set_row_offset(0);
2186 r || s
2187 }
2188
2189 #[inline]
2192 pub fn move_to(&mut self, row: usize) -> bool {
2193 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2194 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2195 r || s
2196 }
2197
2198 #[inline]
2201 pub fn move_up(&mut self, n: usize) -> bool {
2202 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2203 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2204 r || s
2205 }
2206
2207 #[inline]
2210 pub fn move_down(&mut self, n: usize) -> bool {
2211 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2212 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2213 r || s
2214 }
2215}
2216
2217impl TableState<RowSetSelection> {
2218 #[inline]
2220 pub fn clear_selection(&mut self) {
2221 self.selection.clear();
2222 }
2223
2224 #[inline]
2226 pub fn has_selection(&mut self) -> bool {
2227 self.selection.has_selection()
2228 }
2229
2230 #[inline]
2232 pub fn selected(&self) -> HashSet<usize> {
2233 self.selection.selected()
2234 }
2235
2236 #[inline]
2243 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2244 self.selection.set_lead(row, extend)
2245 }
2246
2247 #[inline]
2249 pub fn lead(&self) -> Option<usize> {
2250 self.selection.lead()
2251 }
2252
2253 #[inline]
2255 pub fn anchor(&self) -> Option<usize> {
2256 self.selection.anchor()
2257 }
2258
2259 #[inline]
2262 pub fn retire_selection(&mut self) {
2263 self.selection.retire_selection();
2264 }
2265
2266 #[inline]
2271 pub fn add_selected(&mut self, idx: usize) {
2272 self.selection.add(idx);
2273 }
2274
2275 #[inline]
2280 pub fn remove_selected(&mut self, idx: usize) {
2281 self.selection.remove(idx);
2282 }
2283
2284 #[inline]
2287 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2288 let r = self
2289 .selection
2290 .move_to(row, self.rows.saturating_sub(1), extend);
2291 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2292 r || s
2293 }
2294
2295 #[inline]
2298 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2299 let r = self
2300 .selection
2301 .move_up(n, self.rows.saturating_sub(1), extend);
2302 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2303 r || s
2304 }
2305
2306 #[inline]
2309 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2310 let r = self
2311 .selection
2312 .move_down(n, self.rows.saturating_sub(1), extend);
2313 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2314 r || s
2315 }
2316}
2317
2318impl TableState<CellSelection> {
2319 #[inline]
2320 pub fn clear_selection(&mut self) {
2321 self.selection.clear();
2322 }
2323
2324 #[inline]
2325 pub fn has_selection(&mut self) -> bool {
2326 self.selection.has_selection()
2327 }
2328
2329 #[inline]
2331 pub fn selected(&self) -> Option<(usize, usize)> {
2332 self.selection.selected()
2333 }
2334
2335 #[inline]
2337 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2338 self.selection.select_cell(select)
2339 }
2340
2341 #[inline]
2343 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2344 if let Some(row) = row {
2345 self.selection
2346 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2347 } else {
2348 self.selection.select_row(None)
2349 }
2350 }
2351
2352 #[inline]
2354 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2355 if let Some(column) = column {
2356 self.selection
2357 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2358 } else {
2359 self.selection.select_column(None)
2360 }
2361 }
2362
2363 #[inline]
2365 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2366 let r = self.selection.move_to(
2367 select,
2368 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2369 );
2370 let s = self.scroll_to_selected();
2371 r || s
2372 }
2373
2374 #[inline]
2376 pub fn move_to_row(&mut self, row: usize) -> bool {
2377 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2378 let s = self.scroll_to_selected();
2379 r || s
2380 }
2381
2382 #[inline]
2384 pub fn move_to_col(&mut self, col: usize) -> bool {
2385 let r = self
2386 .selection
2387 .move_to_col(col, self.columns.saturating_sub(1));
2388 let s = self.scroll_to_selected();
2389 r || s
2390 }
2391
2392 #[inline]
2395 pub fn move_up(&mut self, n: usize) -> bool {
2396 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2397 let s = self.scroll_to_selected();
2398 r || s
2399 }
2400
2401 #[inline]
2404 pub fn move_down(&mut self, n: usize) -> bool {
2405 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2406 let s = self.scroll_to_selected();
2407 r || s
2408 }
2409
2410 #[inline]
2413 pub fn move_left(&mut self, n: usize) -> bool {
2414 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2415 let s = self.scroll_to_selected();
2416 r || s
2417 }
2418
2419 #[inline]
2422 pub fn move_right(&mut self, n: usize) -> bool {
2423 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2424 let s = self.scroll_to_selected();
2425 r || s
2426 }
2427}
2428
2429impl<Selection> HandleEvent<Event, DoubleClick, DoubleClickOutcome> for TableState<Selection> {
2430 fn handle(&mut self, event: &Event, _keymap: DoubleClick) -> DoubleClickOutcome {
2432 match event {
2433 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2434 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2435 DoubleClickOutcome::ClickClick(col, row)
2436 } else {
2437 DoubleClickOutcome::Continue
2438 }
2439 }
2440 _ => DoubleClickOutcome::Continue,
2441 }
2442 }
2443}
2444
2445pub fn handle_doubleclick_events<Selection: TableSelection>(
2447 state: &mut TableState<Selection>,
2448 event: &Event,
2449) -> DoubleClickOutcome {
2450 state.handle(event, DoubleClick)
2451}