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