1use crate::_private::NonExhaustive;
2use crate::TableSelection;
3use crate::event::{DoubleClickOutcome, TableOutcome};
4use crate::selection::{CellSelection, NoSelection, RowSelection, RowSetSelection};
5use log::debug;
6use rat_cursor::HasScreenCursor;
7use rat_event::crossterm::event::Event;
8use rat_event::util::MouseFlags;
9use rat_event::{DoubleClick, HandleEvent, MouseOnly, Regular, ct_event};
10use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
11use rat_reloc::{RelocatableState, relocate_areas};
12use rat_scrolled::ScrollState;
13use ratatui_core::layout::Rect;
14use std::cmp::min;
15use std::collections::HashSet;
16
17#[derive(Debug)]
19pub struct TableState<Selection = RowSelection> {
20 pub focus: FocusFlag,
23
24 pub area: Rect,
27 pub inner: Rect,
30
31 pub header_area: Rect,
34 pub table_area: Rect,
37 pub row_areas: Vec<Rect>,
40 pub row_heights: RowHeights,
42 pub column_areas: Vec<Rect>,
46 pub column_layout: Vec<Rect>,
50 pub footer_area: Rect,
53
54 pub rows: usize,
57 pub columns: usize,
60
61 pub vscroll: ScrollState,
64 pub hscroll: ScrollState,
67
68 pub selection: Selection,
72
73 pub mouse: MouseFlags,
75
76 pub non_exhaustive: NonExhaustive,
77}
78
79#[derive(Debug, Clone)]
81pub enum RowHeights {
82 Uniform(u8),
83 Varying(Vec<u8>),
84}
85
86impl Default for RowHeights {
87 fn default() -> Self {
88 RowHeights::Uniform(1)
89 }
90}
91
92impl RowHeights {
93 pub(crate) fn clear(&mut self) {
94 match self {
95 RowHeights::Uniform(_) => {}
96 RowHeights::Varying(h) => h.clear(),
97 }
98 }
99
100 pub(crate) fn push(&mut self, row_height: u8, varying: bool) {
101 if varying {
102 match self {
103 RowHeights::Uniform(_) => {
104 *self = RowHeights::Varying(vec![row_height]);
105 }
106 RowHeights::Varying(h) => h.push(row_height),
107 }
108 } else {
109 *self = RowHeights::Uniform(row_height);
110 }
111 }
112
113 pub(crate) fn items_added(&mut self, pos: usize, n: usize) {
114 match self {
115 RowHeights::Uniform(_) => {}
116 RowHeights::Varying(h) => {
117 if pos <= h.len() {
118 for _ in 0..n {
119 h.insert(pos, 1);
120 }
121 }
122 }
123 }
124 }
125
126 pub(crate) fn items_added_with_heights(&mut self, pos: usize, height: &[u8]) {
127 match self {
128 RowHeights::Uniform(_) => {}
129 RowHeights::Varying(h) => {
130 if pos <= h.len() {
131 h.splice(pos..pos, height.into_iter().copied());
132 }
133 }
134 }
135 }
136
137 pub(crate) fn items_removed(&mut self, pos: usize, n: usize) {
138 match self {
139 RowHeights::Uniform(_) => {}
140 RowHeights::Varying(h) => {
141 if pos < h.len() {
142 if pos + n < h.len() {
143 h.drain(pos..pos + n);
144 } else {
145 h.drain(pos..);
146 }
147 }
148 }
149 }
150 }
151}
152
153impl<Selection: Clone> Clone for TableState<Selection> {
154 fn clone(&self) -> Self {
155 Self {
156 focus: self.focus.new_instance(),
157 area: self.area,
158 inner: self.inner,
159 header_area: self.header_area,
160 table_area: self.table_area,
161 row_areas: self.row_areas.clone(),
162 row_heights: self.row_heights.clone(),
163 column_areas: self.column_areas.clone(),
164 column_layout: self.column_layout.clone(),
165 footer_area: self.footer_area,
166 rows: self.rows,
167 columns: self.columns,
168 vscroll: self.vscroll.clone(),
169 hscroll: self.hscroll.clone(),
170 selection: self.selection.clone(),
171 mouse: Default::default(),
172 non_exhaustive: NonExhaustive,
173 }
174 }
175}
176
177impl<Selection: Default> Default for TableState<Selection> {
178 fn default() -> Self {
179 Self {
180 focus: Default::default(),
181 area: Default::default(),
182 inner: Default::default(),
183 header_area: Default::default(),
184 table_area: Default::default(),
185 row_areas: Default::default(),
186 row_heights: Default::default(),
187 column_areas: Default::default(),
188 column_layout: Default::default(),
189 footer_area: Default::default(),
190 rows: Default::default(),
191 columns: Default::default(),
192 vscroll: Default::default(),
193 hscroll: Default::default(),
194 selection: Default::default(),
195 mouse: Default::default(),
196 non_exhaustive: NonExhaustive,
197 }
198 }
199}
200
201impl<Selection> HasFocus for TableState<Selection> {
202 fn build(&self, builder: &mut FocusBuilder) {
203 builder.leaf_widget(self);
204 }
205
206 #[inline]
207 fn focus(&self) -> FocusFlag {
208 self.focus.clone()
209 }
210
211 #[inline]
212 fn area(&self) -> Rect {
213 self.area
214 }
215}
216
217impl<Selection> HasScreenCursor for TableState<Selection> {
218 fn screen_cursor(&self) -> Option<(u16, u16)> {
219 None
220 }
221}
222
223impl<Selection> RelocatableState for TableState<Selection> {
224 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
225 self.area.relocate(shift, clip);
226 self.inner.relocate(shift, clip);
227 self.header_area.relocate(shift, clip);
228 self.table_area.relocate(shift, clip);
229 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
230 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
231 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
232 self.footer_area.relocate(shift, clip);
233 self.hscroll.relocate(shift, clip);
234 self.vscroll.relocate(shift, clip);
235 }
236}
237
238impl<Selection> TableState<Selection>
239where
240 Selection: Default,
241{
242 pub fn new() -> Self {
244 Self::default()
245 }
246
247 pub fn named(name: &str) -> Self {
249 Self {
250 focus: FocusFlag::new().with_name(name),
251 ..TableState::default()
252 }
253 }
254}
255
256impl<Selection> TableState<Selection> {
258 #[inline]
260 pub fn rows(&self) -> usize {
261 self.rows
262 }
263
264 #[inline]
266 pub fn columns(&self) -> usize {
267 self.columns
268 }
269}
270
271impl<Selection> TableState<Selection> {
273 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
279 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
280 return None;
281 }
282
283 let mut areas = Vec::new();
284
285 let r = self.row_areas[row - self.vscroll.offset()];
286 for c in &self.column_areas {
287 areas.push(Rect::new(c.x, r.y, c.width, r.height));
288 }
289
290 Some((r, areas))
291 }
292
293 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
295 let col = self.column_at_clicked(pos);
296 let row = self.row_at_clicked(pos);
297
298 match (col, row) {
299 (Some(col), Some(row)) => Some((col, row)),
300 _ => None,
301 }
302 }
303
304 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
306 self.mouse.column_at(&self.column_areas, pos.0)
307 }
308
309 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
311 self.mouse
312 .row_at(&self.row_areas, pos.1)
313 .map(|v| self.vscroll.offset() + v)
314 }
315
316 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
319 let col = self.column_at_drag(pos);
320 let row = self.row_at_drag(pos);
321
322 (col, row)
323 }
324
325 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
332 match self
333 .mouse
334 .row_at_drag(self.table_area, &self.row_areas, pos.1)
335 {
336 Ok(v) => self.vscroll.offset() + v,
337 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
338 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
339 }
340 }
341
342 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
346 match self
347 .mouse
348 .column_at_drag(self.table_area, &self.column_areas, pos.0)
349 {
350 Ok(v) => v,
351 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
352 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
353 }
354 }
355}
356
357impl<Selection: TableSelection> TableState<Selection> {
359 #[inline]
361 pub fn clear_selection(&mut self) {
362 self.selection.clear();
363 }
364
365 pub fn items_added(&mut self, pos: usize, n: usize) {
372 self.rows = self.rows.saturating_add(n);
373 self.vscroll.items_added(pos, n);
374 self.row_heights.items_added(pos, n);
375 self.selection.items_added(pos, n);
376 }
377
378 pub fn items_added_with_heigths(&mut self, pos: usize, height: &[u8]) {
385 self.rows = self.rows.saturating_add(height.len());
386 self.vscroll.items_added(pos, height.len());
387 self.row_heights.items_added_with_heights(pos, height);
388 self.selection.items_added(pos, height.len());
389 }
390
391 pub fn items_removed(&mut self, pos: usize, n: usize) {
398 self.rows = self.rows.saturating_sub(n);
399 self.vscroll.items_removed(pos, n);
400 self.row_heights.items_removed(pos, n);
401 self.selection.items_removed(pos, n, self.rows);
402 }
403
404 pub fn rows_changed(&mut self, rows: usize) {
411 self.selection.validate_rows(rows);
412 self.vscroll.rows_changed(rows);
413 self.row_heights.clear();
414 self.rows = rows;
415 }
416
417 pub fn clear_offset(&mut self) {
419 self.vscroll.set_offset(0);
420 self.hscroll.set_offset(0);
421 }
422
423 pub fn row_max_offset(&self) -> usize {
428 self.vscroll.max_offset()
429 }
430
431 pub fn row_offset(&self) -> usize {
433 self.vscroll.offset()
434 }
435
436 pub fn set_row_offset(&mut self, offset: usize) -> bool {
441 self.vscroll.set_offset(offset)
442 }
443
444 pub fn page_len(&self) -> usize {
446 self.vscroll.page_len()
447 }
448
449 pub fn row_scroll_by(&self) -> usize {
451 self.vscroll.scroll_by()
452 }
453
454 pub fn x_max_offset(&self) -> usize {
457 self.hscroll.max_offset()
458 }
459
460 pub fn x_offset(&self) -> usize {
462 self.hscroll.offset()
463 }
464
465 pub fn set_x_offset(&mut self, offset: usize) -> bool {
470 self.hscroll.set_offset(offset)
471 }
472
473 pub fn page_width(&self) -> usize {
475 self.hscroll.page_len()
476 }
477
478 pub fn x_scroll_by(&self) -> usize {
480 self.hscroll.scroll_by()
481 }
482
483 pub fn scroll_to_selected(&mut self) -> bool {
487 if let Some(selected) = self.selection.lead_selection() {
488 let c = self.scroll_to_col(selected.0);
489 let r = self.scroll_to_row(selected.1);
490 r || c
491 } else {
492 false
493 }
494 }
495
496 pub fn scroll_to_row(&mut self, mut pos: usize) -> bool {
500 if pos >= self.rows {
501 false
502 } else if self.table_area.is_empty() {
503 self.set_row_offset(pos)
504 } else if let RowHeights::Uniform(row_height) = &self.row_heights {
505 let page_len = self.table_area.height.div_ceil(*row_height as u16) as usize;
506 debug!(
507 "scroll_to_row {} page_len {} inner.height {}",
508 pos, page_len, self.table_area.height
509 );
510 if pos >= self.row_offset().saturating_add(page_len - 1) {
511 if page_len as u16 * (*row_height) as u16 > self.table_area.height {
512 self.set_row_offset(pos.saturating_sub(page_len - 2))
513 } else {
514 self.set_row_offset(pos.saturating_sub(page_len - 1))
515 }
516 } else if pos < self.row_offset() {
517 self.set_row_offset(pos)
518 } else {
519 false
520 }
521 } else if let RowHeights::Varying(row_heights) = &self.row_heights {
522 if pos >= self.row_offset().saturating_add(self.page_len() - 1) {
523 if pos >= row_heights.len() {
524 self.set_row_offset(pos)
525 } else {
526 let mut sum_height = row_heights[pos];
527 if sum_height < self.table_area.height as u8 {
528 loop {
529 if pos < 1 {
530 break;
531 }
532 sum_height += row_heights[pos - 1];
533 if sum_height > self.table_area.height as u8 {
534 break;
535 }
536 pos -= 1;
537 }
538 }
539 self.set_row_offset(pos)
540 }
541 } else if pos < self.row_offset() {
542 self.set_row_offset(pos)
543 } else {
544 false
545 }
546 } else {
547 false
548 }
549 }
550
551 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
553 if let Some(col) = self.column_layout.get(pos) {
554 self.hscroll
555 .scroll_to_range(col.x as usize..col.right() as usize)
556 } else {
557 false
558 }
559 }
560
561 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
563 self.hscroll.scroll_to_pos(pos)
564 }
565
566 pub fn scroll_up(&mut self, n: usize) -> bool {
568 self.vscroll.scroll_up(n)
569 }
570
571 pub fn scroll_down(&mut self, n: usize) -> bool {
573 self.vscroll.scroll_down(n)
574 }
575
576 pub fn scroll_left(&mut self, n: usize) -> bool {
578 self.hscroll.scroll_left(n)
579 }
580
581 pub fn scroll_right(&mut self, n: usize) -> bool {
583 self.hscroll.scroll_right(n)
584 }
585}
586
587impl TableState<RowSelection> {
588 #[inline]
590 pub fn set_scroll_selection(&mut self, scroll: bool) {
591 self.selection.set_scroll_selected(scroll);
592 }
593
594 #[inline]
596 pub fn has_selection(&mut self) -> bool {
597 self.selection.has_selection()
598 }
599
600 #[inline]
603 pub fn selected(&self) -> Option<usize> {
604 self.selection.selected()
605 }
606
607 #[inline]
610 #[allow(clippy::manual_filter)]
611 pub fn selected_checked(&self) -> Option<usize> {
612 if let Some(selected) = self.selection.selected() {
613 if selected < self.rows {
614 Some(selected)
615 } else {
616 None
617 }
618 } else {
619 None
620 }
621 }
622
623 #[inline]
626 pub fn select(&mut self, row: Option<usize>) -> bool {
627 self.selection.select(row)
628 }
629
630 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
634 if self.vscroll.max_offset() > 0 {
635 (self.rows * offset) / self.vscroll.max_offset()
636 } else {
637 0
638 }
639 }
640
641 #[inline]
643 pub fn move_deselect(&mut self) -> bool {
644 let r = self.selection.select(None);
645 let s = self.set_row_offset(0);
646 r || s
647 }
648
649 #[inline]
652 pub fn move_to(&mut self, row: usize) -> bool {
653 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
654 let s = self.scroll_to_row(self.selection.selected().expect("row"));
655 r || s
656 }
657
658 #[inline]
661 pub fn move_up(&mut self, n: usize) -> bool {
662 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
663 let s = self.scroll_to_row(self.selection.selected().expect("row"));
664 r || s
665 }
666
667 #[inline]
670 pub fn move_down(&mut self, n: usize) -> bool {
671 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
672 let s = self.scroll_to_row(self.selection.selected().expect("row"));
673 r || s
674 }
675}
676
677impl TableState<RowSetSelection> {
678 #[inline]
680 pub fn has_selection(&mut self) -> bool {
681 self.selection.has_selection()
682 }
683
684 #[inline]
686 pub fn selected(&self) -> HashSet<usize> {
687 self.selection.selected()
688 }
689
690 #[inline]
697 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
698 self.selection.set_lead(row, extend)
699 }
700
701 #[inline]
703 pub fn lead(&self) -> Option<usize> {
704 self.selection.lead()
705 }
706
707 #[inline]
709 pub fn anchor(&self) -> Option<usize> {
710 self.selection.anchor()
711 }
712
713 #[inline]
716 pub fn retire_selection(&mut self) {
717 self.selection.retire_selection();
718 }
719
720 #[inline]
725 pub fn add_selected(&mut self, idx: usize) {
726 self.selection.add(idx);
727 }
728
729 #[inline]
734 pub fn remove_selected(&mut self, idx: usize) {
735 self.selection.remove(idx);
736 }
737
738 #[inline]
741 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
742 let r = self
743 .selection
744 .move_to(row, self.rows.saturating_sub(1), extend);
745 let s = self.scroll_to_row(self.selection.lead().expect("row"));
746 r || s
747 }
748
749 #[inline]
752 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
753 let r = self
754 .selection
755 .move_up(n, self.rows.saturating_sub(1), extend);
756 let s = self.scroll_to_row(self.selection.lead().expect("row"));
757 r || s
758 }
759
760 #[inline]
763 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
764 let r = self
765 .selection
766 .move_down(n, self.rows.saturating_sub(1), extend);
767 let s = self.scroll_to_row(self.selection.lead().expect("row"));
768 r || s
769 }
770}
771
772impl TableState<CellSelection> {
773 #[inline]
774 pub fn has_selection(&mut self) -> bool {
775 self.selection.has_selection()
776 }
777
778 #[inline]
780 pub fn selected(&self) -> Option<(usize, usize)> {
781 self.selection.selected()
782 }
783
784 #[inline]
786 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
787 self.selection.select_cell(select)
788 }
789
790 #[inline]
792 pub fn select_row(&mut self, row: Option<usize>) -> bool {
793 if let Some(row) = row {
794 self.selection
795 .select_row(Some(min(row, self.rows.saturating_sub(1))))
796 } else {
797 self.selection.select_row(None)
798 }
799 }
800
801 #[inline]
803 pub fn select_column(&mut self, column: Option<usize>) -> bool {
804 if let Some(column) = column {
805 self.selection
806 .select_column(Some(min(column, self.columns.saturating_sub(1))))
807 } else {
808 self.selection.select_column(None)
809 }
810 }
811
812 #[inline]
814 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
815 let r = self.selection.move_to(
816 select,
817 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
818 );
819 let s = self.scroll_to_selected();
820 r || s
821 }
822
823 #[inline]
825 pub fn move_to_row(&mut self, row: usize) -> bool {
826 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
827 let s = self.scroll_to_selected();
828 r || s
829 }
830
831 #[inline]
833 pub fn move_to_col(&mut self, col: usize) -> bool {
834 let r = self
835 .selection
836 .move_to_col(col, self.columns.saturating_sub(1));
837 let s = self.scroll_to_selected();
838 r || s
839 }
840
841 #[inline]
844 pub fn move_up(&mut self, n: usize) -> bool {
845 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
846 let s = self.scroll_to_selected();
847 r || s
848 }
849
850 #[inline]
853 pub fn move_down(&mut self, n: usize) -> bool {
854 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
855 let s = self.scroll_to_selected();
856 r || s
857 }
858
859 #[inline]
862 pub fn move_left(&mut self, n: usize) -> bool {
863 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
864 let s = self.scroll_to_selected();
865 r || s
866 }
867
868 #[inline]
871 pub fn move_right(&mut self, n: usize) -> bool {
872 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
873 let s = self.scroll_to_selected();
874 r || s
875 }
876}
877
878impl<Selection> HandleEvent<Event, DoubleClick, DoubleClickOutcome> for TableState<Selection> {
879 fn handle(&mut self, event: &Event, _keymap: DoubleClick) -> DoubleClickOutcome {
881 if self.focus.is_obscured() {
882 return DoubleClickOutcome::Continue;
883 }
884
885 match event {
886 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
887 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
888 DoubleClickOutcome::ClickClick(col, row)
889 } else {
890 DoubleClickOutcome::Continue
891 }
892 }
893 _ => DoubleClickOutcome::Continue,
894 }
895 }
896}
897
898impl<Selection> TableState<Selection> {
899 pub fn handle_doubleclick_events(&mut self, event: &Event) -> DoubleClickOutcome {
901 self.handle(event, DoubleClick)
902 }
903}
904
905impl TableState<RowSelection> {
906 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
910 self.focus.set(focus);
911 self.handle(event, Regular)
912 }
913
914 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
916 self.handle(event, MouseOnly)
917 }
918}
919
920impl TableState<CellSelection> {
921 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
925 self.focus.set(focus);
926 self.handle(event, Regular)
927 }
928
929 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
931 self.handle(event, MouseOnly)
932 }
933}
934
935impl TableState<NoSelection> {
936 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
940 self.focus.set(focus);
941 self.handle(event, Regular)
942 }
943
944 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
946 self.handle(event, MouseOnly)
947 }
948}
949
950impl TableState<RowSetSelection> {
951 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
955 self.focus.set(focus);
956 self.handle(event, Regular)
957 }
958
959 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
961 self.handle(event, MouseOnly)
962 }
963}