Skip to main content

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        if !self.has_mouse_focus() {
237            return TableOutcome::Continue;
238        }
239        
240        let mut r = match event {
241            ct_event!(mouse any for m) if self.mouse.drag(self.table_area, m) => {
242                if self.move_to(self.row_at_drag((m.column, m.row))) {
243                    TableOutcome::Selected
244                } else {
245                    TableOutcome::Unchanged
246                }
247            }
248            ct_event!(mouse down Left for column, row) => {
249                if self.table_area.contains((*column, *row).into()) {
250                    if let Some(new_row) = self.row_at_clicked((*column, *row)) {
251                        if self.move_to(new_row) {
252                            TableOutcome::Selected
253                        } else {
254                            TableOutcome::Unchanged
255                        }
256                    } else {
257                        TableOutcome::Continue
258                    }
259                } else {
260                    TableOutcome::Continue
261                }
262            }
263
264            _ => TableOutcome::Continue,
265        };
266
267        r = r.or_else(|| {
268            let mut sas = ScrollAreaState::new()
269                .area(self.inner)
270                .h_scroll(&mut self.hscroll)
271                .v_scroll(&mut self.vscroll);
272            match sas.handle(event, MouseOnly) {
273                ScrollOutcome::Up(v) => {
274                    if self.selection.scroll_selected() {
275                        if self.move_up(1) {
276                            TableOutcome::Selected
277                        } else {
278                            TableOutcome::Unchanged
279                        }
280                    } else {
281                        if self.scroll_up(v) {
282                            TableOutcome::Changed
283                        } else {
284                            TableOutcome::Unchanged
285                        }
286                    }
287                }
288                ScrollOutcome::Down(v) => {
289                    if self.selection.scroll_selected() {
290                        if self.move_down(1) {
291                            TableOutcome::Selected
292                        } else {
293                            TableOutcome::Unchanged
294                        }
295                    } else {
296                        if self.scroll_down(v) {
297                            TableOutcome::Changed
298                        } else {
299                            TableOutcome::Unchanged
300                        }
301                    }
302                }
303                ScrollOutcome::VPos(v) => {
304                    if self.selection.scroll_selected {
305                        if self.move_to(self.remap_offset_selection(v)) {
306                            TableOutcome::Selected
307                        } else {
308                            TableOutcome::Unchanged
309                        }
310                    } else {
311                        if self.set_row_offset(self.vscroll.limited_offset(v)) {
312                            TableOutcome::Changed
313                        } else {
314                            TableOutcome::Unchanged
315                        }
316                    }
317                }
318                ScrollOutcome::Left(v) => {
319                    if self.scroll_left(v) {
320                        TableOutcome::Changed
321                    } else {
322                        TableOutcome::Unchanged
323                    }
324                }
325                ScrollOutcome::Right(v) => {
326                    if self.scroll_right(v) {
327                        TableOutcome::Changed
328                    } else {
329                        TableOutcome::Unchanged
330                    }
331                }
332                ScrollOutcome::HPos(v) => {
333                    if self.set_x_offset(self.hscroll.limited_offset(v)) {
334                        TableOutcome::Changed
335                    } else {
336                        TableOutcome::Unchanged
337                    }
338                }
339
340                ScrollOutcome::Continue => TableOutcome::Continue,
341                ScrollOutcome::Unchanged => TableOutcome::Unchanged,
342                ScrollOutcome::Changed => TableOutcome::Changed,
343            }
344        });
345
346        r
347    }
348}
349
350/// Handle all events.
351/// Table events are only processed if focus is true.
352/// Mouse events are processed if they are in range.
353pub fn handle_events(
354    state: &mut TableState<RowSelection>,
355    focus: bool,
356    event: &Event,
357) -> TableOutcome {
358    state.focus.set(focus);
359    state.handle(event, Regular)
360}
361
362/// Handle only mouse-events.
363pub fn handle_mouse_events(state: &mut TableState<RowSelection>, event: &Event) -> TableOutcome {
364    state.handle(event, MouseOnly)
365}