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