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