rat_ftable/
cellselection.rs

1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
4use rat_focus::HasFocus;
5use rat_scrolled::ScrollAreaState;
6use rat_scrolled::event::ScrollOutcome;
7use ratatui_crossterm::crossterm::event::Event;
8use std::cmp::{max, min};
9
10/// Select a single cell in the table.
11///
12/// This one supports cell + column + row selection.
13#[derive(Debug, Default, Clone)]
14pub struct CellSelection {
15    /// Selected cell.
16    pub lead_cell: Option<(usize, usize)>,
17}
18
19impl TableSelection for CellSelection {
20    fn count(&self) -> usize {
21        if self.lead_cell.is_some() { 1 } else { 0 }
22    }
23
24    fn is_selected_row(&self, row: usize) -> bool {
25        self.lead_cell.map(|(_scol, srow)| srow) == Some(row)
26    }
27
28    fn is_selected_column(&self, column: usize) -> bool {
29        self.lead_cell.map(|(scol, _srow)| scol) == Some(column)
30    }
31
32    fn is_selected_cell(&self, col: usize, row: usize) -> bool {
33        self.lead_cell == Some((col, row))
34    }
35
36    fn lead_selection(&self) -> Option<(usize, usize)> {
37        self.lead_cell
38    }
39}
40
41impl CellSelection {
42    /// New
43    pub fn new() -> CellSelection {
44        Self::default()
45    }
46
47    /// Clear the selection.
48    #[inline]
49    pub fn clear(&mut self) {
50        self.lead_cell = None;
51    }
52
53    /// Selected cell.
54    pub fn selected(&self) -> Option<(usize, usize)> {
55        self.lead_cell
56    }
57
58    #[inline]
59    pub fn has_selection(&mut self) -> bool {
60        self.lead_cell.is_some()
61    }
62
63    /// Select a cell.
64    pub fn select_cell(&mut self, select: Option<(usize, usize)>) -> bool {
65        let old_cell = self.lead_cell;
66        self.lead_cell = select;
67        old_cell != self.lead_cell
68    }
69
70    /// Select a row. Column stays the same.
71    pub fn select_row(&mut self, select: Option<usize>) -> bool {
72        let old_cell = self.lead_cell;
73        self.lead_cell = match self.lead_cell {
74            None => select.map(|v| (0, v)),
75            Some((scol, _)) => select.map(|v| (scol, v)),
76        };
77        old_cell != self.lead_cell
78    }
79
80    /// Select a column, row stays the same.
81    pub fn select_column(&mut self, select: Option<usize>) -> bool {
82        let old_cell = self.lead_cell;
83        self.lead_cell = match self.lead_cell {
84            None => select.map(|v| (v, 0)),
85            Some((_, srow)) => select.map(|v| (v, srow)),
86        };
87        old_cell != self.lead_cell
88    }
89
90    /// Select a cell, clamp between 0 and maximum.
91    pub fn move_to(&mut self, select: (usize, usize), maximum: (usize, usize)) -> bool {
92        let c = self.move_to_col(select.0, maximum.0);
93        let r = self.move_to_row(select.1, maximum.1);
94        c || r
95    }
96
97    /// Select a column. Row stays the same.
98    pub fn move_to_col(&mut self, col: usize, maximum: usize) -> bool {
99        let old = self.lead_cell;
100        let col = min(col, maximum);
101        self.lead_cell = self
102            .lead_cell
103            .map_or(Some((col, 0)), |(_, srow)| Some((col, srow)));
104        old != self.lead_cell
105    }
106
107    /// Select a row. Column stays the same.
108    pub fn move_to_row(&mut self, row: usize, maximum: usize) -> bool {
109        let old = self.lead_cell;
110        let row = min(row, maximum);
111        self.lead_cell = self
112            .lead_cell
113            .map_or(Some((0, row)), |(scol, _)| Some((scol, row)));
114        old != self.lead_cell
115    }
116
117    /// Select the next row, clamp between 0 and maximum.
118    pub fn move_down(&mut self, n: usize, maximum: usize) -> bool {
119        let old_cell = self.lead_cell;
120        self.lead_cell = match self.lead_cell {
121            None => Some((0, 0)),
122            Some((scol, srow)) => Some((scol, min(srow + n, maximum))),
123        };
124        old_cell != self.lead_cell
125    }
126
127    /// Select the previous row, clamp between 0 and maximum.
128    pub fn move_up(&mut self, n: usize, maximum: usize) -> bool {
129        let old_cell = self.lead_cell;
130        self.lead_cell = match self.lead_cell {
131            None => Some((0, maximum)),
132            Some((scol, srow)) => Some((scol, srow.saturating_sub(n))),
133        };
134        old_cell != self.lead_cell
135    }
136
137    /// Select the next column, clamp between 0 and maximum.
138    pub fn move_right(&mut self, n: usize, maximum: usize) -> bool {
139        let old_cell = self.lead_cell;
140        self.lead_cell = match self.lead_cell {
141            None => Some((0, 0)),
142            Some((scol, srow)) => Some((min(scol + n, maximum), srow)),
143        };
144        old_cell != self.lead_cell
145    }
146
147    /// Select the previous row, clamp between 0 and maximum.
148    pub fn move_left(&mut self, n: usize, maximum: usize) -> bool {
149        let old_cell = self.lead_cell;
150        self.lead_cell = match self.lead_cell {
151            None => Some((maximum, 0)),
152            Some((scol, srow)) => Some((scol.saturating_sub(n), srow)),
153        };
154        old_cell != self.lead_cell
155    }
156}
157
158impl HandleEvent<Event, Regular, TableOutcome> for TableState<CellSelection> {
159    fn handle(&mut self, event: &Event, _keymap: Regular) -> TableOutcome {
160        let res = if self.is_focused() {
161            match event {
162                ct_event!(keycode press Up) => {
163                    if self.move_up(1) {
164                        TableOutcome::Selected
165                    } else {
166                        TableOutcome::Unchanged
167                    }
168                }
169                ct_event!(keycode press Down) => {
170                    if self.move_down(1) {
171                        TableOutcome::Selected
172                    } else {
173                        TableOutcome::Unchanged
174                    }
175                }
176                ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press CONTROL-Home) => {
177                    if self.move_to_row(0) {
178                        TableOutcome::Selected
179                    } else {
180                        TableOutcome::Unchanged
181                    }
182                }
183                ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press CONTROL-End) => {
184                    if self.move_to_row(self.rows.saturating_sub(1)) {
185                        TableOutcome::Selected
186                    } else {
187                        TableOutcome::Unchanged
188                    }
189                }
190
191                ct_event!(keycode press PageUp) => {
192                    if self.move_up(max(1, self.page_len().saturating_sub(1))) {
193                        TableOutcome::Selected
194                    } else {
195                        TableOutcome::Unchanged
196                    }
197                }
198                ct_event!(keycode press PageDown) => {
199                    if self.move_down(max(1, self.page_len().saturating_sub(1))) {
200                        TableOutcome::Selected
201                    } else {
202                        TableOutcome::Unchanged
203                    }
204                }
205
206                ct_event!(keycode press Left) => {
207                    if self.move_left(1) {
208                        TableOutcome::Selected
209                    } else {
210                        TableOutcome::Unchanged
211                    }
212                }
213                ct_event!(keycode press Right) => {
214                    if self.move_right(1) {
215                        TableOutcome::Selected
216                    } else {
217                        TableOutcome::Unchanged
218                    }
219                }
220                ct_event!(keycode press CONTROL-Left) | ct_event!(keycode press Home) => {
221                    if self.move_to_col(0) {
222                        TableOutcome::Selected
223                    } else {
224                        TableOutcome::Unchanged
225                    }
226                }
227                ct_event!(keycode press CONTROL-Right) | ct_event!(keycode press End) => {
228                    if self.move_to_col(self.columns.saturating_sub(1)) {
229                        TableOutcome::Selected
230                    } else {
231                        TableOutcome::Unchanged
232                    }
233                }
234
235                _ => TableOutcome::Continue,
236            }
237        } else {
238            TableOutcome::Continue
239        };
240
241        if res == TableOutcome::Continue {
242            self.handle(event, MouseOnly)
243        } else {
244            res
245        }
246    }
247}
248
249impl HandleEvent<Event, MouseOnly, TableOutcome> for TableState<CellSelection> {
250    fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> TableOutcome {
251        let mut r = match event {
252            ct_event!(mouse any for m) if self.mouse.drag(self.table_area, m) => {
253                if self.move_to(self.cell_at_drag((m.column, m.row))) {
254                    TableOutcome::Selected
255                } else {
256                    TableOutcome::Unchanged
257                }
258            }
259            ct_event!(mouse down Left for column, row) => {
260                if self.area.contains((*column, *row).into()) {
261                    if let Some(new_cell) = self.cell_at_clicked((*column, *row)) {
262                        if self.move_to(new_cell) {
263                            TableOutcome::Selected
264                        } else {
265                            TableOutcome::Unchanged
266                        }
267                    } else {
268                        TableOutcome::Continue
269                    }
270                } else {
271                    TableOutcome::Continue
272                }
273            }
274            _ => TableOutcome::Continue,
275        };
276
277        r = r.or_else(|| {
278            let mut sas = ScrollAreaState::new()
279                .area(self.inner)
280                .h_scroll(&mut self.hscroll)
281                .v_scroll(&mut self.vscroll);
282            match sas.handle(event, MouseOnly) {
283                ScrollOutcome::Up(v) => {
284                    if self.scroll_up(v) {
285                        TableOutcome::Changed
286                    } else {
287                        TableOutcome::Unchanged
288                    }
289                }
290                ScrollOutcome::Down(v) => {
291                    if self.scroll_down(v) {
292                        TableOutcome::Changed
293                    } else {
294                        TableOutcome::Unchanged
295                    }
296                }
297                ScrollOutcome::VPos(v) => {
298                    if self.set_row_offset(self.vscroll.limited_offset(v)) {
299                        TableOutcome::Changed
300                    } else {
301                        TableOutcome::Unchanged
302                    }
303                }
304                ScrollOutcome::Left(v) => {
305                    if self.scroll_left(v) {
306                        TableOutcome::Changed
307                    } else {
308                        TableOutcome::Unchanged
309                    }
310                }
311                ScrollOutcome::Right(v) => {
312                    if self.scroll_right(v) {
313                        TableOutcome::Changed
314                    } else {
315                        TableOutcome::Unchanged
316                    }
317                }
318                ScrollOutcome::HPos(v) => {
319                    if self.set_x_offset(self.hscroll.limited_offset(v)) {
320                        TableOutcome::Changed
321                    } else {
322                        TableOutcome::Unchanged
323                    }
324                }
325                ScrollOutcome::Continue => TableOutcome::Continue,
326                ScrollOutcome::Unchanged => TableOutcome::Unchanged,
327                ScrollOutcome::Changed => TableOutcome::Changed,
328            }
329        });
330        r
331    }
332}
333
334/// Handle all events.
335/// Table events are only processed if focus is true.
336/// Mouse events are processed if they are in range.
337pub fn handle_events(
338    state: &mut TableState<CellSelection>,
339    focus: bool,
340    event: &Event,
341) -> TableOutcome {
342    state.focus.set(focus);
343    state.handle(event, Regular)
344}
345
346/// Handle only mouse-events.
347pub fn handle_mouse_events(state: &mut TableState<CellSelection>, event: &Event) -> TableOutcome {
348    state.handle(event, MouseOnly)
349}