Skip to main content

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        if !self.has_mouse_focus() {
292            return TableOutcome::Continue;
293        }
294        
295        let mut r = match event {
296            ct_event!(mouse any for m) if self.mouse.drag(self.table_area, m) => {
297                if self.move_to(self.cell_at_drag((m.column, m.row))) {
298                    TableOutcome::Selected
299                } else {
300                    TableOutcome::Unchanged
301                }
302            }
303            ct_event!(mouse down Left for column, row) => {
304                if self.area.contains((*column, *row).into()) {
305                    if let Some(new_cell) = self.cell_at_clicked((*column, *row)) {
306                        if self.move_to(new_cell) {
307                            TableOutcome::Selected
308                        } else {
309                            TableOutcome::Unchanged
310                        }
311                    } else {
312                        TableOutcome::Continue
313                    }
314                } else {
315                    TableOutcome::Continue
316                }
317            }
318            _ => TableOutcome::Continue,
319        };
320
321        r = r.or_else(|| {
322            let mut sas = ScrollAreaState::new()
323                .area(self.inner)
324                .h_scroll(&mut self.hscroll)
325                .v_scroll(&mut self.vscroll);
326            match sas.handle(event, MouseOnly) {
327                ScrollOutcome::Up(v) => {
328                    if self.scroll_up(v) {
329                        TableOutcome::Changed
330                    } else {
331                        TableOutcome::Unchanged
332                    }
333                }
334                ScrollOutcome::Down(v) => {
335                    if self.scroll_down(v) {
336                        TableOutcome::Changed
337                    } else {
338                        TableOutcome::Unchanged
339                    }
340                }
341                ScrollOutcome::VPos(v) => {
342                    if self.set_row_offset(self.vscroll.limited_offset(v)) {
343                        TableOutcome::Changed
344                    } else {
345                        TableOutcome::Unchanged
346                    }
347                }
348                ScrollOutcome::Left(v) => {
349                    if self.scroll_left(v) {
350                        TableOutcome::Changed
351                    } else {
352                        TableOutcome::Unchanged
353                    }
354                }
355                ScrollOutcome::Right(v) => {
356                    if self.scroll_right(v) {
357                        TableOutcome::Changed
358                    } else {
359                        TableOutcome::Unchanged
360                    }
361                }
362                ScrollOutcome::HPos(v) => {
363                    if self.set_x_offset(self.hscroll.limited_offset(v)) {
364                        TableOutcome::Changed
365                    } else {
366                        TableOutcome::Unchanged
367                    }
368                }
369                ScrollOutcome::Continue => TableOutcome::Continue,
370                ScrollOutcome::Unchanged => TableOutcome::Unchanged,
371                ScrollOutcome::Changed => TableOutcome::Changed,
372            }
373        });
374        r
375    }
376}
377
378/// Handle all events.
379/// Table events are only processed if focus is true.
380/// Mouse events are processed if they are in range.
381pub fn handle_events(
382    state: &mut TableState<CellSelection>,
383    focus: bool,
384    event: &Event,
385) -> TableOutcome {
386    state.focus.set(focus);
387    state.handle(event, Regular)
388}
389
390/// Handle only mouse-events.
391pub fn handle_mouse_events(state: &mut TableState<CellSelection>, event: &Event) -> TableOutcome {
392    state.handle(event, MouseOnly)
393}