rat_ftable/
rowselection.rs

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