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