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 if let Some(last_page) = state.calc_last_page(row_heights) {
1336 state.vscroll.set_max_offset(state.rows - last_page);
1337 } else {
1338 state.vscroll.set_max_offset(
1342 state.rows.saturating_sub(state.table_area.height as usize),
1343 );
1344 }
1345 state.selection.validate_rows(state.rows);
1346 state.selection.validate_cols(state.columns);
1347 } else if self.no_row_count {
1348 algorithm = 1;
1349
1350 if row.is_some() {
1354 if data.nth(0) {
1355 row = Some(row.expect("row").saturating_add(1));
1357 if data.nth(0) {
1358 row = Some(usize::MAX - 1);
1360 }
1361 }
1362 }
1363
1364 state.rows = row.map_or(0, |v| v + 1);
1365 state._counted_rows = row.map_or(0, |v| v + 1);
1366 state.vscroll.set_max_offset(usize::MAX - 1);
1368 if state.vscroll.page_len() == 0 {
1369 state.vscroll.set_page_len(state.table_area.height as usize);
1370 }
1371 state.selection.validate_rows(state.rows);
1372 state.selection.validate_cols(state.columns);
1373 } else {
1374 algorithm = 2;
1375
1376 let mut sum_height = row_heights.iter().sum::<u16>();
1378 while data.nth(0) {
1379 let row_height = data.row_height();
1380 row_heights.push(row_height);
1381
1382 sum_height += row_height;
1385 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1386 > state.table_area.height
1387 {
1388 let lost_height = row_heights.remove(0);
1389 sum_height -= lost_height;
1390 }
1391 row = Some(row.map_or(0, |v| v + 1));
1392 }
1393
1394 state.rows = row.map_or(0, |v| v + 1);
1395 state._counted_rows = row.map_or(0, |v| v + 1);
1396 if let Some(last_page) = state.calc_last_page(row_heights) {
1398 state.vscroll.set_max_offset(state.rows - last_page);
1399 } else {
1400 state.vscroll.set_max_offset(0);
1401 }
1402 state.selection.validate_rows(state.rows);
1403 state.selection.validate_cols(state.columns);
1404 }
1405 }
1406 {
1407 state
1408 .hscroll
1409 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1410 }
1411
1412 if state.rows == 0 && self.show_empty && !state.inner.is_empty() {
1413 let area = Rect::new(state.table_area.x, state.table_area.y, 3, 1);
1414 let style = if state.is_focused() {
1415 self.focus_style.unwrap_or_default()
1416 } else {
1417 self.style
1418 };
1419 Span::from(self.empty_str.as_ref())
1420 .style(style)
1421 .render(area, buf);
1422 }
1423
1424 ScrollArea::new()
1426 .style(self.style)
1427 .block(self.block.as_ref())
1428 .h_scroll(self.hscroll.as_ref())
1429 .v_scroll(self.vscroll.as_ref())
1430 .render_scrollbars(
1431 area,
1432 buf,
1433 &mut ScrollAreaState::new()
1434 .h_scroll(&mut state.hscroll)
1435 .v_scroll(&mut state.vscroll),
1436 );
1437
1438 #[cfg(feature = "perf_warnings")]
1439 {
1440 use std::fmt::Write;
1441 let mut msg = String::new();
1442 if insane_offset {
1443 _ = write!(
1444 msg,
1445 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1446 state.vscroll.offset(),
1447 state.rows,
1448 state._counted_rows,
1449 algorithm
1450 );
1451 }
1452 if state.rows != state._counted_rows {
1453 _ = write!(
1454 msg,
1455 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1456 state.rows, state._counted_rows, algorithm
1457 );
1458 }
1459 if !msg.is_empty() {
1460 use log::warn;
1461 use ratatui_core::style::Stylize;
1462 use ratatui_core::text::Text;
1463
1464 warn!("{}", &msg);
1465 Text::from(msg)
1466 .white()
1467 .on_red()
1468 .render(state.table_area, buf);
1469 }
1470 }
1471 }
1472
1473 #[allow(clippy::too_many_arguments)]
1474 fn render_footer(
1475 &self,
1476 columns: usize,
1477 width: u16,
1478 l_columns: &[Rect],
1479 l_spacers: &[Rect],
1480 area: Rect,
1481 buf: &mut Buffer,
1482 state: &mut TableState<Selection>,
1483 ) {
1484 if let Some(footer) = &self.footer {
1485 let render_row_area = Rect::new(0, 0, width, footer.height);
1486 let mut row_buf = Buffer::empty(render_row_area);
1487
1488 row_buf.set_style(render_row_area, self.style);
1489 if let Some(footer_style) = footer.style {
1490 row_buf.set_style(render_row_area, footer_style);
1491 } else if let Some(footer_style) = self.footer_style {
1492 row_buf.set_style(render_row_area, footer_style);
1493 }
1494
1495 let mut col = 0;
1496 loop {
1497 if col >= columns {
1498 break;
1499 }
1500
1501 let render_cell_area =
1502 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1503 let render_space_area = Rect::new(
1504 l_spacers[col + 1].x,
1505 0,
1506 l_spacers[col + 1].width,
1507 area.height,
1508 );
1509
1510 if state.selection.is_selected_column(col) {
1511 if let Some(selected_style) = self.patch_select(
1512 self.select_footer_style,
1513 state.focus.get(),
1514 self.show_footer_focus,
1515 ) {
1516 row_buf.set_style(render_cell_area, selected_style);
1517 row_buf.set_style(render_space_area, selected_style);
1518 }
1519 };
1520
1521 if render_cell_area.right() > state.hscroll.offset as u16
1523 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1524 {
1525 if let Some(cell) = footer.cells.get(col) {
1526 if let Some(cell_style) = cell.style {
1527 row_buf.set_style(render_cell_area, cell_style);
1528 }
1529 cell.content.clone().render(render_cell_area, &mut row_buf);
1530 }
1531 }
1532
1533 col += 1;
1534 }
1535
1536 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1538 }
1539 }
1540
1541 #[allow(clippy::too_many_arguments)]
1542 fn render_header(
1543 &self,
1544 columns: usize,
1545 width: u16,
1546 l_columns: &[Rect],
1547 l_spacers: &[Rect],
1548 area: Rect,
1549 buf: &mut Buffer,
1550 state: &mut TableState<Selection>,
1551 ) {
1552 if let Some(header) = &self.header {
1553 let render_row_area = Rect::new(0, 0, width, header.height);
1554 let mut row_buf = Buffer::empty(render_row_area);
1555
1556 row_buf.set_style(render_row_area, self.style);
1557 if let Some(header_style) = header.style {
1558 row_buf.set_style(render_row_area, header_style);
1559 } else if let Some(header_style) = self.header_style {
1560 row_buf.set_style(render_row_area, header_style);
1561 }
1562
1563 let mut col = 0;
1564 loop {
1565 if col >= columns {
1566 break;
1567 }
1568
1569 let render_cell_area =
1570 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1571 let render_space_area = Rect::new(
1572 l_spacers[col + 1].x,
1573 0,
1574 l_spacers[col + 1].width,
1575 area.height,
1576 );
1577
1578 if state.selection.is_selected_column(col) {
1579 if let Some(selected_style) = self.patch_select(
1580 self.select_header_style,
1581 state.focus.get(),
1582 self.show_header_focus,
1583 ) {
1584 row_buf.set_style(render_cell_area, selected_style);
1585 row_buf.set_style(render_space_area, selected_style);
1586 }
1587 };
1588
1589 if render_cell_area.right() > state.hscroll.offset as u16
1591 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1592 {
1593 if let Some(cell) = header.cells.get(col) {
1594 if let Some(cell_style) = cell.style {
1595 row_buf.set_style(render_cell_area, cell_style);
1596 }
1597 cell.content.clone().render(render_cell_area, &mut row_buf);
1598 }
1599 }
1600
1601 col += 1;
1602 }
1603
1604 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1606 }
1607 }
1608
1609 fn calculate_column_areas(
1610 &self,
1611 columns: usize,
1612 l_columns: &[Rect],
1613 l_spacers: &[Rect],
1614 state: &mut TableState<Selection>,
1615 ) {
1616 state.column_areas.clear();
1617 state.column_layout.clear();
1618
1619 let mut col = 0;
1620 let shift = state.hscroll.offset() as isize;
1621 loop {
1622 if col >= columns {
1623 break;
1624 }
1625
1626 state.column_layout.push(Rect::new(
1627 l_columns[col].x,
1628 0,
1629 l_columns[col].width + l_spacers[col + 1].width,
1630 0,
1631 ));
1632
1633 let cell_x1 = l_columns[col].x as isize;
1634 let cell_x2 =
1635 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1636
1637 let squish_x1 = cell_x1.saturating_sub(shift);
1638 let squish_x2 = cell_x2.saturating_sub(shift);
1639
1640 let abs_x1 = max(0, squish_x1) as u16;
1641 let abs_x2 = max(0, squish_x2) as u16;
1642
1643 let v_area = Rect::new(
1644 state.table_area.x + abs_x1,
1645 state.table_area.y,
1646 abs_x2 - abs_x1,
1647 state.table_area.height,
1648 );
1649 state
1650 .column_areas
1651 .push(v_area.intersection(state.table_area));
1652
1653 col += 1;
1654 }
1655 }
1656
1657 #[expect(clippy::collapsible_else_if)]
1658 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1659 if let Some(style) = style {
1660 if let Some(focus_style) = self.focus_style {
1661 if focus && show {
1662 Some(style.patch(focus_style))
1663 } else {
1664 Some(fallback_select_style(style))
1665 }
1666 } else {
1667 if focus && show {
1668 Some(revert_style(style))
1669 } else {
1670 Some(fallback_select_style(style))
1671 }
1672 }
1673 } else {
1674 None
1675 }
1676 }
1677}
1678
1679impl Default for TableStyle {
1680 fn default() -> Self {
1681 Self {
1682 style: Default::default(),
1683 header: Default::default(),
1684 footer: Default::default(),
1685 select_row: Default::default(),
1686 select_column: Default::default(),
1687 select_cell: Default::default(),
1688 select_header: Default::default(),
1689 select_footer: Default::default(),
1690 show_row_focus: true,
1691 show_column_focus: Default::default(),
1692 show_cell_focus: Default::default(),
1693 show_header_focus: Default::default(),
1694 show_footer_focus: Default::default(),
1695 show_empty: Default::default(),
1696 empty_str: Default::default(),
1697 focus_style: Default::default(),
1698 block: Default::default(),
1699 border_style: Default::default(),
1700 title_style: Default::default(),
1701 scroll: Default::default(),
1702 non_exhaustive: NonExhaustive,
1703 }
1704 }
1705}
1706
1707impl<Selection: Clone> Clone for TableState<Selection> {
1708 fn clone(&self) -> Self {
1709 Self {
1710 focus: self.focus.new_instance(),
1711 area: self.area,
1712 inner: self.inner,
1713 header_area: self.header_area,
1714 table_area: self.table_area,
1715 row_areas: self.row_areas.clone(),
1716 column_areas: self.column_areas.clone(),
1717 column_layout: self.column_layout.clone(),
1718 footer_area: self.footer_area,
1719 rows: self.rows,
1720 _counted_rows: self._counted_rows,
1721 columns: self.columns,
1722 vscroll: self.vscroll.clone(),
1723 hscroll: self.hscroll.clone(),
1724 selection: self.selection.clone(),
1725 mouse: Default::default(),
1726 non_exhaustive: NonExhaustive,
1727 }
1728 }
1729}
1730
1731impl<Selection: Default> Default for TableState<Selection> {
1732 fn default() -> Self {
1733 Self {
1734 focus: Default::default(),
1735 area: Default::default(),
1736 inner: Default::default(),
1737 header_area: Default::default(),
1738 table_area: Default::default(),
1739 row_areas: Default::default(),
1740 column_areas: Default::default(),
1741 column_layout: Default::default(),
1742 footer_area: Default::default(),
1743 rows: Default::default(),
1744 _counted_rows: Default::default(),
1745 columns: Default::default(),
1746 vscroll: Default::default(),
1747 hscroll: Default::default(),
1748 selection: Default::default(),
1749 mouse: Default::default(),
1750 non_exhaustive: NonExhaustive,
1751 }
1752 }
1753}
1754
1755impl<Selection> HasFocus for TableState<Selection> {
1756 fn build(&self, builder: &mut FocusBuilder) {
1757 builder.leaf_widget(self);
1758 }
1759
1760 #[inline]
1761 fn focus(&self) -> FocusFlag {
1762 self.focus.clone()
1763 }
1764
1765 #[inline]
1766 fn area(&self) -> Rect {
1767 self.area
1768 }
1769}
1770
1771impl<Selection> HasScreenCursor for TableState<Selection> {
1772 fn screen_cursor(&self) -> Option<(u16, u16)> {
1773 None
1774 }
1775}
1776
1777impl<Selection> RelocatableState for TableState<Selection> {
1778 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1779 self.area.relocate(shift, clip);
1780 self.inner.relocate(shift, clip);
1781 self.header_area.relocate(shift, clip);
1782 self.table_area.relocate(shift, clip);
1783 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1784 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1785 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1786 self.footer_area.relocate(shift, clip);
1787 self.hscroll.relocate(shift, clip);
1788 self.vscroll.relocate(shift, clip);
1789 }
1790}
1791
1792impl<Selection> TableState<Selection> {
1793 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1794 let mut sum_heights = 0;
1795 let mut n_rows = 0;
1796 while let Some(h) = row_heights.pop() {
1797 sum_heights += h;
1798 n_rows += 1;
1799 if sum_heights >= self.table_area.height {
1800 break;
1801 }
1802 }
1803
1804 if sum_heights < self.table_area.height {
1805 None
1806 } else {
1807 Some(n_rows)
1808 }
1809 }
1810}
1811
1812impl<Selection> TableState<Selection>
1814where
1815 Selection: Default,
1816{
1817 pub fn new() -> Self {
1818 Self::default()
1819 }
1820
1821 pub fn named(name: &str) -> Self {
1822 Self {
1823 focus: FocusFlag::new().with_name(name),
1824 ..TableState::default()
1825 }
1826 }
1827}
1828
1829impl<Selection> TableState<Selection> {
1831 #[inline]
1833 pub fn rows(&self) -> usize {
1834 self.rows
1835 }
1836
1837 #[inline]
1839 pub fn columns(&self) -> usize {
1840 self.columns
1841 }
1842}
1843
1844impl<Selection> TableState<Selection> {
1846 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1852 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1853 return None;
1854 }
1855
1856 let mut areas = Vec::new();
1857
1858 let r = self.row_areas[row - self.vscroll.offset()];
1859 for c in &self.column_areas {
1860 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1861 }
1862
1863 Some((r, areas))
1864 }
1865
1866 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
1868 let col = self.column_at_clicked(pos);
1869 let row = self.row_at_clicked(pos);
1870
1871 match (col, row) {
1872 (Some(col), Some(row)) => Some((col, row)),
1873 _ => None,
1874 }
1875 }
1876
1877 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1879 self.mouse.column_at(&self.column_areas, pos.0)
1880 }
1881
1882 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1884 self.mouse
1885 .row_at(&self.row_areas, pos.1)
1886 .map(|v| self.vscroll.offset() + v)
1887 }
1888
1889 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
1892 let col = self.column_at_drag(pos);
1893 let row = self.row_at_drag(pos);
1894
1895 (col, row)
1896 }
1897
1898 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1905 match self
1906 .mouse
1907 .row_at_drag(self.table_area, &self.row_areas, pos.1)
1908 {
1909 Ok(v) => self.vscroll.offset() + v,
1910 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
1911 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
1912 }
1913 }
1914
1915 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
1919 match self
1920 .mouse
1921 .column_at_drag(self.table_area, &self.column_areas, pos.0)
1922 {
1923 Ok(v) => v,
1924 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
1925 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
1926 }
1927 }
1928}
1929
1930impl<Selection: TableSelection> TableState<Selection> {
1932 pub fn items_added(&mut self, pos: usize, n: usize) {
1935 self.rows = self.rows.saturating_add(n);
1936 self.vscroll.items_added(pos, n);
1937 self.selection.items_added(pos, n);
1938 }
1939
1940 pub fn items_removed(&mut self, pos: usize, n: usize) {
1943 self.rows = self.rows.saturating_sub(n);
1944 self.vscroll.items_removed(pos, n);
1945 self.selection.items_removed(pos, n, self.rows);
1946 }
1947
1948 pub fn rows_changed(&mut self, rows: usize) {
1958 self.selection.validate_rows(rows);
1959 self.vscroll
1960 .set_max_offset(self.rows.saturating_sub(self.table_area.height as usize));
1961 self.vscroll.offset = self.vscroll.limited_offset(self.vscroll.offset);
1962 self.rows = rows;
1963 }
1964
1965 pub fn clear_offset(&mut self) {
1967 self.vscroll.set_offset(0);
1968 self.hscroll.set_offset(0);
1969 }
1970
1971 pub fn row_max_offset(&self) -> usize {
1976 self.vscroll.max_offset()
1977 }
1978
1979 pub fn row_offset(&self) -> usize {
1981 self.vscroll.offset()
1982 }
1983
1984 pub fn set_row_offset(&mut self, offset: usize) -> bool {
1991 self.vscroll.set_offset(offset)
1992 }
1993
1994 pub fn page_len(&self) -> usize {
1996 self.vscroll.page_len()
1997 }
1998
1999 pub fn row_scroll_by(&self) -> usize {
2001 self.vscroll.scroll_by()
2002 }
2003
2004 pub fn x_max_offset(&self) -> usize {
2009 self.hscroll.max_offset()
2010 }
2011
2012 pub fn x_offset(&self) -> usize {
2014 self.hscroll.offset()
2015 }
2016
2017 pub fn set_x_offset(&mut self, offset: usize) -> bool {
2024 self.hscroll.set_offset(offset)
2025 }
2026
2027 pub fn page_width(&self) -> usize {
2029 self.hscroll.page_len()
2030 }
2031
2032 pub fn x_scroll_by(&self) -> usize {
2034 self.hscroll.scroll_by()
2035 }
2036
2037 pub fn scroll_to_selected(&mut self) -> bool {
2041 if let Some(selected) = self.selection.lead_selection() {
2042 let c = self.scroll_to_col(selected.0);
2043 let r = self.scroll_to_row(selected.1);
2044 r || c
2045 } else {
2046 false
2047 }
2048 }
2049
2050 pub fn scroll_to_row(&mut self, pos: usize) -> bool {
2055 if pos >= self.rows {
2056 false
2057 } else if pos == self.row_offset().saturating_add(self.page_len()) {
2058 let heights = self.row_areas.iter().map(|v| v.height).sum::<u16>();
2060 if heights < self.table_area.height {
2061 false
2062 } else {
2063 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2064 }
2065 } else if pos >= self.row_offset().saturating_add(self.page_len()) {
2066 self.set_row_offset(pos.saturating_sub(self.page_len()).saturating_add(1))
2067 } else if pos < self.row_offset() {
2068 self.set_row_offset(pos)
2069 } else {
2070 false
2071 }
2072 }
2073
2074 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2076 if let Some(col) = self.column_layout.get(pos) {
2077 if (col.left() as usize) < self.x_offset() {
2078 self.set_x_offset(col.x as usize)
2079 } else if (col.right() as usize) >= self.x_offset().saturating_add(self.page_width()) {
2080 self.set_x_offset((col.right() as usize).saturating_sub(self.page_width()))
2081 } else {
2082 false
2083 }
2084 } else {
2085 false
2086 }
2087 }
2088
2089 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2091 if pos >= self.x_offset().saturating_add(self.page_width()) {
2092 self.set_x_offset(pos.saturating_sub(self.page_width()).saturating_add(1))
2093 } else if pos < self.x_offset() {
2094 self.set_x_offset(pos)
2095 } else {
2096 false
2097 }
2098 }
2099
2100 pub fn scroll_up(&mut self, n: usize) -> bool {
2102 self.vscroll.scroll_up(n)
2103 }
2104
2105 pub fn scroll_down(&mut self, n: usize) -> bool {
2107 self.vscroll.scroll_down(n)
2108 }
2109
2110 pub fn scroll_left(&mut self, n: usize) -> bool {
2112 self.hscroll.scroll_left(n)
2113 }
2114
2115 pub fn scroll_right(&mut self, n: usize) -> bool {
2117 self.hscroll.scroll_right(n)
2118 }
2119}
2120
2121impl TableState<RowSelection> {
2122 #[inline]
2125 pub fn set_scroll_selection(&mut self, scroll: bool) {
2126 self.selection.set_scroll_selected(scroll);
2127 }
2128
2129 #[inline]
2131 pub fn clear_selection(&mut self) {
2132 self.selection.clear();
2133 }
2134
2135 #[inline]
2137 pub fn has_selection(&mut self) -> bool {
2138 self.selection.has_selection()
2139 }
2140
2141 #[inline]
2144 pub fn selected(&self) -> Option<usize> {
2145 self.selection.selected()
2146 }
2147
2148 #[inline]
2151 #[allow(clippy::manual_filter)]
2152 pub fn selected_checked(&self) -> Option<usize> {
2153 if let Some(selected) = self.selection.selected() {
2154 if selected < self.rows {
2155 Some(selected)
2156 } else {
2157 None
2158 }
2159 } else {
2160 None
2161 }
2162 }
2163
2164 #[inline]
2167 pub fn select(&mut self, row: Option<usize>) -> bool {
2168 self.selection.select(row)
2169 }
2170
2171 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2175 if self.vscroll.max_offset() > 0 {
2176 (self.rows * offset) / self.vscroll.max_offset()
2177 } else {
2178 0 }
2180 }
2181
2182 #[inline]
2184 pub fn move_deselect(&mut self) -> bool {
2185 let r = self.selection.select(None);
2186 let s = self.set_row_offset(0);
2187 r || s
2188 }
2189
2190 #[inline]
2193 pub fn move_to(&mut self, row: usize) -> bool {
2194 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2195 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2196 r || s
2197 }
2198
2199 #[inline]
2202 pub fn move_up(&mut self, n: usize) -> bool {
2203 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2204 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2205 r || s
2206 }
2207
2208 #[inline]
2211 pub fn move_down(&mut self, n: usize) -> bool {
2212 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2213 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2214 r || s
2215 }
2216}
2217
2218impl TableState<RowSetSelection> {
2219 #[inline]
2221 pub fn clear_selection(&mut self) {
2222 self.selection.clear();
2223 }
2224
2225 #[inline]
2227 pub fn has_selection(&mut self) -> bool {
2228 self.selection.has_selection()
2229 }
2230
2231 #[inline]
2233 pub fn selected(&self) -> HashSet<usize> {
2234 self.selection.selected()
2235 }
2236
2237 #[inline]
2244 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2245 self.selection.set_lead(row, extend)
2246 }
2247
2248 #[inline]
2250 pub fn lead(&self) -> Option<usize> {
2251 self.selection.lead()
2252 }
2253
2254 #[inline]
2256 pub fn anchor(&self) -> Option<usize> {
2257 self.selection.anchor()
2258 }
2259
2260 #[inline]
2263 pub fn retire_selection(&mut self) {
2264 self.selection.retire_selection();
2265 }
2266
2267 #[inline]
2272 pub fn add_selected(&mut self, idx: usize) {
2273 self.selection.add(idx);
2274 }
2275
2276 #[inline]
2281 pub fn remove_selected(&mut self, idx: usize) {
2282 self.selection.remove(idx);
2283 }
2284
2285 #[inline]
2288 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2289 let r = self
2290 .selection
2291 .move_to(row, self.rows.saturating_sub(1), extend);
2292 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2293 r || s
2294 }
2295
2296 #[inline]
2299 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2300 let r = self
2301 .selection
2302 .move_up(n, self.rows.saturating_sub(1), extend);
2303 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2304 r || s
2305 }
2306
2307 #[inline]
2310 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2311 let r = self
2312 .selection
2313 .move_down(n, self.rows.saturating_sub(1), extend);
2314 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2315 r || s
2316 }
2317}
2318
2319impl TableState<CellSelection> {
2320 #[inline]
2321 pub fn clear_selection(&mut self) {
2322 self.selection.clear();
2323 }
2324
2325 #[inline]
2326 pub fn has_selection(&mut self) -> bool {
2327 self.selection.has_selection()
2328 }
2329
2330 #[inline]
2332 pub fn selected(&self) -> Option<(usize, usize)> {
2333 self.selection.selected()
2334 }
2335
2336 #[inline]
2338 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2339 self.selection.select_cell(select)
2340 }
2341
2342 #[inline]
2344 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2345 if let Some(row) = row {
2346 self.selection
2347 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2348 } else {
2349 self.selection.select_row(None)
2350 }
2351 }
2352
2353 #[inline]
2355 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2356 if let Some(column) = column {
2357 self.selection
2358 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2359 } else {
2360 self.selection.select_column(None)
2361 }
2362 }
2363
2364 #[inline]
2366 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2367 let r = self.selection.move_to(
2368 select,
2369 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2370 );
2371 let s = self.scroll_to_selected();
2372 r || s
2373 }
2374
2375 #[inline]
2377 pub fn move_to_row(&mut self, row: usize) -> bool {
2378 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2379 let s = self.scroll_to_selected();
2380 r || s
2381 }
2382
2383 #[inline]
2385 pub fn move_to_col(&mut self, col: usize) -> bool {
2386 let r = self
2387 .selection
2388 .move_to_col(col, self.columns.saturating_sub(1));
2389 let s = self.scroll_to_selected();
2390 r || s
2391 }
2392
2393 #[inline]
2396 pub fn move_up(&mut self, n: usize) -> bool {
2397 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2398 let s = self.scroll_to_selected();
2399 r || s
2400 }
2401
2402 #[inline]
2405 pub fn move_down(&mut self, n: usize) -> bool {
2406 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2407 let s = self.scroll_to_selected();
2408 r || s
2409 }
2410
2411 #[inline]
2414 pub fn move_left(&mut self, n: usize) -> bool {
2415 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2416 let s = self.scroll_to_selected();
2417 r || s
2418 }
2419
2420 #[inline]
2423 pub fn move_right(&mut self, n: usize) -> bool {
2424 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2425 let s = self.scroll_to_selected();
2426 r || s
2427 }
2428}
2429
2430impl<Selection> HandleEvent<Event, DoubleClick, DoubleClickOutcome> for TableState<Selection> {
2431 fn handle(&mut self, event: &Event, _keymap: DoubleClick) -> DoubleClickOutcome {
2433 match event {
2434 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2435 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2436 DoubleClickOutcome::ClickClick(col, row)
2437 } else {
2438 DoubleClickOutcome::Continue
2439 }
2440 }
2441 _ => DoubleClickOutcome::Continue,
2442 }
2443 }
2444}
2445
2446pub fn handle_doubleclick_events<Selection: TableSelection>(
2448 state: &mut TableState<Selection>,
2449 event: &Event,
2450) -> DoubleClickOutcome {
2451 state.handle(event, DoubleClick)
2452}