Skip to main content

rat_ftable2/table/
state.rs

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/// State for the table.
18#[derive(Debug)]
19pub struct TableState<Selection = RowSelection> {
20    /// Current focus state.
21    /// __read+write__
22    pub focus: FocusFlag,
23
24    /// Total area.
25    /// __read only__ Renewed with each render.
26    pub area: Rect,
27    /// Area inside the border and scrollbars
28    /// __read only__ Renewed with each render.
29    pub inner: Rect,
30
31    /// Total header area.
32    /// __read only__ Renewed with each render.
33    pub header_area: Rect,
34    /// Total table area.
35    /// __read only__ Renewed with each render.
36    pub table_area: Rect,
37    /// Area per visible row. The first element is at row_offset.
38    /// __read only__ Renewed with each render.
39    pub row_areas: Vec<Rect>,
40    /// Height for every row.
41    pub row_heights: RowHeights,
42    /// Area for each column plus the following spacer if any.
43    /// Invisible columns have width 0, height is the height of the table_area.
44    /// __read only__ Renewed with each render.
45    pub column_areas: Vec<Rect>,
46    /// Layout areas for each column plus the following spacer if any.
47    /// Positions are 0-based, y and height are 0.
48    /// __read only__ Renewed with each render.
49    pub column_layout: Vec<Rect>,
50    /// Total footer area.
51    /// __read only__ Renewed with each render.
52    pub footer_area: Rect,
53
54    /// Row count.
55    /// __read+write__ Renewed with each render.
56    pub rows: usize,
57    /// Column count.
58    /// __read only__ Renewed with each render.
59    pub columns: usize,
60
61    /// Row scrolling data.
62    /// __read+write__ max_offset set with each render.
63    pub vscroll: ScrollState,
64    /// Column scrolling data.
65    /// __read+write__ max_offset set with each render.
66    pub hscroll: ScrollState,
67
68    /// Selection data.
69    /// __read+write__ selection model.
70    /// selection is not bound by actual rows.
71    pub selection: Selection,
72
73    /// Helper for mouse interactions.
74    pub mouse: MouseFlags,
75
76    pub non_exhaustive: NonExhaustive,
77}
78
79/// Row heights for every row.
80#[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    /// New widget.
243    pub fn new() -> Self {
244        Self::default()
245    }
246
247    /// New named widget.
248    pub fn named(name: &str) -> Self {
249        Self {
250            focus: FocusFlag::new().with_name(name),
251            ..TableState::default()
252        }
253    }
254}
255
256// Baseline
257impl<Selection> TableState<Selection> {
258    /// Number of rows.
259    #[inline]
260    pub fn rows(&self) -> usize {
261        self.rows
262    }
263
264    /// Number of columns.
265    #[inline]
266    pub fn columns(&self) -> usize {
267        self.columns
268    }
269}
270
271// Table areas
272impl<Selection> TableState<Selection> {
273    /// Returns the whole row-area and the cell-areas for the
274    /// given row, if it is visible.
275    ///
276    /// Attention: These areas might be 0-length if the column is scrolled
277    /// beyond the table-area.
278    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    /// Cell at given position.
294    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    /// Column at given position.
305    pub fn column_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
306        self.mouse.column_at(&self.column_areas, pos.0)
307    }
308
309    /// Row at given position.
310    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    /// Cell when dragging. Position can be outside the table area.
317    /// See [row_at_drag](TableState::row_at_drag), [col_at_drag](TableState::column_at_drag)
318    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    /// Row when dragging. Position can be outside the table area.
326    /// If the position is above the table-area this returns offset - #rows.
327    /// If the position is below the table-area this returns offset + page_len + #rows.
328    ///
329    /// This doesn't account for the row-height of the actual rows outside
330    /// the table area, just assumes '1'.
331    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    /// Column when dragging. Position can be outside the table area.
343    /// If the position is left of the table area this returns offset - 1.
344    /// If the position is right of the table area this returns offset + page_width + 1.
345    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
357// Offset related.
358impl<Selection: TableSelection> TableState<Selection> {
359    /// Clear the selection.
360    #[inline]
361    pub fn clear_selection(&mut self) {
362        self.selection.clear();
363    }
364
365    /// Update the state to match adding items *during* event-handling.
366    /// A lot of functions use the number of rows, so when you change the
367    /// underlying data you might want to immediately reflect this in the
368    /// table-state.
369    ///
370    /// This corrects the number of rows, offset and selection.
371    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    /// Update the state to match adding items.
379    /// Use this function if you have varying row-heights.
380    ///
381    /// This corrects the number of rows, offset and selection.
382    /// This also corrects the stored row-heights with the
383    /// given heights.
384    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    /// Update the state to match removing items *during* event-handling.
392    /// A lot of functions use the number of rows, so when you change the
393    /// underlying data you might want to immediately reflect this in the
394    /// table-state.
395    ///
396    /// This corrects the number of rows, offset and selection.
397    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    /// Update the number of rows *during* event-handling.
405    /// A lot of functions use the number of rows, so when you change the
406    /// underlying data you might want to immediately reflect this in the
407    /// table-state.
408    ///
409    /// This corrects the number of rows, offset and selection.
410    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    /// Sets both offsets to 0.
418    pub fn clear_offset(&mut self) {
419        self.vscroll.set_offset(0);
420        self.hscroll.set_offset(0);
421    }
422
423    /// Maximum offset that is accessible with scrolling.
424    ///
425    /// This is shorter than the length by whatever fills the last page.
426    /// This is the base for the scrollbar content_length.
427    pub fn row_max_offset(&self) -> usize {
428        self.vscroll.max_offset()
429    }
430
431    /// Current vertical offset.
432    pub fn row_offset(&self) -> usize {
433        self.vscroll.offset()
434    }
435
436    /// Change the vertical offset.
437    /// The vertical offset may be beyond the number of rows.
438    ///
439    /// The widget returns true if the offset changed at all.
440    pub fn set_row_offset(&mut self, offset: usize) -> bool {
441        self.vscroll.set_offset(offset)
442    }
443
444    /// Vertical page-size at the current offset.
445    pub fn page_len(&self) -> usize {
446        self.vscroll.page_len()
447    }
448
449    /// Suggested scroll per scroll-event.
450    pub fn row_scroll_by(&self) -> usize {
451        self.vscroll.scroll_by()
452    }
453
454    /// Maximum offset that is accessible with scrolling.
455    /// This is the table-width minus the widget-width plus some overscroll.
456    pub fn x_max_offset(&self) -> usize {
457        self.hscroll.max_offset()
458    }
459
460    /// Current horizontal offset.
461    pub fn x_offset(&self) -> usize {
462        self.hscroll.offset()
463    }
464
465    /// Change the horizontal offset.
466    /// The offset may be beyond the table-width.
467    ///
468    /// The widget returns true if the offset changed at all.
469    pub fn set_x_offset(&mut self, offset: usize) -> bool {
470        self.hscroll.set_offset(offset)
471    }
472
473    /// Horizontal page-size at the current offset.
474    pub fn page_width(&self) -> usize {
475        self.hscroll.page_len()
476    }
477
478    /// Suggested scroll per scroll-event.
479    pub fn x_scroll_by(&self) -> usize {
480        self.hscroll.scroll_by()
481    }
482
483    /// Ensures that the selected item is visible.
484    /// Caveat: This doesn't work nicely if you have varying row-heights.
485    /// Caveat: Number of rows needs to be correct.
486    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    /// Ensures that the given row is visible.
497    /// Caveat: This doesn't work nicely if you have varying row-heights.
498    /// Caveat: Number of rows needs to be correct.
499    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    /// Ensures that the given column is completely visible.
552    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    /// Ensures that the given position is visible.
562    pub fn scroll_to_x(&mut self, pos: usize) -> bool {
563        self.hscroll.scroll_to_pos(pos)
564    }
565
566    /// Reduce the row-offset by n.
567    pub fn scroll_up(&mut self, n: usize) -> bool {
568        self.vscroll.scroll_up(n)
569    }
570
571    /// Increase the row-offset by n.
572    pub fn scroll_down(&mut self, n: usize) -> bool {
573        self.vscroll.scroll_down(n)
574    }
575
576    /// Reduce the col-offset by n.
577    pub fn scroll_left(&mut self, n: usize) -> bool {
578        self.hscroll.scroll_left(n)
579    }
580
581    /// Increase the col-offset by n.
582    pub fn scroll_right(&mut self, n: usize) -> bool {
583        self.hscroll.scroll_right(n)
584    }
585}
586
587impl TableState<RowSelection> {
588    /// When scrolling the table, change the selection instead of the offset.
589    #[inline]
590    pub fn set_scroll_selection(&mut self, scroll: bool) {
591        self.selection.set_scroll_selected(scroll);
592    }
593
594    /// Anything selected?
595    #[inline]
596    pub fn has_selection(&mut self) -> bool {
597        self.selection.has_selection()
598    }
599
600    /// Selected row.
601    /// The selected row is not constrained by the row-count.
602    #[inline]
603    pub fn selected(&self) -> Option<usize> {
604        self.selection.selected()
605    }
606
607    /// Return the selected row and ensure it is in the
608    /// range `0..rows`.
609    #[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    /// Select the row.
624    /// The selection is not constrained by the row-count.
625    #[inline]
626    pub fn select(&mut self, row: Option<usize>) -> bool {
627        self.selection.select(row)
628    }
629
630    /// Scroll delivers a value between 0 and max_offset as offset.
631    /// This remaps the ratio to the selection with a range 0..row_len.
632    /// Info: This is used when scroll_selected is active.
633    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    /// Set the selection to None and set the offset to 0
642    #[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    /// Move the selection to the given row.
650    /// Ensures the row is visible afterward.
651    #[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    /// Move the selection up n rows.
659    /// Ensures the row is visible afterward.
660    #[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    /// Move the selection down n rows.
668    /// Ensures the row is visible afterward.
669    #[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    /// Anything selected?
679    #[inline]
680    pub fn has_selection(&mut self) -> bool {
681        self.selection.has_selection()
682    }
683
684    /// Selected rows.
685    #[inline]
686    pub fn selected(&self) -> HashSet<usize> {
687        self.selection.selected()
688    }
689
690    /// Change the lead-selection. Limits the value to the number of rows.
691    /// If extend is false the current selection is cleared and both lead and
692    /// anchor are set to the given value.
693    /// If extend is true, the anchor is kept where it is and lead is changed.
694    /// Everything in the range `anchor..lead` is selected. It doesn't matter
695    /// if anchor < lead.
696    #[inline]
697    pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
698        self.selection.set_lead(row, extend)
699    }
700
701    /// Current lead.
702    #[inline]
703    pub fn lead(&self) -> Option<usize> {
704        self.selection.lead()
705    }
706
707    /// Current anchor.
708    #[inline]
709    pub fn anchor(&self) -> Option<usize> {
710        self.selection.anchor()
711    }
712
713    /// Retire the current anchor/lead selection to the set of selected rows.
714    /// Resets lead and anchor and starts a new selection round.
715    #[inline]
716    pub fn retire_selection(&mut self) {
717        self.selection.retire_selection();
718    }
719
720    /// Add to selection. Only works for retired selections, not for the
721    /// active anchor-lead range.
722    ///
723    /// To be sure call [retire_selection][Self::retire_selection] first.
724    #[inline]
725    pub fn add_selected(&mut self, idx: usize) {
726        self.selection.add(idx);
727    }
728
729    /// Remove from selection. Only works for retired selections, not for the
730    /// active anchor-lead range.
731    ///
732    /// To be sure call [retire_selection][Self::retire_selection] first.
733    #[inline]
734    pub fn remove_selected(&mut self, idx: usize) {
735        self.selection.remove(idx);
736    }
737
738    /// Move the selection to the given row.
739    /// Ensures the row is visible afterward.
740    #[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    /// Move the selection up n rows.
750    /// Ensures the row is visible afterward.
751    #[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    /// Move the selection down n rows.
761    /// Ensures the row is visible afterwards.
762    #[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    /// Selected cell.
779    #[inline]
780    pub fn selected(&self) -> Option<(usize, usize)> {
781        self.selection.selected()
782    }
783
784    /// Select a cell.
785    #[inline]
786    pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
787        self.selection.select_cell(select)
788    }
789
790    /// Select a row. Column stays the same.
791    #[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    /// Select a column, row stays the same.
802    #[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    /// Select a cell, limit to maximum.
813    #[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    /// Select a row, limit to maximum.
824    #[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    /// Select a cell, clamp between 0 and maximum.
832    #[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    /// Move the selection up n rows.
842    /// Ensures the row is visible afterwards.
843    #[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    /// Move the selection down n rows.
851    /// Ensures the row is visible afterwards.
852    #[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    /// Move the selection left n columns.
860    /// Ensures the row is visible afterwards.
861    #[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    /// Move the selection right n columns.
869    /// Ensures the row is visible afterwards.
870    #[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    /// Handles double-click events on the table.
880    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    /// Handle all events for recognizing double-clicks.
900    pub fn handle_doubleclick_events(&mut self, event: &Event) -> DoubleClickOutcome {
901        self.handle(event, DoubleClick)
902    }
903}
904
905impl TableState<RowSelection> {
906    /// Handle all events.
907    /// Table events are only processed if focus is true.
908    /// Mouse events are processed if they are in range.
909    pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
910        self.focus.set(focus);
911        self.handle(event, Regular)
912    }
913
914    /// Handle only mouse-events.
915    pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
916        self.handle(event, MouseOnly)
917    }
918}
919
920impl TableState<CellSelection> {
921    /// Handle all events.
922    /// Table events are only processed if focus is true.
923    /// Mouse events are processed if they are in range.
924    pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
925        self.focus.set(focus);
926        self.handle(event, Regular)
927    }
928
929    /// Handle only mouse-events.
930    pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
931        self.handle(event, MouseOnly)
932    }
933}
934
935impl TableState<NoSelection> {
936    /// Handle all events.
937    /// Table events are only processed if focus is true.
938    /// Mouse events are processed if they are in range.
939    pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
940        self.focus.set(focus);
941        self.handle(event, Regular)
942    }
943
944    /// Handle only mouse-events.
945    pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
946        self.handle(event, MouseOnly)
947    }
948}
949
950impl TableState<RowSetSelection> {
951    /// Handle all events.
952    /// Table events are only processed if focus is true.
953    /// Mouse events are processed if they are in range.
954    pub fn handle_events(&mut self, focus: bool, event: &Event) -> TableOutcome {
955        self.focus.set(focus);
956        self.handle(event, Regular)
957    }
958
959    /// Handle only mouse-events.
960    pub fn handle_mouse_events(&mut self, event: &Event) -> TableOutcome {
961        self.handle(event, MouseOnly)
962    }
963}