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,
50 varying_heights: bool,
52
53 header: Option<Row<'a>>,
54 footer: Option<Row<'a>>,
55
56 widths: Vec<Constraint>,
57 flex: Flex,
58 column_spacing: u16,
59 layout_width: Option<u16>,
60 layout_column_widths: bool,
61
62 style: Style,
63 block: Option<Block<'a>>,
64 hscroll: Option<Scroll<'a>>,
65 vscroll: Option<Scroll<'a>>,
66 header_style: Option<Style>,
67 footer_style: Option<Style>,
68 focus_style: Option<Style>,
69
70 auto_styles: bool,
71 select_row_style: Option<Style>,
72 show_row_focus: bool,
73 select_column_style: Option<Style>,
74 show_column_focus: bool,
75 select_cell_style: Option<Style>,
76 show_cell_focus: bool,
77 select_header_style: Option<Style>,
78 show_header_focus: bool,
79 select_footer_style: Option<Style>,
80 show_footer_focus: bool,
81
82 show_empty: bool,
83 empty_str: Cow<'a, str>,
84
85 _phantom: PhantomData<Selection>,
86}
87
88mod data {
89 use crate::textdata::TextTableData;
90 use crate::{TableContext, TableData, TableDataIter};
91 #[cfg(feature = "perf_warnings")]
92 use log::warn;
93 use ratatui_core::buffer::Buffer;
94 use ratatui_core::layout::Rect;
95 use ratatui_core::style::Style;
96 use std::fmt::{Debug, Formatter};
97
98 #[derive(Default)]
99 pub(super) enum DataRepr<'a> {
100 #[default]
101 None,
102 Text(TextTableData<'a>),
103 Data(Box<dyn TableData<'a> + 'a>),
104 Iter(Box<dyn TableDataIter<'a> + 'a>),
105 }
106
107 impl<'a> DataRepr<'a> {
108 pub(super) fn into_iter(self) -> DataReprIter<'a, 'a> {
109 match self {
110 DataRepr::None => DataReprIter::None,
111 DataRepr::Text(v) => DataReprIter::IterText(v, None),
112 DataRepr::Data(v) => DataReprIter::IterData(v, None),
113 DataRepr::Iter(v) => DataReprIter::IterIter(v),
114 }
115 }
116
117 pub(super) fn iter<'b>(&'b self) -> DataReprIter<'a, 'b> {
118 match self {
119 DataRepr::None => DataReprIter::None,
120 DataRepr::Text(v) => DataReprIter::IterDataRef(v, None),
121 DataRepr::Data(v) => DataReprIter::IterDataRef(v.as_ref(), None),
122 DataRepr::Iter(v) => {
123 if let Some(v) = v.cloned() {
125 DataReprIter::IterIter(v)
126 } else {
127 DataReprIter::Invalid(None)
128 }
129 }
130 }
131 }
132 }
133
134 impl Debug for DataRepr<'_> {
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 f.debug_struct("Data").finish()
137 }
138 }
139
140 #[derive(Default)]
141 pub(super) enum DataReprIter<'a, 'b> {
142 #[default]
143 None,
144 #[allow(dead_code)]
145 Invalid(Option<usize>),
146 IterText(TextTableData<'a>, Option<usize>),
147 IterData(Box<dyn TableData<'a> + 'a>, Option<usize>),
148 #[allow(dead_code)]
149 IterDataRef(&'b dyn TableData<'a>, Option<usize>),
150 IterIter(Box<dyn TableDataIter<'a> + 'a>),
151 }
152
153 impl<'a> TableDataIter<'a> for DataReprIter<'a, '_> {
154 fn rows(&self) -> Option<usize> {
155 match self {
156 DataReprIter::None => Some(0),
157 DataReprIter::Invalid(_) => Some(1),
158 DataReprIter::IterText(v, _) => Some(v.rows.len()),
159 DataReprIter::IterData(v, _) => Some(v.rows()),
160 DataReprIter::IterDataRef(v, _) => Some(v.rows()),
161 DataReprIter::IterIter(v) => v.rows(),
162 }
163 }
164
165 fn nth(&mut self, n: usize) -> bool {
166 let incr = |row: &mut Option<usize>, rows: usize| match *row {
167 None => {
168 *row = Some(n);
169 *row < Some(rows)
170 }
171 Some(w) => {
172 *row = Some(w.saturating_add(n).saturating_add(1));
173 *row < Some(rows)
174 }
175 };
176
177 match self {
178 DataReprIter::None => false,
179 DataReprIter::Invalid(row) => incr(row, 1),
180 DataReprIter::IterText(v, row) => incr(row, v.rows.len()),
181 DataReprIter::IterData(v, row) => incr(row, v.rows()),
182 DataReprIter::IterDataRef(v, row) => incr(row, v.rows()),
183 DataReprIter::IterIter(v) => v.nth(n),
184 }
185 }
186
187 fn row_height(&self) -> u16 {
189 match self {
190 DataReprIter::None => 1,
191 DataReprIter::Invalid(_) => 1,
192 DataReprIter::IterText(v, n) => v.row_height(n.expect("row")),
193 DataReprIter::IterData(v, n) => v.row_height(n.expect("row")),
194 DataReprIter::IterDataRef(v, n) => v.row_height(n.expect("row")),
195 DataReprIter::IterIter(v) => v.row_height(),
196 }
197 }
198
199 fn row_style(&self) -> Option<Style> {
200 match self {
201 DataReprIter::None => None,
202 DataReprIter::Invalid(_) => Some(Style::new().white().on_red()),
203 DataReprIter::IterText(v, n) => v.row_style(n.expect("row")),
204 DataReprIter::IterData(v, n) => v.row_style(n.expect("row")),
205 DataReprIter::IterDataRef(v, n) => v.row_style(n.expect("row")),
206 DataReprIter::IterIter(v) => v.row_style(),
207 }
208 }
209
210 fn render_cell(&self, ctx: &TableContext, column: usize, area: Rect, buf: &mut Buffer) {
212 match self {
213 DataReprIter::None => {}
214 DataReprIter::Invalid(_) => {
215 if column == 0 {
216 #[cfg(feature = "perf_warnings")]
217 warn!(
218 "Table::render_ref - TableDataIter must implement a valid cloned() for this to work."
219 );
220
221 buf.set_string(
222 area.x,
223 area.y,
224 "TableDataIter must implement a valid cloned() for this",
225 Style::default(),
226 );
227 }
228 }
229 DataReprIter::IterText(v, n) => {
230 v.render_cell(ctx, column, n.expect("row"), area, buf)
231 }
232 DataReprIter::IterData(v, n) => {
233 v.render_cell(ctx, column, n.expect("row"), area, buf)
234 }
235 DataReprIter::IterDataRef(v, n) => {
236 v.render_cell(ctx, column, n.expect("row"), area, buf)
237 }
238 DataReprIter::IterIter(v) => v.render_cell(ctx, column, area, buf),
239 }
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct TableStyle {
247 pub style: Style,
248 pub block: Option<Block<'static>>,
249 pub border_style: Option<Style>,
250 pub title_style: Option<Style>,
251 pub scroll: Option<ScrollStyle>,
252 pub header: Option<Style>,
253 pub footer: Option<Style>,
254 pub focus_style: Option<Style>,
255
256 pub select_row: Option<Style>,
257 pub select_column: Option<Style>,
258 pub select_cell: Option<Style>,
259 pub select_header: Option<Style>,
260 pub select_footer: Option<Style>,
261
262 pub show_row_focus: bool,
263 pub show_column_focus: bool,
264 pub show_cell_focus: bool,
265 pub show_header_focus: bool,
266 pub show_footer_focus: bool,
267 pub show_empty: bool,
268 pub empty_str: Option<Cow<'static, str>>,
269
270 pub non_exhaustive: NonExhaustive,
271}
272
273#[derive(Debug, Clone)]
275pub enum RowHeights {
276 Uniform(u8),
277 Varying(Vec<u8>),
278}
279
280impl Default for RowHeights {
281 fn default() -> Self {
282 RowHeights::Uniform(1)
283 }
284}
285
286impl RowHeights {
287 pub(crate) fn clear(&mut self) {
288 match self {
289 RowHeights::Uniform(_) => {}
290 RowHeights::Varying(h) => h.clear(),
291 }
292 }
293
294 pub(crate) fn push(&mut self, row_height: u8, varying: bool) {
295 if varying {
296 match self {
297 RowHeights::Uniform(_) => {
298 *self = RowHeights::Varying(vec![row_height]);
299 }
300 RowHeights::Varying(h) => h.push(row_height),
301 }
302 } else {
303 *self = RowHeights::Uniform(row_height);
304 }
305 }
306
307 pub(crate) fn items_added(&mut self, pos: usize, n: usize) {
308 match self {
309 RowHeights::Uniform(_) => {}
310 RowHeights::Varying(h) => {
311 if pos <= h.len() {
312 for _ in 0..n {
313 h.insert(pos, 1);
314 }
315 }
316 }
317 }
318 }
319
320 pub(crate) fn items_added_with_heights(&mut self, pos: usize, height: &[u8]) {
321 match self {
322 RowHeights::Uniform(_) => {}
323 RowHeights::Varying(h) => {
324 if pos <= h.len() {
325 h.splice(pos..pos, height.into_iter().copied());
326 }
327 }
328 }
329 }
330
331 pub(crate) fn items_removed(&mut self, pos: usize, n: usize) {
332 match self {
333 RowHeights::Uniform(_) => {}
334 RowHeights::Varying(h) => {
335 if pos < h.len() {
336 if pos + n < h.len() {
337 h.drain(pos..pos + n);
338 } else {
339 h.drain(pos..);
340 }
341 }
342 }
343 }
344 }
345}
346
347#[derive(Debug)]
349pub struct TableState<Selection = RowSelection> {
350 pub focus: FocusFlag,
353
354 pub area: Rect,
357 pub inner: Rect,
360
361 pub header_area: Rect,
364 pub table_area: Rect,
367 pub row_areas: Vec<Rect>,
370 pub row_heights: RowHeights,
372 pub column_areas: Vec<Rect>,
376 pub column_layout: Vec<Rect>,
380 pub footer_area: Rect,
383
384 pub rows: usize,
387 pub _counted_rows: usize,
389 pub columns: usize,
392
393 pub vscroll: ScrollState,
396 pub hscroll: ScrollState,
399
400 pub selection: Selection,
403
404 pub mouse: MouseFlags,
406
407 pub non_exhaustive: NonExhaustive,
408}
409
410impl<Selection> Default for Table<'_, Selection> {
411 fn default() -> Self {
412 Self {
413 data: Default::default(),
414 no_row_count: Default::default(),
415 varying_heights: Default::default(),
416 header: Default::default(),
417 footer: Default::default(),
418 widths: Default::default(),
419 flex: Default::default(),
420 column_spacing: Default::default(),
421 layout_width: Default::default(),
422 layout_column_widths: true,
423 block: Default::default(),
424 hscroll: Default::default(),
425 vscroll: Default::default(),
426 header_style: Default::default(),
427 footer_style: Default::default(),
428 style: Default::default(),
429 auto_styles: true,
430 select_row_style: Default::default(),
431 show_row_focus: true,
432 select_column_style: Default::default(),
433 show_column_focus: Default::default(),
434 select_cell_style: Default::default(),
435 show_cell_focus: Default::default(),
436 select_header_style: Default::default(),
437 show_header_focus: Default::default(),
438 select_footer_style: Default::default(),
439 show_footer_focus: Default::default(),
440 focus_style: Default::default(),
441 _phantom: Default::default(),
442 show_empty: Default::default(),
443 empty_str: Cow::Borrowed(" \u{2205} "),
444 }
445 }
446}
447
448impl<'a, Selection> Table<'a, Selection> {
449 pub fn new() -> Self
451 where
452 Selection: Default,
453 {
454 Self::default()
455 }
456
457 pub fn new_ratatui<R, C>(rows: R, widths: C) -> Self
462 where
463 R: IntoIterator,
464 R::Item: Into<Row<'a>>,
465 C: IntoIterator,
466 C::Item: Into<Constraint>,
467 Selection: Default,
468 {
469 let widths = widths.into_iter().map(|v| v.into()).collect::<Vec<_>>();
470 let data = TextTableData {
471 rows: rows.into_iter().map(|v| v.into()).collect(),
472 };
473 Self {
474 data: DataRepr::Text(data),
475 widths,
476 ..Default::default()
477 }
478 }
479
480 pub fn rows<T>(mut self, rows: T) -> Self
484 where
485 T: IntoIterator<Item = Row<'a>>,
486 {
487 let rows = rows.into_iter().collect();
488 self.data = DataRepr::Text(TextTableData { rows });
489 self
490 }
491
492 #[inline]
559 pub fn data(mut self, data: impl TableData<'a> + 'a) -> Self {
560 self.widths = data.widths();
561 self.header = data.header();
562 self.footer = data.footer();
563 self.data = DataRepr::Data(Box::new(data));
564 self
565 }
566
567 #[inline]
676 pub fn iter(mut self, data: impl TableDataIter<'a> + 'a) -> Self {
677 #[cfg(feature = "perf_warnings")]
678 if data.rows().is_none() {
679 use log::warn;
680 warn!("Table::iter - rows is None, this will be slower");
681 }
682 self.header = data.header();
683 self.footer = data.footer();
684 self.widths = data.widths();
685 self.data = DataRepr::Iter(Box::new(data));
686 self
687 }
688
689 pub fn no_row_count(mut self, no_row_count: bool) -> Self {
707 self.no_row_count = no_row_count;
708 self
709 }
710
711 pub fn varying_heights(mut self, varying: bool) -> Self {
717 self.varying_heights = varying;
718 self
719 }
720
721 #[inline]
723 pub fn header(mut self, header: Row<'a>) -> Self {
724 self.header = Some(header);
725 self
726 }
727
728 #[inline]
730 pub fn footer(mut self, footer: Row<'a>) -> Self {
731 self.footer = Some(footer);
732 self
733 }
734
735 pub fn widths<I>(mut self, widths: I) -> Self
737 where
738 I: IntoIterator,
739 I::Item: Into<Constraint>,
740 {
741 self.widths = widths.into_iter().map(|v| v.into()).collect();
742 self
743 }
744
745 #[inline]
747 pub fn flex(mut self, flex: Flex) -> Self {
748 self.flex = flex;
749 self
750 }
751
752 #[inline]
754 pub fn column_spacing(mut self, spacing: u16) -> Self {
755 self.column_spacing = spacing;
756 self
757 }
758
759 #[inline]
762 pub fn layout_width(mut self, width: u16) -> Self {
763 self.layout_width = Some(width);
764 self
765 }
766
767 #[inline]
776 pub fn layout_column_widths(mut self) -> Self {
777 self.layout_column_widths = true;
778 self
779 }
780
781 #[deprecated(since = "1.1.1", note = "no longer supported")]
788 #[inline]
789 pub fn auto_layout_width(self) -> Self {
790 self
791 }
792
793 #[inline]
795 pub fn block(mut self, block: Block<'a>) -> Self {
796 self.block = Some(block);
797 self.block = self.block.map(|v| v.style(self.style));
798 self
799 }
800
801 pub fn border_style(mut self, style: Style) -> Self {
803 self.block = self.block.map(|v| v.border_style(style));
804 self
805 }
806
807 pub fn title_style(mut self, style: Style) -> Self {
809 self.block = self.block.map(|v| v.title_style(style));
810 self
811 }
812
813 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
815 self.hscroll = Some(scroll.clone().override_horizontal());
816 self.vscroll = Some(scroll.override_vertical());
817 self
818 }
819
820 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
822 self.hscroll = Some(scroll.override_horizontal());
823 self
824 }
825
826 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
828 self.vscroll = Some(scroll.override_vertical());
829 self
830 }
831
832 #[inline]
834 pub fn styles(mut self, styles: TableStyle) -> Self {
835 self.style = styles.style;
836 if styles.block.is_some() {
837 self.block = styles.block;
838 }
839 if let Some(border_style) = styles.border_style {
840 self.block = self.block.map(|v| v.border_style(border_style));
841 }
842 if let Some(title_style) = styles.title_style {
843 self.block = self.block.map(|v| v.title_style(title_style));
844 }
845 self.block = self.block.map(|v| v.style(self.style));
846
847 if let Some(styles) = styles.scroll {
848 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
849 self.vscroll = self.vscroll.map(|v| v.styles(styles));
850 }
851 if styles.header.is_some() {
852 self.header_style = styles.header;
853 }
854 if styles.footer.is_some() {
855 self.footer_style = styles.footer;
856 }
857 if styles.select_row.is_some() {
858 self.select_row_style = styles.select_row;
859 }
860 self.show_row_focus = styles.show_row_focus;
861 if styles.select_column.is_some() {
862 self.select_column_style = styles.select_column;
863 }
864 self.show_column_focus = styles.show_column_focus;
865 if styles.select_cell.is_some() {
866 self.select_cell_style = styles.select_cell;
867 }
868 self.show_cell_focus = styles.show_cell_focus;
869 if styles.select_header.is_some() {
870 self.select_header_style = styles.select_header;
871 }
872 self.show_header_focus = styles.show_header_focus;
873 if styles.select_footer.is_some() {
874 self.select_footer_style = styles.select_footer;
875 }
876 self.show_footer_focus = styles.show_footer_focus;
877 if styles.focus_style.is_some() {
878 self.focus_style = styles.focus_style;
879 }
880 self.show_empty = styles.show_empty;
881 if let Some(empty_str) = styles.empty_str {
882 self.empty_str = empty_str;
883 }
884 self
885 }
886
887 #[inline]
889 pub fn style(mut self, style: Style) -> Self {
890 self.style = style;
891 self.block = self.block.map(|v| v.style(self.style));
892 self
893 }
894
895 #[inline]
897 pub fn header_style(mut self, style: Option<Style>) -> Self {
898 self.header_style = style;
899 self
900 }
901
902 #[inline]
904 pub fn footer_style(mut self, style: Option<Style>) -> Self {
905 self.footer_style = style;
906 self
907 }
908
909 #[inline]
915 pub fn auto_styles(mut self, auto_styles: bool) -> Self {
916 self.auto_styles = auto_styles;
917 self
918 }
919
920 #[inline]
923 pub fn select_row_style(mut self, select_style: Option<Style>) -> Self {
924 self.select_row_style = select_style;
925 self
926 }
927
928 #[inline]
930 pub fn show_row_focus(mut self, show: bool) -> Self {
931 self.show_row_focus = show;
932 self
933 }
934
935 #[inline]
938 pub fn select_column_style(mut self, select_style: Option<Style>) -> Self {
939 self.select_column_style = select_style;
940 self
941 }
942
943 #[inline]
945 pub fn show_column_focus(mut self, show: bool) -> Self {
946 self.show_column_focus = show;
947 self
948 }
949
950 #[inline]
953 pub fn select_cell_style(mut self, select_style: Option<Style>) -> Self {
954 self.select_cell_style = select_style;
955 self
956 }
957
958 #[inline]
960 pub fn show_cell_focus(mut self, show: bool) -> Self {
961 self.show_cell_focus = show;
962 self
963 }
964
965 #[inline]
968 pub fn select_header_style(mut self, select_style: Option<Style>) -> Self {
969 self.select_header_style = select_style;
970 self
971 }
972
973 #[inline]
975 pub fn show_header_focus(mut self, show: bool) -> Self {
976 self.show_header_focus = show;
977 self
978 }
979
980 #[inline]
983 pub fn select_footer_style(mut self, select_style: Option<Style>) -> Self {
984 self.select_footer_style = select_style;
985 self
986 }
987
988 #[inline]
990 pub fn show_footer_focus(mut self, show: bool) -> Self {
991 self.show_footer_focus = show;
992 self
993 }
994
995 #[inline]
1001 pub fn focus_style(mut self, focus_style: Option<Style>) -> Self {
1002 self.focus_style = focus_style;
1003 self
1004 }
1005
1006 #[inline]
1008 pub fn show_empty(mut self, show: bool) -> Self {
1009 self.show_empty = show;
1010 self
1011 }
1012
1013 #[inline]
1014 pub fn show_empty_str(mut self, str: impl Into<Cow<'a, str>>) -> Self {
1015 self.empty_str = str.into();
1016 self
1017 }
1018
1019 pub fn width(&self) -> u16 {
1021 let sa = ScrollArea::new()
1022 .style(self.style)
1023 .block(self.block.as_ref())
1024 .h_scroll(self.hscroll.as_ref())
1025 .v_scroll(self.vscroll.as_ref());
1026 let padding = sa.padding();
1027 let width = self.total_width(0);
1028
1029 width + padding.left + padding.right
1030 }
1031
1032 pub fn heigth(&self) -> u16 {
1034 let sa = ScrollArea::new()
1035 .style(self.style)
1036 .block(self.block.as_ref())
1037 .h_scroll(self.hscroll.as_ref())
1038 .v_scroll(self.vscroll.as_ref());
1039 let padding = sa.padding();
1040 let header = self.header.as_ref().map(|v| v.height).unwrap_or(0);
1041 let footer = self.footer.as_ref().map(|v| v.height).unwrap_or(0);
1042
1043 1 + header + footer + padding.top + padding.bottom
1044 }
1045
1046 #[deprecated(since = "1.1.1", note = "not in use")]
1047 pub fn debug(self, _: bool) -> Self {
1048 self
1049 }
1050}
1051
1052impl<Selection> Table<'_, Selection> {
1053 #[inline]
1055 fn total_width(&self, area_width: u16) -> u16 {
1056 if let Some(layout_width) = self.layout_width {
1057 layout_width
1058 } else if self.layout_column_widths {
1059 let mut width = 0;
1060 for w in self.widths.iter().copied() {
1061 match w {
1062 Constraint::Min(v) => width += v + self.column_spacing,
1063 Constraint::Max(v) => width += v + self.column_spacing,
1064 Constraint::Length(v) => width += v + self.column_spacing,
1065 Constraint::Percentage(p) => {
1066 width += (((area_width as u32) * (p as u32)) / 100) as u16;
1067 }
1068 Constraint::Ratio(n, d) => {
1069 width += (((area_width as u32) * n) / d) as u16;
1070 }
1071 Constraint::Fill(_) => {
1072 width += 10;
1074 }
1075 }
1076 }
1077 max(width, area_width)
1078 } else {
1079 area_width
1080 }
1081 }
1082
1083 #[inline]
1085 fn layout_columns(&self, width: u16) -> (u16, Rc<[Rect]>, Rc<[Rect]>) {
1086 let width = self.total_width(width);
1087 let area = Rect::new(0, 0, width, 0);
1088
1089 let (layout, spacers) = Layout::horizontal(&self.widths)
1090 .flex(self.flex)
1091 .spacing(self.column_spacing)
1092 .split_with_spacers(area);
1093
1094 (width, layout, spacers)
1095 }
1096
1097 #[inline]
1099 fn layout_areas(&self, area: Rect) -> Rc<[Rect]> {
1100 let heights = vec![
1101 Constraint::Length(self.header.as_ref().map(|v| v.height).unwrap_or(0)),
1102 Constraint::Fill(1),
1103 Constraint::Length(self.footer.as_ref().map(|v| v.height).unwrap_or(0)),
1104 ];
1105
1106 Layout::vertical(heights).split(area)
1107 }
1108}
1109
1110impl<'a, Selection> StatefulWidget for &Table<'a, Selection>
1111where
1112 Selection: TableSelection,
1113{
1114 type State = TableState<Selection>;
1115
1116 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1117 let iter = self.data.iter();
1118 self.render_iter(iter, area, buf, state);
1119 }
1120}
1121
1122impl<Selection> StatefulWidget for Table<'_, Selection>
1123where
1124 Selection: TableSelection,
1125{
1126 type State = TableState<Selection>;
1127
1128 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
1129 let iter = mem::take(&mut self.data).into_iter();
1130 self.render_iter(iter, area, buf, state);
1131 }
1132}
1133
1134impl<'a, Selection> Table<'a, Selection>
1135where
1136 Selection: TableSelection,
1137{
1138 fn render_iter<'b>(
1143 &self,
1144 mut data: DataReprIter<'a, 'b>,
1145 area: Rect,
1146 buf: &mut Buffer,
1147 state: &mut TableState<Selection>,
1148 ) {
1149 if let Some(rows) = data.rows() {
1150 state.rows = rows;
1151 }
1152 state.columns = self.widths.len();
1153 state.area = area;
1154
1155 let sa = ScrollArea::new()
1156 .style(self.style)
1157 .block(self.block.as_ref())
1158 .h_scroll(self.hscroll.as_ref())
1159 .v_scroll(self.vscroll.as_ref());
1160 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
1161
1162 let l_rows = self.layout_areas(state.inner);
1163 state.header_area = l_rows[0];
1164 state.table_area = l_rows[1];
1165 state.footer_area = l_rows[2];
1166
1167 let (width, l_columns, l_spacers) = self.layout_columns(state.table_area.width);
1169 self.calculate_column_areas(state.columns, l_columns.as_ref(), l_spacers.as_ref(), state);
1170
1171 sa.render_block(area, buf);
1173
1174 self.render_header(
1176 state.columns,
1177 width,
1178 l_columns.as_ref(),
1179 l_spacers.as_ref(),
1180 state.header_area,
1181 buf,
1182 state,
1183 );
1184 self.render_footer(
1185 state.columns,
1186 width,
1187 l_columns.as_ref(),
1188 l_spacers.as_ref(),
1189 state.footer_area,
1190 buf,
1191 state,
1192 );
1193
1194 state.row_areas.clear();
1196 state.row_heights.clear();
1197 state.vscroll.set_page_len(0);
1198 state.hscroll.set_page_len(area.width as usize);
1199
1200 let mut row_buf = Buffer::empty(Rect::new(0, 0, width, 1));
1201 let mut row = None;
1202 let mut row_y = state.table_area.y;
1203 let mut row_heights = Vec::with_capacity(state.table_area.height as usize);
1204 #[cfg(feature = "perf_warnings")]
1205 let mut insane_offset = false;
1206
1207 let mut ctx = TableContext {
1208 focus: state.focus.get(),
1209 selected_cell: false,
1210 selected_row: false,
1211 selected_column: false,
1212 style: self.style,
1213 row_style: None,
1214 select_style: None,
1215 space_area: Default::default(),
1216 row_area: Default::default(),
1217 non_exhaustive: NonExhaustive,
1218 };
1219
1220 let skipped_to_offset = if self.varying_heights {
1221 'l: {
1222 while row.is_none() || row.expect("row") < state.vscroll.offset() {
1223 if !data.nth(0) {
1224 break 'l false;
1225 }
1226 row = match row {
1227 Some(row) => {
1228 if row + 1 < state.vscroll.offset() {
1229 state.row_heights.push(data.row_height() as u8, true);
1230 }
1231 Some(row + 1)
1232 }
1233 None => {
1234 if 0 < state.vscroll.offset() {
1235 state.row_heights.push(data.row_height() as u8, true);
1236 }
1237 Some(0)
1238 }
1239 };
1240 }
1241 true
1242 }
1243 } else {
1244 data.nth(state.vscroll.offset())
1245 };
1246 if skipped_to_offset {
1247 row = Some(state.vscroll.offset());
1248 loop {
1249 ctx.row_style = data.row_style();
1250
1251 let row_height = data.row_height();
1252
1253 let render_row_area = Rect::new(0, 0, width, row_height);
1259
1260 let visible_row_area = Rect::new(
1262 state.table_area.x,
1263 row_y,
1264 state.table_area.width,
1265 render_row_area.height,
1266 )
1267 .intersection(state.table_area);
1268
1269 row_heights.push(row_height);
1270 state
1271 .row_heights
1272 .push(row_height as u8, self.varying_heights);
1273 ctx.row_area = render_row_area;
1274 state.row_areas.push(visible_row_area);
1275 state.vscroll.page_len += 1;
1276
1277 if render_row_area.height > 0 {
1278 row_buf.resize(render_row_area);
1279
1280 if self.auto_styles {
1281 if let Some(row_style) = ctx.row_style {
1282 row_buf.set_style(render_row_area, row_style);
1283 } else {
1284 row_buf.set_style(render_row_area, self.style);
1285 }
1286 }
1287
1288 let mut col = 0;
1289 loop {
1290 if col >= state.columns {
1291 break;
1292 }
1293
1294 let render_cell_area = Rect::new(
1295 l_columns[col].x,
1296 0,
1297 l_columns[col].width,
1298 render_row_area.height,
1299 );
1300
1301 if render_cell_area.right() <= state.hscroll.offset as u16
1302 || render_cell_area.left() >= state.hscroll.offset as u16 + area.width
1303 {
1304 col += 1;
1306 continue;
1307 }
1308
1309 ctx.space_area = Rect::new(
1310 l_spacers[col + 1].x,
1311 0,
1312 l_spacers[col + 1].width,
1313 render_row_area.height,
1314 );
1315
1316 if state.selection.is_selected_cell(col, row.expect("row")) {
1317 ctx.selected_cell = true;
1318 ctx.selected_row = false;
1319 ctx.selected_column = false;
1320 ctx.select_style = self.patch_select(
1321 self.select_cell_style,
1322 state.focus.get(),
1323 self.show_cell_focus,
1324 );
1325 } else if state.selection.is_selected_row(row.expect("row")) {
1326 ctx.selected_cell = false;
1327 ctx.selected_row = true;
1328 ctx.selected_column = false;
1329 ctx.select_style = if self.select_row_style.is_some() {
1331 self.patch_select(
1332 self.select_row_style,
1333 state.focus.get(),
1334 self.show_row_focus,
1335 )
1336 } else {
1337 self.patch_select(
1338 Some(self.style),
1339 state.focus.get(),
1340 self.show_row_focus,
1341 )
1342 };
1343 } else if state.selection.is_selected_column(col) {
1344 ctx.selected_cell = false;
1345 ctx.selected_row = false;
1346 ctx.selected_column = true;
1347 ctx.select_style = self.patch_select(
1348 self.select_column_style,
1349 state.focus.get(),
1350 self.show_column_focus,
1351 );
1352 } else {
1353 ctx.selected_cell = false;
1354 ctx.selected_row = false;
1355 ctx.selected_column = false;
1356 ctx.select_style = None;
1357 }
1358
1359 if self.auto_styles {
1360 if let Some(select_style) = ctx.select_style {
1361 row_buf.set_style(render_cell_area, select_style);
1362 row_buf.set_style(ctx.space_area, select_style);
1363 }
1364 }
1365 data.render_cell(&ctx, col, render_cell_area, &mut row_buf);
1366
1367 col += 1;
1368 }
1369
1370 transfer_buffer(
1372 &mut row_buf,
1373 state.hscroll.offset() as u16,
1374 visible_row_area,
1375 buf,
1376 );
1377 }
1378
1379 if visible_row_area.bottom() >= state.table_area.bottom() {
1380 break;
1381 }
1382 if !data.nth(0) {
1383 break;
1384 }
1385 row = Some(row.expect("row").saturating_add(1));
1386 row_y += render_row_area.height;
1387 }
1388 } else {
1389 if data.rows().is_none() || data.rows() == Some(0) {
1394 } else {
1396 #[cfg(feature = "perf_warnings")]
1397 {
1398 insane_offset = true;
1399 }
1400 }
1401 }
1402
1403 #[allow(unused_variables)]
1405 let algorithm;
1406 #[allow(unused_assignments)]
1407 {
1408 if !self.varying_heights && data.rows().is_some() {
1409 algorithm = 0;
1410 let rows = data.rows().expect("data-rows");
1411 let skip_rows = rows
1415 .saturating_sub(row.map_or(0, |v| v + 1))
1416 .saturating_sub(state.table_area.height as usize);
1417 if skip_rows > 0 {
1419 row_heights.clear();
1420 }
1421 let nth_row = skip_rows;
1422 if data.nth(nth_row) {
1424 let mut sum_height = row_heights.iter().sum::<u16>();
1425 row = Some(row.map_or(nth_row, |row| row + nth_row + 1));
1426 loop {
1427 let row_height = data.row_height();
1428 row_heights.push(row_height);
1429
1430 sum_height += row_height;
1433 if sum_height
1434 .saturating_sub(row_heights.first().copied().unwrap_or_default())
1435 > state.table_area.height
1436 {
1437 let lost_height = row_heights.remove(0);
1438 sum_height -= lost_height;
1439 }
1440
1441 if !data.nth(0) {
1442 break;
1443 }
1444
1445 row = Some(row.expect("row") + 1);
1446 if row.expect("row") > rows {
1448 break;
1449 }
1450 }
1451 while data.nth(0) {
1454 row = Some(row.expect("row") + 1);
1455 }
1456 } else {
1457 }
1460
1461 state.rows = rows;
1462 state._counted_rows = row.map_or(0, |v| v + 1);
1463 if let Some(last_page) = state.calc_last_page(row_heights) {
1465 state.vscroll.set_max_offset(state.rows - last_page);
1466 } else {
1467 state.vscroll.set_max_offset(
1471 state.rows.saturating_sub(state.table_area.height as usize),
1472 );
1473 }
1474 state.selection.validate_rows(state.rows);
1475 state.selection.validate_cols(state.columns);
1476 } else if !self.varying_heights && self.no_row_count {
1477 algorithm = 1;
1478
1479 if row.is_some() {
1483 if data.nth(0) {
1484 row = Some(row.expect("row").saturating_add(1));
1486 if data.nth(0) {
1487 row = Some(usize::MAX - 1);
1489 }
1490 }
1491 }
1492
1493 state.rows = row.map_or(0, |v| v + 1);
1494 state._counted_rows = row.map_or(0, |v| v + 1);
1495 state.vscroll.set_max_offset(usize::MAX - 1);
1497 if state.vscroll.page_len() == 0 {
1498 state.vscroll.set_page_len(state.table_area.height as usize);
1499 }
1500 state.selection.validate_rows(state.rows);
1501 state.selection.validate_cols(state.columns);
1502 } else {
1503 algorithm = 2;
1504
1505 let mut sum_height = row_heights.iter().sum::<u16>();
1507 while data.nth(0) {
1508 let row_height = data.row_height();
1509 row = Some(row.map_or(0, |v| v + 1));
1510 state
1511 .row_heights
1512 .push(row_height as u8, self.varying_heights);
1513 row_heights.push(row_height);
1514
1515 sum_height += row_height;
1518 if sum_height.saturating_sub(row_heights.first().copied().unwrap_or_default())
1519 > state.table_area.height
1520 {
1521 let lost_height = row_heights.remove(0);
1522 sum_height -= lost_height;
1523 }
1524 }
1525
1526 state.rows = row.map_or(0, |v| v + 1);
1527 state._counted_rows = row.map_or(0, |v| v + 1);
1528 if let Some(last_page) = state.calc_last_page(row_heights) {
1530 state.vscroll.set_max_offset(state.rows - last_page);
1531 } else {
1532 state.vscroll.set_max_offset(0);
1533 }
1534 state.selection.validate_rows(state.rows);
1535 state.selection.validate_cols(state.columns);
1536 }
1537 }
1538 {
1539 state
1540 .hscroll
1541 .set_max_offset(width.saturating_sub(state.table_area.width) as usize);
1542 }
1543
1544 if state.rows == 0 && self.show_empty && !state.inner.is_empty() {
1545 let area = Rect::new(state.table_area.x, state.table_area.y, 3, 1);
1546 let style = if state.is_focused() {
1547 self.focus_style.unwrap_or_default()
1548 } else {
1549 self.style
1550 };
1551 Span::from(self.empty_str.as_ref())
1552 .style(style)
1553 .render(area, buf);
1554 }
1555
1556 ScrollArea::new()
1558 .style(self.style)
1559 .block(self.block.as_ref())
1560 .h_scroll(self.hscroll.as_ref())
1561 .v_scroll(self.vscroll.as_ref())
1562 .render_scrollbars(
1563 area,
1564 buf,
1565 &mut ScrollAreaState::new()
1566 .h_scroll(&mut state.hscroll)
1567 .v_scroll(&mut state.vscroll),
1568 );
1569
1570 #[cfg(feature = "perf_warnings")]
1571 {
1572 use std::fmt::Write;
1573 let mut msg = String::new();
1574 if insane_offset {
1575 _ = write!(
1576 msg,
1577 "Table::render:\n offset {}\n rows {}\n iter-rows {}max\n don't match up\nCode X{}X\n",
1578 state.vscroll.offset(),
1579 state.rows,
1580 state._counted_rows,
1581 algorithm
1582 );
1583 }
1584 if state.rows != state._counted_rows {
1585 _ = write!(
1586 msg,
1587 "Table::render:\n rows {} don't match\n iterated rows {}\nCode X{}X\n",
1588 state.rows, state._counted_rows, algorithm
1589 );
1590 }
1591 if !msg.is_empty() {
1592 use log::warn;
1593 use ratatui_core::style::Stylize;
1594 use ratatui_core::text::Text;
1595
1596 warn!("{}", &msg);
1597 Text::from(msg)
1598 .white()
1599 .on_red()
1600 .render(state.table_area, buf);
1601 }
1602 }
1603 }
1604
1605 #[allow(clippy::too_many_arguments)]
1606 fn render_footer(
1607 &self,
1608 columns: usize,
1609 width: u16,
1610 l_columns: &[Rect],
1611 l_spacers: &[Rect],
1612 area: Rect,
1613 buf: &mut Buffer,
1614 state: &mut TableState<Selection>,
1615 ) {
1616 if let Some(footer) = &self.footer {
1617 let render_row_area = Rect::new(0, 0, width, footer.height);
1618 let mut row_buf = Buffer::empty(render_row_area);
1619
1620 row_buf.set_style(render_row_area, self.style);
1621 if let Some(footer_style) = footer.style {
1622 row_buf.set_style(render_row_area, footer_style);
1623 } else if let Some(footer_style) = self.footer_style {
1624 row_buf.set_style(render_row_area, footer_style);
1625 }
1626
1627 let mut col = 0;
1628 loop {
1629 if col >= columns {
1630 break;
1631 }
1632
1633 let render_cell_area =
1634 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1635 let render_space_area = Rect::new(
1636 l_spacers[col + 1].x,
1637 0,
1638 l_spacers[col + 1].width,
1639 area.height,
1640 );
1641
1642 if state.selection.is_selected_column(col) {
1643 if let Some(selected_style) = self.patch_select(
1644 self.select_footer_style,
1645 state.focus.get(),
1646 self.show_footer_focus,
1647 ) {
1648 row_buf.set_style(render_cell_area, selected_style);
1649 row_buf.set_style(render_space_area, selected_style);
1650 }
1651 };
1652
1653 if render_cell_area.right() > state.hscroll.offset as u16
1655 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1656 {
1657 if let Some(cell) = footer.cells.get(col) {
1658 if let Some(cell_style) = cell.style {
1659 row_buf.set_style(render_cell_area, cell_style);
1660 }
1661 cell.content.clone().render(render_cell_area, &mut row_buf);
1662 }
1663 }
1664
1665 col += 1;
1666 }
1667
1668 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1670 }
1671 }
1672
1673 #[allow(clippy::too_many_arguments)]
1674 fn render_header(
1675 &self,
1676 columns: usize,
1677 width: u16,
1678 l_columns: &[Rect],
1679 l_spacers: &[Rect],
1680 area: Rect,
1681 buf: &mut Buffer,
1682 state: &mut TableState<Selection>,
1683 ) {
1684 if let Some(header) = &self.header {
1685 let render_row_area = Rect::new(0, 0, width, header.height);
1686 let mut row_buf = Buffer::empty(render_row_area);
1687
1688 row_buf.set_style(render_row_area, self.style);
1689 if let Some(header_style) = header.style {
1690 row_buf.set_style(render_row_area, header_style);
1691 } else if let Some(header_style) = self.header_style {
1692 row_buf.set_style(render_row_area, header_style);
1693 }
1694
1695 let mut col = 0;
1696 loop {
1697 if col >= columns {
1698 break;
1699 }
1700
1701 let render_cell_area =
1702 Rect::new(l_columns[col].x, 0, l_columns[col].width, area.height);
1703 let render_space_area = Rect::new(
1704 l_spacers[col + 1].x,
1705 0,
1706 l_spacers[col + 1].width,
1707 area.height,
1708 );
1709
1710 if state.selection.is_selected_column(col) {
1711 if let Some(selected_style) = self.patch_select(
1712 self.select_header_style,
1713 state.focus.get(),
1714 self.show_header_focus,
1715 ) {
1716 row_buf.set_style(render_cell_area, selected_style);
1717 row_buf.set_style(render_space_area, selected_style);
1718 }
1719 };
1720
1721 if render_cell_area.right() > state.hscroll.offset as u16
1723 || render_cell_area.left() < state.hscroll.offset as u16 + area.width
1724 {
1725 if let Some(cell) = header.cells.get(col) {
1726 if let Some(cell_style) = cell.style {
1727 row_buf.set_style(render_cell_area, cell_style);
1728 }
1729 cell.content.clone().render(render_cell_area, &mut row_buf);
1730 }
1731 }
1732
1733 col += 1;
1734 }
1735
1736 transfer_buffer(&mut row_buf, state.hscroll.offset() as u16, area, buf);
1738 }
1739 }
1740
1741 fn calculate_column_areas(
1742 &self,
1743 columns: usize,
1744 l_columns: &[Rect],
1745 l_spacers: &[Rect],
1746 state: &mut TableState<Selection>,
1747 ) {
1748 state.column_areas.clear();
1749 state.column_layout.clear();
1750
1751 let mut col = 0;
1752 let shift = state.hscroll.offset() as isize;
1753 loop {
1754 if col >= columns {
1755 break;
1756 }
1757
1758 state.column_layout.push(Rect::new(
1759 l_columns[col].x,
1760 0,
1761 l_columns[col].width + l_spacers[col + 1].width,
1762 0,
1763 ));
1764
1765 let cell_x1 = l_columns[col].x as isize;
1766 let cell_x2 =
1767 (l_columns[col].x + l_columns[col].width + l_spacers[col + 1].width) as isize;
1768
1769 let squish_x1 = cell_x1.saturating_sub(shift);
1770 let squish_x2 = cell_x2.saturating_sub(shift);
1771
1772 let abs_x1 = max(0, squish_x1) as u16;
1773 let abs_x2 = max(0, squish_x2) as u16;
1774
1775 let v_area = Rect::new(
1776 state.table_area.x + abs_x1,
1777 state.table_area.y,
1778 abs_x2 - abs_x1,
1779 state.table_area.height,
1780 );
1781 state
1782 .column_areas
1783 .push(v_area.intersection(state.table_area));
1784
1785 col += 1;
1786 }
1787 }
1788
1789 #[expect(clippy::collapsible_else_if)]
1790 fn patch_select(&self, style: Option<Style>, focus: bool, show: bool) -> Option<Style> {
1791 if let Some(style) = style {
1792 if let Some(focus_style) = self.focus_style {
1793 if focus && show {
1794 Some(style.patch(focus_style))
1795 } else {
1796 Some(fallback_select_style(style))
1797 }
1798 } else {
1799 if focus && show {
1800 Some(revert_style(style))
1801 } else {
1802 Some(fallback_select_style(style))
1803 }
1804 }
1805 } else {
1806 None
1807 }
1808 }
1809}
1810
1811impl Default for TableStyle {
1812 fn default() -> Self {
1813 Self {
1814 style: Default::default(),
1815 header: Default::default(),
1816 footer: Default::default(),
1817 select_row: Default::default(),
1818 select_column: Default::default(),
1819 select_cell: Default::default(),
1820 select_header: Default::default(),
1821 select_footer: Default::default(),
1822 show_row_focus: true,
1823 show_column_focus: Default::default(),
1824 show_cell_focus: Default::default(),
1825 show_header_focus: Default::default(),
1826 show_footer_focus: Default::default(),
1827 show_empty: Default::default(),
1828 empty_str: Default::default(),
1829 focus_style: Default::default(),
1830 block: Default::default(),
1831 border_style: Default::default(),
1832 title_style: Default::default(),
1833 scroll: Default::default(),
1834 non_exhaustive: NonExhaustive,
1835 }
1836 }
1837}
1838
1839impl<Selection: Clone> Clone for TableState<Selection> {
1840 fn clone(&self) -> Self {
1841 Self {
1842 focus: self.focus.new_instance(),
1843 area: self.area,
1844 inner: self.inner,
1845 header_area: self.header_area,
1846 table_area: self.table_area,
1847 row_areas: self.row_areas.clone(),
1848 row_heights: self.row_heights.clone(),
1849 column_areas: self.column_areas.clone(),
1850 column_layout: self.column_layout.clone(),
1851 footer_area: self.footer_area,
1852 rows: self.rows,
1853 _counted_rows: self._counted_rows,
1854 columns: self.columns,
1855 vscroll: self.vscroll.clone(),
1856 hscroll: self.hscroll.clone(),
1857 selection: self.selection.clone(),
1858 mouse: Default::default(),
1859 non_exhaustive: NonExhaustive,
1860 }
1861 }
1862}
1863
1864impl<Selection: Default> Default for TableState<Selection> {
1865 fn default() -> Self {
1866 Self {
1867 focus: Default::default(),
1868 area: Default::default(),
1869 inner: Default::default(),
1870 header_area: Default::default(),
1871 table_area: Default::default(),
1872 row_areas: Default::default(),
1873 row_heights: Default::default(),
1874 column_areas: Default::default(),
1875 column_layout: Default::default(),
1876 footer_area: Default::default(),
1877 rows: Default::default(),
1878 _counted_rows: Default::default(),
1879 columns: Default::default(),
1880 vscroll: Default::default(),
1881 hscroll: Default::default(),
1882 selection: Default::default(),
1883 mouse: Default::default(),
1884 non_exhaustive: NonExhaustive,
1885 }
1886 }
1887}
1888
1889impl<Selection> HasFocus for TableState<Selection> {
1890 fn build(&self, builder: &mut FocusBuilder) {
1891 builder.leaf_widget(self);
1892 }
1893
1894 #[inline]
1895 fn focus(&self) -> FocusFlag {
1896 self.focus.clone()
1897 }
1898
1899 #[inline]
1900 fn area(&self) -> Rect {
1901 self.area
1902 }
1903}
1904
1905impl<Selection> HasScreenCursor for TableState<Selection> {
1906 fn screen_cursor(&self) -> Option<(u16, u16)> {
1907 None
1908 }
1909}
1910
1911impl<Selection> RelocatableState for TableState<Selection> {
1912 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1913 self.area.relocate(shift, clip);
1914 self.inner.relocate(shift, clip);
1915 self.header_area.relocate(shift, clip);
1916 self.table_area.relocate(shift, clip);
1917 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
1918 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
1919 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
1920 self.footer_area.relocate(shift, clip);
1921 self.hscroll.relocate(shift, clip);
1922 self.vscroll.relocate(shift, clip);
1923 }
1924}
1925
1926impl<Selection> TableState<Selection> {
1927 fn calc_last_page(&self, mut row_heights: Vec<u16>) -> Option<usize> {
1928 let mut sum_heights = 0;
1929 let mut n_rows = 0;
1930 while let Some(h) = row_heights.pop() {
1931 sum_heights += h;
1932 n_rows += 1;
1933 if sum_heights >= self.table_area.height {
1934 break;
1935 }
1936 }
1937
1938 if sum_heights < self.table_area.height {
1939 None
1940 } else {
1941 Some(n_rows)
1942 }
1943 }
1944}
1945
1946impl<Selection> TableState<Selection>
1948where
1949 Selection: Default,
1950{
1951 pub fn new() -> Self {
1952 Self::default()
1953 }
1954
1955 pub fn named(name: &str) -> Self {
1956 Self {
1957 focus: FocusFlag::new().with_name(name),
1958 ..TableState::default()
1959 }
1960 }
1961}
1962
1963impl<Selection> TableState<Selection> {
1965 #[inline]
1967 pub fn rows(&self) -> usize {
1968 self.rows
1969 }
1970
1971 #[inline]
1973 pub fn columns(&self) -> usize {
1974 self.columns
1975 }
1976}
1977
1978impl<Selection> TableState<Selection> {
1980 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
1986 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
1987 return None;
1988 }
1989
1990 let mut areas = Vec::new();
1991
1992 let r = self.row_areas[row - self.vscroll.offset()];
1993 for c in &self.column_areas {
1994 areas.push(Rect::new(c.x, r.y, c.width, r.height));
1995 }
1996
1997 Some((r, areas))
1998 }
1999
2000 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
2002 let col = self.column_at_clicked(pos);
2003 let row = self.row_at_clicked(pos);
2004
2005 match (col, row) {
2006 (Some(col), Some(row)) => Some((col, row)),
2007 _ => None,
2008 }
2009 }
2010
2011 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
2013 self.mouse.column_at(&self.column_areas, pos.0)
2014 }
2015
2016 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
2018 self.mouse
2019 .row_at(&self.row_areas, pos.1)
2020 .map(|v| self.vscroll.offset() + v)
2021 }
2022
2023 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
2026 let col = self.column_at_drag(pos);
2027 let row = self.row_at_drag(pos);
2028
2029 (col, row)
2030 }
2031
2032 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
2039 match self
2040 .mouse
2041 .row_at_drag(self.table_area, &self.row_areas, pos.1)
2042 {
2043 Ok(v) => self.vscroll.offset() + v,
2044 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
2045 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
2046 }
2047 }
2048
2049 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
2053 match self
2054 .mouse
2055 .column_at_drag(self.table_area, &self.column_areas, pos.0)
2056 {
2057 Ok(v) => v,
2058 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
2059 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
2060 }
2061 }
2062}
2063
2064impl<Selection: TableSelection> TableState<Selection> {
2066 pub fn items_added(&mut self, pos: usize, n: usize) {
2076 self.rows = self.rows.saturating_add(n);
2077 self.vscroll.items_added(pos, n);
2078 self.row_heights.items_added(pos, n);
2079 self.selection.items_added(pos, n);
2080 }
2081
2082 pub fn items_added_with_heigths(&mut self, pos: usize, height: &[u8]) {
2089 self.rows = self.rows.saturating_add(height.len());
2090 self.vscroll.items_added(pos, height.len());
2091 self.row_heights.items_added_with_heights(pos, height);
2092 self.selection.items_added(pos, height.len());
2093 }
2094
2095 pub fn items_removed(&mut self, pos: usize, n: usize) {
2099 self.rows = self.rows.saturating_sub(n);
2100 self.vscroll.items_removed(pos, n);
2101 self.row_heights.items_removed(pos, n);
2102 self.selection.items_removed(pos, n, self.rows);
2103 }
2104
2105 pub fn rows_changed(&mut self, rows: usize) {
2118 self.selection.validate_rows(rows);
2119 self.vscroll.rows_changed(rows);
2120 self.row_heights.clear();
2121 self.rows = rows;
2122 }
2123
2124 pub fn clear_offset(&mut self) {
2126 self.vscroll.set_offset(0);
2127 self.hscroll.set_offset(0);
2128 }
2129
2130 pub fn row_max_offset(&self) -> usize {
2135 self.vscroll.max_offset()
2136 }
2137
2138 pub fn row_offset(&self) -> usize {
2140 self.vscroll.offset()
2141 }
2142
2143 pub fn set_row_offset(&mut self, offset: usize) -> bool {
2148 self.vscroll.set_offset(offset)
2149 }
2150
2151 pub fn page_len(&self) -> usize {
2153 self.vscroll.page_len()
2154 }
2155
2156 pub fn row_scroll_by(&self) -> usize {
2158 self.vscroll.scroll_by()
2159 }
2160
2161 pub fn x_max_offset(&self) -> usize {
2166 self.hscroll.max_offset()
2167 }
2168
2169 pub fn x_offset(&self) -> usize {
2171 self.hscroll.offset()
2172 }
2173
2174 pub fn set_x_offset(&mut self, offset: usize) -> bool {
2181 self.hscroll.set_offset(offset)
2182 }
2183
2184 pub fn page_width(&self) -> usize {
2186 self.hscroll.page_len()
2187 }
2188
2189 pub fn x_scroll_by(&self) -> usize {
2191 self.hscroll.scroll_by()
2192 }
2193
2194 pub fn scroll_to_selected(&mut self) -> bool {
2203 if let Some(selected) = self.selection.lead_selection() {
2204 let c = self.scroll_to_col(selected.0);
2205 let r = self.scroll_to_row(selected.1);
2206 r || c
2207 } else {
2208 false
2209 }
2210 }
2211
2212 pub fn scroll_to_row(&mut self, mut pos: usize) -> bool {
2216 if pos >= self.rows {
2217 false
2218 } else if self.table_area.is_empty() {
2219 self.set_row_offset(pos)
2220 } else if let RowHeights::Uniform(row_height) = &self.row_heights {
2221 let page_len = self.table_area.height.div_ceil(*row_height as u16) as usize;
2222 if pos >= self.row_offset().saturating_add(page_len - 1) {
2223 if page_len as u16 * (*row_height) as u16 > self.table_area.height {
2224 self.set_row_offset(pos.saturating_sub(page_len - 2))
2225 } else {
2226 self.set_row_offset(pos.saturating_sub(page_len - 1))
2227 }
2228 } else if pos < self.row_offset() {
2229 self.set_row_offset(pos)
2230 } else {
2231 false
2232 }
2233 } else if let RowHeights::Varying(row_heights) = &self.row_heights {
2234 if pos >= self.row_offset().saturating_add(self.page_len() - 1) {
2235 if pos >= row_heights.len() {
2236 self.set_row_offset(pos)
2237 } else {
2238 let mut sum_height = row_heights[pos];
2239 if sum_height < self.table_area.height as u8 {
2240 loop {
2241 if pos < 1 {
2242 break;
2243 }
2244 sum_height += row_heights[pos - 1];
2245 if sum_height > self.table_area.height as u8 {
2246 break;
2247 }
2248 pos -= 1;
2249 }
2250 }
2251 self.set_row_offset(pos)
2252 }
2253 } else if pos < self.row_offset() {
2254 self.set_row_offset(pos)
2255 } else {
2256 false
2257 }
2258 } else {
2259 false
2260 }
2261 }
2262
2263 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2265 if let Some(col) = self.column_layout.get(pos) {
2266 self.hscroll
2267 .scroll_to_range(col.x as usize..col.right() as usize)
2268 } else {
2269 false
2270 }
2271 }
2272
2273 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
2275 self.hscroll.scroll_to_pos(pos)
2276 }
2277
2278 pub fn scroll_up(&mut self, n: usize) -> bool {
2280 self.vscroll.scroll_up(n)
2281 }
2282
2283 pub fn scroll_down(&mut self, n: usize) -> bool {
2285 self.vscroll.scroll_down(n)
2286 }
2287
2288 pub fn scroll_left(&mut self, n: usize) -> bool {
2290 self.hscroll.scroll_left(n)
2291 }
2292
2293 pub fn scroll_right(&mut self, n: usize) -> bool {
2295 self.hscroll.scroll_right(n)
2296 }
2297}
2298
2299impl TableState<RowSelection> {
2300 #[inline]
2303 pub fn set_scroll_selection(&mut self, scroll: bool) {
2304 self.selection.set_scroll_selected(scroll);
2305 }
2306
2307 #[inline]
2309 pub fn clear_selection(&mut self) {
2310 self.selection.clear();
2311 }
2312
2313 #[inline]
2315 pub fn has_selection(&mut self) -> bool {
2316 self.selection.has_selection()
2317 }
2318
2319 #[inline]
2322 pub fn selected(&self) -> Option<usize> {
2323 self.selection.selected()
2324 }
2325
2326 #[inline]
2329 #[allow(clippy::manual_filter)]
2330 pub fn selected_checked(&self) -> Option<usize> {
2331 if let Some(selected) = self.selection.selected() {
2332 if selected < self.rows {
2333 Some(selected)
2334 } else {
2335 None
2336 }
2337 } else {
2338 None
2339 }
2340 }
2341
2342 #[inline]
2345 pub fn select(&mut self, row: Option<usize>) -> bool {
2346 self.selection.select(row)
2347 }
2348
2349 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
2353 if self.vscroll.max_offset() > 0 {
2354 (self.rows * offset) / self.vscroll.max_offset()
2355 } else {
2356 0 }
2358 }
2359
2360 #[inline]
2362 pub fn move_deselect(&mut self) -> bool {
2363 let r = self.selection.select(None);
2364 let s = self.set_row_offset(0);
2365 r || s
2366 }
2367
2368 #[inline]
2371 pub fn move_to(&mut self, row: usize) -> bool {
2372 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
2373 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2374 r || s
2375 }
2376
2377 #[inline]
2380 pub fn move_up(&mut self, n: usize) -> bool {
2381 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2382 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2383 r || s
2384 }
2385
2386 #[inline]
2389 pub fn move_down(&mut self, n: usize) -> bool {
2390 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2391 let s = self.scroll_to_row(self.selection.selected().expect("row"));
2392 r || s
2393 }
2394}
2395
2396impl TableState<RowSetSelection> {
2397 #[inline]
2399 pub fn clear_selection(&mut self) {
2400 self.selection.clear();
2401 }
2402
2403 #[inline]
2405 pub fn has_selection(&mut self) -> bool {
2406 self.selection.has_selection()
2407 }
2408
2409 #[inline]
2411 pub fn selected(&self) -> HashSet<usize> {
2412 self.selection.selected()
2413 }
2414
2415 #[inline]
2422 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
2423 self.selection.set_lead(row, extend)
2424 }
2425
2426 #[inline]
2428 pub fn lead(&self) -> Option<usize> {
2429 self.selection.lead()
2430 }
2431
2432 #[inline]
2434 pub fn anchor(&self) -> Option<usize> {
2435 self.selection.anchor()
2436 }
2437
2438 #[inline]
2441 pub fn retire_selection(&mut self) {
2442 self.selection.retire_selection();
2443 }
2444
2445 #[inline]
2450 pub fn add_selected(&mut self, idx: usize) {
2451 self.selection.add(idx);
2452 }
2453
2454 #[inline]
2459 pub fn remove_selected(&mut self, idx: usize) {
2460 self.selection.remove(idx);
2461 }
2462
2463 #[inline]
2466 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
2467 let r = self
2468 .selection
2469 .move_to(row, self.rows.saturating_sub(1), extend);
2470 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2471 r || s
2472 }
2473
2474 #[inline]
2477 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
2478 let r = self
2479 .selection
2480 .move_up(n, self.rows.saturating_sub(1), extend);
2481 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2482 r || s
2483 }
2484
2485 #[inline]
2488 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
2489 let r = self
2490 .selection
2491 .move_down(n, self.rows.saturating_sub(1), extend);
2492 let s = self.scroll_to_row(self.selection.lead().expect("row"));
2493 r || s
2494 }
2495}
2496
2497impl TableState<CellSelection> {
2498 #[inline]
2499 pub fn clear_selection(&mut self) {
2500 self.selection.clear();
2501 }
2502
2503 #[inline]
2504 pub fn has_selection(&mut self) -> bool {
2505 self.selection.has_selection()
2506 }
2507
2508 #[inline]
2510 pub fn selected(&self) -> Option<(usize, usize)> {
2511 self.selection.selected()
2512 }
2513
2514 #[inline]
2516 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
2517 self.selection.select_cell(select)
2518 }
2519
2520 #[inline]
2522 pub fn select_row(&mut self, row: Option<usize>) -> bool {
2523 if let Some(row) = row {
2524 self.selection
2525 .select_row(Some(min(row, self.rows.saturating_sub(1))))
2526 } else {
2527 self.selection.select_row(None)
2528 }
2529 }
2530
2531 #[inline]
2533 pub fn select_column(&mut self, column: Option<usize>) -> bool {
2534 if let Some(column) = column {
2535 self.selection
2536 .select_column(Some(min(column, self.columns.saturating_sub(1))))
2537 } else {
2538 self.selection.select_column(None)
2539 }
2540 }
2541
2542 #[inline]
2544 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
2545 let r = self.selection.move_to(
2546 select,
2547 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
2548 );
2549 let s = self.scroll_to_selected();
2550 r || s
2551 }
2552
2553 #[inline]
2555 pub fn move_to_row(&mut self, row: usize) -> bool {
2556 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
2557 let s = self.scroll_to_selected();
2558 r || s
2559 }
2560
2561 #[inline]
2563 pub fn move_to_col(&mut self, col: usize) -> bool {
2564 let r = self
2565 .selection
2566 .move_to_col(col, self.columns.saturating_sub(1));
2567 let s = self.scroll_to_selected();
2568 r || s
2569 }
2570
2571 #[inline]
2574 pub fn move_up(&mut self, n: usize) -> bool {
2575 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
2576 let s = self.scroll_to_selected();
2577 r || s
2578 }
2579
2580 #[inline]
2583 pub fn move_down(&mut self, n: usize) -> bool {
2584 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
2585 let s = self.scroll_to_selected();
2586 r || s
2587 }
2588
2589 #[inline]
2592 pub fn move_left(&mut self, n: usize) -> bool {
2593 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
2594 let s = self.scroll_to_selected();
2595 r || s
2596 }
2597
2598 #[inline]
2601 pub fn move_right(&mut self, n: usize) -> bool {
2602 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
2603 let s = self.scroll_to_selected();
2604 r || s
2605 }
2606}
2607
2608impl<Selection> HandleEvent<Event, DoubleClick, DoubleClickOutcome> for TableState<Selection> {
2609 fn handle(&mut self, event: &Event, _keymap: DoubleClick) -> DoubleClickOutcome {
2611 match event {
2612 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
2613 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
2614 DoubleClickOutcome::ClickClick(col, row)
2615 } else {
2616 DoubleClickOutcome::Continue
2617 }
2618 }
2619 _ => DoubleClickOutcome::Continue,
2620 }
2621 }
2622}
2623
2624pub fn handle_doubleclick_events<Selection: TableSelection>(
2626 state: &mut TableState<Selection>,
2627 event: &Event,
2628) -> DoubleClickOutcome {
2629 state.handle(event, DoubleClick)
2630}