1use crate::_private::NonExhaustive;
2use crate::TableSelection;
3use crate::event::{DoubleClickOutcome, TableOutcome};
4use crate::selection::{CellSelection, NoSelection, RowSelection, RowSetSelection};
5use rat_cursor::HasScreenCursor;
6use rat_event::crossterm::event::Event;
7use rat_event::util::MouseFlags;
8use rat_event::{DoubleClick, HandleEvent, MouseOnly, Regular, ct_event};
9use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
10use rat_reloc::{RelocatableState, relocate_areas};
11use rat_scrolled::ScrollState;
12use ratatui_core::layout::Rect;
13use std::cmp::min;
14use std::collections::HashSet;
15
16#[derive(Debug)]
18pub struct TableState<Selection = RowSelection> {
19 pub focus: FocusFlag,
22
23 pub area: Rect,
26 pub inner: Rect,
29
30 pub header_area: Rect,
33 pub table_area: Rect,
36 pub row_areas: Vec<Rect>,
39 pub row_heights: RowHeights,
41 pub column_areas: Vec<Rect>,
45 pub column_layout: Vec<Rect>,
49 pub footer_area: Rect,
52
53 pub rows: usize,
56 pub columns: usize,
59
60 pub vscroll: ScrollState,
63 pub hscroll: ScrollState,
66
67 pub selection: Selection,
71
72 pub mouse: MouseFlags,
74
75 pub non_exhaustive: NonExhaustive,
76}
77
78#[derive(Debug, Clone)]
80pub enum RowHeights {
81 Uniform(u8),
82 Varying(Vec<u8>),
83}
84
85impl Default for RowHeights {
86 fn default() -> Self {
87 RowHeights::Uniform(1)
88 }
89}
90
91impl RowHeights {
92 pub(crate) fn clear(&mut self) {
93 match self {
94 RowHeights::Uniform(_) => {}
95 RowHeights::Varying(h) => h.clear(),
96 }
97 }
98
99 pub(crate) fn push(&mut self, row_height: u8, varying: bool) {
100 if varying {
101 match self {
102 RowHeights::Uniform(_) => {
103 *self = RowHeights::Varying(vec![row_height]);
104 }
105 RowHeights::Varying(h) => h.push(row_height),
106 }
107 } else {
108 *self = RowHeights::Uniform(row_height);
109 }
110 }
111
112 pub(crate) fn items_added(&mut self, pos: usize, n: usize) {
113 match self {
114 RowHeights::Uniform(_) => {}
115 RowHeights::Varying(h) => {
116 if pos <= h.len() {
117 for _ in 0..n {
118 h.insert(pos, 1);
119 }
120 }
121 }
122 }
123 }
124
125 pub(crate) fn items_added_with_heights(&mut self, pos: usize, height: &[u8]) {
126 match self {
127 RowHeights::Uniform(_) => {}
128 RowHeights::Varying(h) => {
129 if pos <= h.len() {
130 h.splice(pos..pos, height.into_iter().copied());
131 }
132 }
133 }
134 }
135
136 pub(crate) fn items_removed(&mut self, pos: usize, n: usize) {
137 match self {
138 RowHeights::Uniform(_) => {}
139 RowHeights::Varying(h) => {
140 if pos < h.len() {
141 if pos + n < h.len() {
142 h.drain(pos..pos + n);
143 } else {
144 h.drain(pos..);
145 }
146 }
147 }
148 }
149 }
150}
151
152impl<Selection: Clone> Clone for TableState<Selection> {
153 fn clone(&self) -> Self {
154 Self {
155 focus: self.focus.new_instance(),
156 area: self.area,
157 inner: self.inner,
158 header_area: self.header_area,
159 table_area: self.table_area,
160 row_areas: self.row_areas.clone(),
161 row_heights: self.row_heights.clone(),
162 column_areas: self.column_areas.clone(),
163 column_layout: self.column_layout.clone(),
164 footer_area: self.footer_area,
165 rows: self.rows,
166 columns: self.columns,
167 vscroll: self.vscroll.clone(),
168 hscroll: self.hscroll.clone(),
169 selection: self.selection.clone(),
170 mouse: Default::default(),
171 non_exhaustive: NonExhaustive,
172 }
173 }
174}
175
176impl<Selection: Default> Default for TableState<Selection> {
177 fn default() -> Self {
178 Self {
179 focus: Default::default(),
180 area: Default::default(),
181 inner: Default::default(),
182 header_area: Default::default(),
183 table_area: Default::default(),
184 row_areas: Default::default(),
185 row_heights: Default::default(),
186 column_areas: Default::default(),
187 column_layout: Default::default(),
188 footer_area: Default::default(),
189 rows: Default::default(),
190 columns: Default::default(),
191 vscroll: Default::default(),
192 hscroll: Default::default(),
193 selection: Default::default(),
194 mouse: Default::default(),
195 non_exhaustive: NonExhaustive,
196 }
197 }
198}
199
200impl<Selection> HasFocus for TableState<Selection> {
201 fn build(&self, builder: &mut FocusBuilder) {
202 builder.leaf_widget(self);
203 }
204
205 #[inline]
206 fn focus(&self) -> FocusFlag {
207 self.focus.clone()
208 }
209
210 #[inline]
211 fn area(&self) -> Rect {
212 self.area
213 }
214}
215
216impl<Selection> HasScreenCursor for TableState<Selection> {
217 fn screen_cursor(&self) -> Option<(u16, u16)> {
218 None
219 }
220}
221
222impl<Selection> RelocatableState for TableState<Selection> {
223 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
224 self.area.relocate(shift, clip);
225 self.inner.relocate(shift, clip);
226 self.header_area.relocate(shift, clip);
227 self.table_area.relocate(shift, clip);
228 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
229 relocate_areas(self.column_areas.as_mut_slice(), shift, clip);
230 relocate_areas(self.column_layout.as_mut_slice(), shift, clip);
231 self.footer_area.relocate(shift, clip);
232 self.hscroll.relocate(shift, clip);
233 self.vscroll.relocate(shift, clip);
234 }
235}
236
237impl<Selection> TableState<Selection>
238where
239 Selection: Default,
240{
241 pub fn new() -> Self {
243 Self::default()
244 }
245
246 pub fn named(name: &str) -> Self {
248 Self {
249 focus: FocusFlag::new().with_name(name),
250 ..TableState::default()
251 }
252 }
253}
254
255impl<Selection> TableState<Selection> {
257 #[inline]
259 pub fn rows(&self) -> usize {
260 self.rows
261 }
262
263 #[inline]
265 pub fn columns(&self) -> usize {
266 self.columns
267 }
268}
269
270impl<Selection> TableState<Selection> {
272 pub fn row_cells(&self, row: usize) -> Option<(Rect, Vec<Rect>)> {
278 if row < self.vscroll.offset() || row >= self.vscroll.offset() + self.vscroll.page_len() {
279 return None;
280 }
281
282 let mut areas = Vec::new();
283
284 let r = self.row_areas[row - self.vscroll.offset()];
285 for c in &self.column_areas {
286 areas.push(Rect::new(c.x, r.y, c.width, r.height));
287 }
288
289 Some((r, areas))
290 }
291
292 pub fn cell_at_clicked(&self, pos: (u16, u16)) -> Option<(usize, usize)> {
294 let col = self.column_at_clicked(pos);
295 let row = self.row_at_clicked(pos);
296
297 match (col, row) {
298 (Some(col), Some(row)) => Some((col, row)),
299 _ => None,
300 }
301 }
302
303 pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
305 self.mouse.column_at(&self.column_areas, pos.0)
306 }
307
308 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
310 self.mouse
311 .row_at(&self.row_areas, pos.1)
312 .map(|v| self.vscroll.offset() + v)
313 }
314
315 pub fn cell_at_drag(&self, pos: (u16, u16)) -> (usize, usize) {
318 let col = self.column_at_drag(pos);
319 let row = self.row_at_drag(pos);
320
321 (col, row)
322 }
323
324 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
331 match self
332 .mouse
333 .row_at_drag(self.table_area, &self.row_areas, pos.1)
334 {
335 Ok(v) => self.vscroll.offset() + v,
336 Err(v) if v <= 0 => self.vscroll.offset().saturating_sub((-v) as usize),
337 Err(v) => self.vscroll.offset() + self.row_areas.len() + v as usize,
338 }
339 }
340
341 pub fn column_at_drag(&self, pos: (u16, u16)) -> usize {
345 match self
346 .mouse
347 .column_at_drag(self.table_area, &self.column_areas, pos.0)
348 {
349 Ok(v) => v,
350 Err(v) if v <= 0 => self.hscroll.offset().saturating_sub((-v) as usize),
351 Err(v) => self.hscroll.offset() + self.hscroll.page_len() + v as usize,
352 }
353 }
354}
355
356impl<Selection: TableSelection> TableState<Selection> {
358 #[inline]
360 pub fn clear_selection(&mut self) {
361 self.selection.clear();
362 }
363
364 pub fn items_added(&mut self, pos: usize, n: usize) {
371 self.rows = self.rows.saturating_add(n);
372 self.vscroll.items_added(pos, n);
373 self.row_heights.items_added(pos, n);
374 self.selection.items_added(pos, n);
375 }
376
377 pub fn items_added_with_heigths(&mut self, pos: usize, height: &[u8]) {
384 self.rows = self.rows.saturating_add(height.len());
385 self.vscroll.items_added(pos, height.len());
386 self.row_heights.items_added_with_heights(pos, height);
387 self.selection.items_added(pos, height.len());
388 }
389
390 pub fn items_removed(&mut self, pos: usize, n: usize) {
397 self.rows = self.rows.saturating_sub(n);
398 self.vscroll.items_removed(pos, n);
399 self.row_heights.items_removed(pos, n);
400 self.selection.items_removed(pos, n, self.rows);
401 }
402
403 pub fn rows_changed(&mut self, rows: usize) {
410 self.selection.validate_rows(rows);
411 self.vscroll.rows_changed(rows);
412 self.row_heights.clear();
413 self.rows = rows;
414 }
415
416 pub fn clear_offset(&mut self) {
418 self.vscroll.set_offset(0);
419 self.hscroll.set_offset(0);
420 }
421
422 pub fn row_max_offset(&self) -> usize {
427 self.vscroll.max_offset()
428 }
429
430 pub fn row_offset(&self) -> usize {
432 self.vscroll.offset()
433 }
434
435 pub fn set_row_offset(&mut self, offset: usize) -> bool {
440 self.vscroll.set_offset(offset)
441 }
442
443 pub fn page_len(&self) -> usize {
445 self.vscroll.page_len()
446 }
447
448 pub fn row_scroll_by(&self) -> usize {
450 self.vscroll.scroll_by()
451 }
452
453 pub fn x_max_offset(&self) -> usize {
456 self.hscroll.max_offset()
457 }
458
459 pub fn x_offset(&self) -> usize {
461 self.hscroll.offset()
462 }
463
464 pub fn set_x_offset(&mut self, offset: usize) -> bool {
469 self.hscroll.set_offset(offset)
470 }
471
472 pub fn page_width(&self) -> usize {
474 self.hscroll.page_len()
475 }
476
477 pub fn x_scroll_by(&self) -> usize {
479 self.hscroll.scroll_by()
480 }
481
482 pub fn scroll_to_selected(&mut self) -> bool {
486 if let Some(selected) = self.selection.lead_selection() {
487 let c = self.scroll_to_col(selected.0);
488 let r = self.scroll_to_row(selected.1);
489 r || c
490 } else {
491 false
492 }
493 }
494
495 pub fn scroll_to_row(&mut self, mut pos: usize) -> bool {
499 if pos >= self.rows {
500 false
501 } else if self.inner.is_empty() {
502 self.set_row_offset(pos)
503 } else if let RowHeights::Uniform(row_height) = &self.row_heights {
504 let page_len = self.inner.height.div_ceil(*row_height as u16) as usize;
505 if pos >= self.row_offset().saturating_add(page_len - 1) {
506 if page_len as u16 * (*row_height) as u16 > self.table_area.height {
507 self.set_row_offset(pos.saturating_sub(page_len - 2))
508 } else {
509 self.set_row_offset(pos.saturating_sub(page_len - 1))
510 }
511 } else if pos < self.row_offset() {
512 self.set_row_offset(pos)
513 } else {
514 false
515 }
516 } else if let RowHeights::Varying(row_heights) = &self.row_heights {
517 if pos >= self.row_offset().saturating_add(self.page_len() - 1) {
518 if pos >= row_heights.len() {
519 self.set_row_offset(pos)
520 } else {
521 let mut sum_height = row_heights[pos];
522 if sum_height < self.table_area.height as u8 {
523 loop {
524 if pos < 1 {
525 break;
526 }
527 sum_height += row_heights[pos - 1];
528 if sum_height > self.table_area.height as u8 {
529 break;
530 }
531 pos -= 1;
532 }
533 }
534 self.set_row_offset(pos)
535 }
536 } else if pos < self.row_offset() {
537 self.set_row_offset(pos)
538 } else {
539 false
540 }
541 } else {
542 false
543 }
544 }
545
546 pub fn scroll_to_col(&mut self, pos: usize) -> bool {
548 if let Some(col) = self.column_layout.get(pos) {
549 self.hscroll
550 .scroll_to_range(col.x as usize..col.right() as usize)
551 } else {
552 false
553 }
554 }
555
556 pub fn scroll_to_x(&mut self, pos: usize) -> bool {
558 self.hscroll.scroll_to_pos(pos)
559 }
560
561 pub fn scroll_up(&mut self, n: usize) -> bool {
563 self.vscroll.scroll_up(n)
564 }
565
566 pub fn scroll_down(&mut self, n: usize) -> bool {
568 self.vscroll.scroll_down(n)
569 }
570
571 pub fn scroll_left(&mut self, n: usize) -> bool {
573 self.hscroll.scroll_left(n)
574 }
575
576 pub fn scroll_right(&mut self, n: usize) -> bool {
578 self.hscroll.scroll_right(n)
579 }
580}
581
582impl TableState<RowSelection> {
583 #[inline]
585 pub fn set_scroll_selection(&mut self, scroll: bool) {
586 self.selection.set_scroll_selected(scroll);
587 }
588
589 #[inline]
591 pub fn has_selection(&mut self) -> bool {
592 self.selection.has_selection()
593 }
594
595 #[inline]
598 pub fn selected(&self) -> Option<usize> {
599 self.selection.selected()
600 }
601
602 #[inline]
605 #[allow(clippy::manual_filter)]
606 pub fn selected_checked(&self) -> Option<usize> {
607 if let Some(selected) = self.selection.selected() {
608 if selected < self.rows {
609 Some(selected)
610 } else {
611 None
612 }
613 } else {
614 None
615 }
616 }
617
618 #[inline]
621 pub fn select(&mut self, row: Option<usize>) -> bool {
622 self.selection.select(row)
623 }
624
625 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
629 if self.vscroll.max_offset() > 0 {
630 (self.rows * offset) / self.vscroll.max_offset()
631 } else {
632 0
633 }
634 }
635
636 #[inline]
638 pub fn move_deselect(&mut self) -> bool {
639 let r = self.selection.select(None);
640 let s = self.set_row_offset(0);
641 r || s
642 }
643
644 #[inline]
647 pub fn move_to(&mut self, row: usize) -> bool {
648 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
649 let s = self.scroll_to_row(self.selection.selected().expect("row"));
650 r || s
651 }
652
653 #[inline]
656 pub fn move_up(&mut self, n: usize) -> bool {
657 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
658 let s = self.scroll_to_row(self.selection.selected().expect("row"));
659 r || s
660 }
661
662 #[inline]
665 pub fn move_down(&mut self, n: usize) -> bool {
666 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
667 let s = self.scroll_to_row(self.selection.selected().expect("row"));
668 r || s
669 }
670}
671
672impl TableState<RowSetSelection> {
673 #[inline]
675 pub fn has_selection(&mut self) -> bool {
676 self.selection.has_selection()
677 }
678
679 #[inline]
681 pub fn selected(&self) -> HashSet<usize> {
682 self.selection.selected()
683 }
684
685 #[inline]
692 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
693 self.selection.set_lead(row, extend)
694 }
695
696 #[inline]
698 pub fn lead(&self) -> Option<usize> {
699 self.selection.lead()
700 }
701
702 #[inline]
704 pub fn anchor(&self) -> Option<usize> {
705 self.selection.anchor()
706 }
707
708 #[inline]
711 pub fn retire_selection(&mut self) {
712 self.selection.retire_selection();
713 }
714
715 #[inline]
720 pub fn add_selected(&mut self, idx: usize) {
721 self.selection.add(idx);
722 }
723
724 #[inline]
729 pub fn remove_selected(&mut self, idx: usize) {
730 self.selection.remove(idx);
731 }
732
733 #[inline]
736 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
737 let r = self
738 .selection
739 .move_to(row, self.rows.saturating_sub(1), extend);
740 let s = self.scroll_to_row(self.selection.lead().expect("row"));
741 r || s
742 }
743
744 #[inline]
747 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
748 let r = self
749 .selection
750 .move_up(n, self.rows.saturating_sub(1), extend);
751 let s = self.scroll_to_row(self.selection.lead().expect("row"));
752 r || s
753 }
754
755 #[inline]
758 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
759 let r = self
760 .selection
761 .move_down(n, self.rows.saturating_sub(1), extend);
762 let s = self.scroll_to_row(self.selection.lead().expect("row"));
763 r || s
764 }
765}
766
767impl TableState<CellSelection> {
768 #[inline]
769 pub fn has_selection(&mut self) -> bool {
770 self.selection.has_selection()
771 }
772
773 #[inline]
775 pub fn selected(&self) -> Option<(usize, usize)> {
776 self.selection.selected()
777 }
778
779 #[inline]
781 pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
782 self.selection.select_cell(select)
783 }
784
785 #[inline]
787 pub fn select_row(&mut self, row: Option<usize>) -> bool {
788 if let Some(row) = row {
789 self.selection
790 .select_row(Some(min(row, self.rows.saturating_sub(1))))
791 } else {
792 self.selection.select_row(None)
793 }
794 }
795
796 #[inline]
798 pub fn select_column(&mut self, column: Option<usize>) -> bool {
799 if let Some(column) = column {
800 self.selection
801 .select_column(Some(min(column, self.columns.saturating_sub(1))))
802 } else {
803 self.selection.select_column(None)
804 }
805 }
806
807 #[inline]
809 pub fn move_to(&mut self, select: (usize, usize)) -> bool {
810 let r = self.selection.move_to(
811 select,
812 (self.columns.saturating_sub(1), self.rows.saturating_sub(1)),
813 );
814 let s = self.scroll_to_selected();
815 r || s
816 }
817
818 #[inline]
820 pub fn move_to_row(&mut self, row: usize) -> bool {
821 let r = self.selection.move_to_row(row, self.rows.saturating_sub(1));
822 let s = self.scroll_to_selected();
823 r || s
824 }
825
826 #[inline]
828 pub fn move_to_col(&mut self, col: usize) -> bool {
829 let r = self
830 .selection
831 .move_to_col(col, self.columns.saturating_sub(1));
832 let s = self.scroll_to_selected();
833 r || s
834 }
835
836 #[inline]
839 pub fn move_up(&mut self, n: usize) -> bool {
840 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
841 let s = self.scroll_to_selected();
842 r || s
843 }
844
845 #[inline]
848 pub fn move_down(&mut self, n: usize) -> bool {
849 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
850 let s = self.scroll_to_selected();
851 r || s
852 }
853
854 #[inline]
857 pub fn move_left(&mut self, n: usize) -> bool {
858 let r = self.selection.move_left(n, self.columns.saturating_sub(1));
859 let s = self.scroll_to_selected();
860 r || s
861 }
862
863 #[inline]
866 pub fn move_right(&mut self, n: usize) -> bool {
867 let r = self.selection.move_right(n, self.columns.saturating_sub(1));
868 let s = self.scroll_to_selected();
869 r || s
870 }
871}
872
873impl<Selection> HandleEvent<Event, DoubleClick, DoubleClickOutcome> for TableState<Selection> {
874 fn handle(&mut self, event: &Event, _keymap: DoubleClick) -> DoubleClickOutcome {
876 if self.focus.is_obscured() {
877 return DoubleClickOutcome::Continue;
878 }
879
880 match event {
881 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table_area, m) => {
882 if let Some((col, row)) = self.cell_at_clicked((m.column, m.row)) {
883 DoubleClickOutcome::ClickClick(col, row)
884 } else {
885 DoubleClickOutcome::Continue
886 }
887 }
888 _ => DoubleClickOutcome::Continue,
889 }
890 }
891}
892
893impl<Selection> TableState<Selection> {
894 pub fn handle_doubleclick_events(&mut self, event: &Event) -> DoubleClickOutcome {
896 self.handle(event, DoubleClick)
897 }
898}
899
900impl TableState<RowSelection> {
901 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
905 self.focus.set(focus);
906 self.handle(event, Regular)
907 }
908
909 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
911 self.handle(event, MouseOnly)
912 }
913}
914
915impl TableState<CellSelection> {
916 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
920 self.focus.set(focus);
921 self.handle(event, Regular)
922 }
923
924 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
926 self.handle(event, MouseOnly)
927 }
928}
929
930impl TableState<NoSelection> {
931 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
935 self.focus.set(focus);
936 self.handle(event, Regular)
937 }
938
939 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
941 self.handle(event, MouseOnly)
942 }
943}
944
945impl TableState<RowSetSelection> {
946 pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
950 self.focus.set(focus);
951 self.handle(event, Regular)
952 }
953
954 pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
956 self.handle(event, MouseOnly)
957 }
958}