rat_ftable/
rowsetselection.rs

1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, flow};
4use rat_focus::HasFocus;
5use rat_scrolled::ScrollAreaState;
6use rat_scrolled::event::ScrollOutcome;
7use ratatui_crossterm::crossterm::event::{Event, KeyModifiers};
8use std::cmp::{max, min};
9use std::collections::HashSet;
10use std::mem;
11
12/// Allows selection an active range of rows.
13///
14/// The current range can be retired to a set of selected rows,
15/// and a new range be started. This allows multiple interval
16/// selection and deselection of certain rows.
17///
18/// This one only supports row-selection.
19#[derive(Debug, Default, Clone)]
20pub struct RowSetSelection {
21    /// Start of the active selection.
22    pub anchor_row: Option<usize>,
23    /// Current end of the active selection.
24    pub lead_row: Option<usize>,
25    /// Retired rows. This doesn't contain the rows
26    /// between anchor and lead.
27    ///
28    /// You can call [RowSetSelection::retire_selection] to
29    /// add the anchor-lead range. This resets anchor and lead though.
30    /// Or iterate the complete range and call [RowSetSelection::is_selected_row].
31    pub selected: HashSet<usize>,
32}
33
34impl TableSelection for RowSetSelection {
35    fn count(&self) -> usize {
36        let n = if let Some(anchor) = self.anchor_row {
37            if let Some(lead) = self.lead_row {
38                lead.abs_diff(anchor) + 1
39            } else {
40                0
41            }
42        } else {
43            0
44        };
45
46        n + self.selected.len()
47    }
48
49    #[allow(clippy::collapsible_else_if)]
50    #[allow(clippy::collapsible_if)]
51    fn is_selected_row(&self, row: usize) -> bool {
52        if let Some(mut anchor) = self.anchor_row {
53            if let Some(mut lead) = self.lead_row {
54                if lead < anchor {
55                    mem::swap(&mut lead, &mut anchor);
56                }
57                if row >= anchor && row <= lead {
58                    return true;
59                }
60            }
61        } else {
62            if let Some(lead) = self.lead_row {
63                if row == lead {
64                    return true;
65                }
66            }
67        }
68
69        self.selected.contains(&row)
70    }
71
72    fn is_selected_column(&self, _column: usize) -> bool {
73        false
74    }
75
76    fn is_selected_cell(&self, _column: usize, _row: usize) -> bool {
77        false
78    }
79
80    fn lead_selection(&self) -> Option<(usize, usize)> {
81        self.lead_row.map(|srow| (0, srow))
82    }
83
84    fn validate_rows(&mut self, rows: usize) {
85        if let Some(lead_row) = self.lead_row {
86            if rows == 0 {
87                self.lead_row = None;
88            } else if lead_row >= rows {
89                self.lead_row = Some(rows - 1);
90            }
91        }
92        if let Some(anchor_row) = self.anchor_row {
93            if rows == 0 {
94                self.anchor_row = None;
95            } else if anchor_row >= rows {
96                self.anchor_row = Some(rows - 1);
97            }
98        }
99        self.selected.retain(|v| *v < rows);
100    }
101
102    fn validate_cols(&mut self, _cols: usize) {}
103
104    fn items_added(&mut self, pos: usize, n: usize) {
105        if let Some(lead_row) = self.lead_row {
106            if lead_row > pos {
107                self.lead_row = Some(lead_row + n);
108            }
109        }
110        if let Some(anchor_row) = self.anchor_row {
111            if anchor_row > pos {
112                self.anchor_row = Some(anchor_row + n);
113            }
114        }
115        let corr = self.selected.extract_if(|v| *v > pos).collect::<Vec<_>>();
116        for v in corr {
117            self.selected.insert(v + n);
118        }
119    }
120
121    fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {
122        if let Some(lead_row) = self.lead_row {
123            if rows == 0 {
124                self.lead_row = None;
125            } else if lead_row == pos && lead_row + n >= rows {
126                self.lead_row = Some(rows.saturating_sub(1))
127            } else if lead_row > pos {
128                self.lead_row = Some(lead_row.saturating_sub(n).min(pos));
129            }
130        }
131        if let Some(anchor_row) = self.anchor_row {
132            if rows == 0 {
133                self.anchor_row = None;
134            } else if anchor_row == pos && anchor_row + n >= rows {
135                self.anchor_row = Some(rows.saturating_sub(1))
136            } else if anchor_row > pos {
137                self.anchor_row = Some(anchor_row.saturating_sub(n).min(pos));
138            }
139        }
140        let corr = self.selected.extract_if(|v| *v > pos).collect::<Vec<_>>();
141        for v in corr {
142            if rows == 0 {
143                // removed
144            } else if v == pos && v + n >= rows {
145                self.selected.insert(rows.saturating_sub(1));
146            } else if v > pos {
147                self.selected.insert(v.saturating_sub(n).min(pos));
148            }
149        }
150    }
151}
152
153impl RowSetSelection {
154    /// New selection.
155    pub fn new() -> RowSetSelection {
156        RowSetSelection {
157            anchor_row: None,
158            lead_row: None,
159            selected: HashSet::new(),
160        }
161    }
162
163    /// Clear the selection.
164    pub fn clear(&mut self) {
165        self.anchor_row = None;
166        self.lead_row = None;
167        self.selected.clear();
168    }
169
170    /// Current lead.
171    pub fn lead(&self) -> Option<usize> {
172        self.lead_row
173    }
174
175    /// Current anchor.
176    pub fn anchor(&self) -> Option<usize> {
177        self.anchor_row
178    }
179
180    /// Set of all selected rows. Clones the retired set and adds the current anchor..lead range.
181    pub fn selected(&self) -> HashSet<usize> {
182        let mut selected = self.selected.clone();
183        Self::fill(self.anchor_row, self.lead_row, &mut selected);
184        selected
185    }
186
187    /// Has some selection.
188    pub fn has_selection(&self) -> bool {
189        self.lead_row.is_some() || !self.selected.is_empty()
190    }
191
192    /// Set a new lead. Maybe extend the range.
193    pub fn set_lead(&mut self, lead: Option<usize>, extend: bool) -> bool {
194        let old_selection = (self.anchor_row, self.lead_row);
195        self.extend(extend);
196        self.lead_row = lead;
197        old_selection != (self.anchor_row, self.lead_row)
198    }
199
200    /// Transfers the range anchor to lead to the selection set and reset both.
201    pub fn retire_selection(&mut self) {
202        Self::fill(self.anchor_row, self.lead_row, &mut self.selected);
203        self.anchor_row = None;
204        self.lead_row = None;
205    }
206
207    /// Add to selection. Only works for retired selections, not for the
208    /// active anchor-lead range.
209    pub fn add(&mut self, idx: usize) {
210        self.selected.insert(idx);
211    }
212
213    /// Remove from selection. Only works for retired selections, not for the
214    /// active anchor-lead range.
215    pub fn remove(&mut self, idx: usize) {
216        self.selected.remove(&idx);
217    }
218
219    /// Set a new lead, at the same time limit the lead to max.
220    pub fn move_to(&mut self, lead: usize, max: usize, extend: bool) -> bool {
221        let old_selection = (self.anchor_row, self.lead_row);
222        self.extend(extend);
223        if lead <= max {
224            self.lead_row = Some(lead);
225        } else {
226            self.lead_row = Some(max);
227        }
228        old_selection != (self.anchor_row, self.lead_row)
229    }
230
231    /// Select next. Maybe extend the range.
232    pub fn move_down(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
233        let old_selection = (self.anchor_row, self.lead_row);
234        self.extend(extend);
235        self.lead_row = Some(self.lead_row.map_or(0, |v| min(v + n, maximum)));
236        old_selection != (self.anchor_row, self.lead_row)
237    }
238
239    /// Select next. Maybe extend the range.
240    pub fn move_up(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
241        let old_selection = (self.anchor_row, self.lead_row);
242        self.extend(extend);
243        self.lead_row = Some(self.lead_row.map_or(maximum, |v| v.saturating_sub(n)));
244        old_selection != (self.anchor_row, self.lead_row)
245    }
246
247    fn extend(&mut self, extend: bool) {
248        if extend {
249            if self.anchor_row.is_none() {
250                self.anchor_row = self.lead_row;
251            }
252        } else {
253            self.anchor_row = None;
254            self.selected.clear();
255        }
256    }
257
258    #[allow(clippy::collapsible_else_if)]
259    fn fill(anchor: Option<usize>, lead: Option<usize>, selection: &mut HashSet<usize>) {
260        if let Some(mut anchor) = anchor {
261            if let Some(mut lead) = lead {
262                if lead < anchor {
263                    mem::swap(&mut lead, &mut anchor);
264                }
265
266                for n in anchor..=lead {
267                    selection.insert(n);
268                }
269            }
270        } else {
271            if let Some(lead) = lead {
272                selection.insert(lead);
273            }
274        }
275    }
276}
277
278impl HandleEvent<Event, Regular, TableOutcome> for TableState<RowSetSelection> {
279    fn handle(&mut self, event: &Event, _: Regular) -> TableOutcome {
280        let res = if self.is_focused() {
281            match event {
282                ct_event!(keycode press Up) => {
283                    if self.move_up(1, false) {
284                        TableOutcome::Selected
285                    } else {
286                        TableOutcome::Unchanged
287                    }
288                }
289                ct_event!(keycode press Down) => {
290                    if self.move_down(1, false) {
291                        TableOutcome::Selected
292                    } else {
293                        TableOutcome::Unchanged
294                    }
295                }
296                ct_event!(keycode press CONTROL-Up)
297                | ct_event!(keycode press CONTROL-Home)
298                | ct_event!(keycode press Home) => {
299                    if self.move_to(0, false) {
300                        TableOutcome::Selected
301                    } else {
302                        TableOutcome::Unchanged
303                    }
304                }
305                ct_event!(keycode press CONTROL-Down)
306                | ct_event!(keycode press CONTROL-End)
307                | ct_event!(keycode press End) => {
308                    if self.move_to(self.rows.saturating_sub(1), false) {
309                        TableOutcome::Selected
310                    } else {
311                        TableOutcome::Unchanged
312                    }
313                }
314                ct_event!(keycode press PageUp) => {
315                    if self.move_up(max(1, self.page_len().saturating_sub(1)), false) {
316                        TableOutcome::Selected
317                    } else {
318                        TableOutcome::Unchanged
319                    }
320                }
321                ct_event!(keycode press PageDown) => {
322                    if self.move_down(max(1, self.page_len().saturating_sub(1)), false) {
323                        TableOutcome::Selected
324                    } else {
325                        TableOutcome::Unchanged
326                    }
327                }
328                ct_event!(keycode press SHIFT-Up) => {
329                    if self.move_up(1, true) {
330                        TableOutcome::Selected
331                    } else {
332                        TableOutcome::Unchanged
333                    }
334                }
335                ct_event!(keycode press SHIFT-Down) => {
336                    if self.move_down(1, true) {
337                        TableOutcome::Selected
338                    } else {
339                        TableOutcome::Unchanged
340                    }
341                }
342                ct_event!(keycode press CONTROL_SHIFT-Up)
343                | ct_event!(keycode press CONTROL_SHIFT-Home)
344                | ct_event!(keycode press SHIFT-Home) => {
345                    if self.move_to(0, true) {
346                        TableOutcome::Selected
347                    } else {
348                        TableOutcome::Unchanged
349                    }
350                }
351                ct_event!(keycode press CONTROL_SHIFT-Down)
352                | ct_event!(keycode press CONTROL_SHIFT-End)
353                | ct_event!(keycode press SHIFT-End) => {
354                    if self.move_to(self.rows.saturating_sub(1), true) {
355                        TableOutcome::Selected
356                    } else {
357                        TableOutcome::Unchanged
358                    }
359                }
360                ct_event!(keycode press SHIFT-PageUp) => {
361                    if self.move_up(max(1, self.page_len().saturating_sub(1)), true) {
362                        TableOutcome::Selected
363                    } else {
364                        TableOutcome::Unchanged
365                    }
366                }
367                ct_event!(keycode press SHIFT-PageDown) => {
368                    if self.move_down(max(1, self.page_len().saturating_sub(1)), true) {
369                        TableOutcome::Selected
370                    } else {
371                        TableOutcome::Unchanged
372                    }
373                }
374                ct_event!(keycode press Left) => {
375                    if self.scroll_left(1) {
376                        TableOutcome::Changed
377                    } else {
378                        TableOutcome::Unchanged
379                    }
380                }
381                ct_event!(keycode press Right) => {
382                    if self.scroll_right(1) {
383                        TableOutcome::Changed
384                    } else {
385                        TableOutcome::Unchanged
386                    }
387                }
388                ct_event!(keycode press CONTROL-Left) => {
389                    if self.scroll_to_x(0) {
390                        TableOutcome::Changed
391                    } else {
392                        TableOutcome::Unchanged
393                    }
394                }
395                ct_event!(keycode press CONTROL-Right) => {
396                    if self.scroll_to_x(self.x_max_offset()) {
397                        TableOutcome::Changed
398                    } else {
399                        TableOutcome::Unchanged
400                    }
401                }
402                _ => TableOutcome::Continue,
403            }
404        } else {
405            TableOutcome::Continue
406        };
407
408        if res == TableOutcome::Continue {
409            self.handle(event, MouseOnly)
410        } else {
411            res
412        }
413    }
414}
415
416impl HandleEvent<Event, MouseOnly, TableOutcome> for TableState<RowSetSelection> {
417    fn handle(&mut self, event: &Event, _: MouseOnly) -> TableOutcome {
418        flow!(match event {
419            ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
420                if self.mouse.drag(self.table_area, m)
421                    || self.mouse.drag2(self.table_area, m, KeyModifiers::CONTROL) =>
422            {
423                if self.move_to(self.row_at_drag((m.column, m.row)), true) {
424                    TableOutcome::Selected
425                } else {
426                    TableOutcome::Unchanged
427                }
428            }
429            ct_event!(mouse down Left for column, row) => {
430                let pos = (*column, *row);
431                if self.table_area.contains(pos.into()) {
432                    if let Some(new_row) = self.row_at_clicked(pos) {
433                        if self.move_to(new_row, false) {
434                            TableOutcome::Selected
435                        } else {
436                            TableOutcome::Unchanged
437                        }
438                    } else {
439                        TableOutcome::Continue
440                    }
441                } else {
442                    TableOutcome::Continue
443                }
444            }
445            ct_event!(mouse down ALT-Left for column, row) => {
446                let pos = (*column, *row);
447                if self.area.contains(pos.into()) {
448                    if let Some(new_row) = self.row_at_clicked(pos) {
449                        if self.move_to(new_row, true) {
450                            TableOutcome::Selected
451                        } else {
452                            TableOutcome::Unchanged
453                        }
454                    } else {
455                        TableOutcome::Continue
456                    }
457                } else {
458                    TableOutcome::Continue
459                }
460            }
461            ct_event!(mouse down CONTROL-Left for column, row) => {
462                let pos = (*column, *row);
463                if self.area.contains(pos.into()) {
464                    if let Some(new_row) = self.row_at_clicked(pos) {
465                        self.retire_selection();
466                        if self.selection.is_selected_row(new_row) {
467                            self.selection.remove(new_row);
468                        } else {
469                            self.move_to(new_row, true);
470                        }
471                        TableOutcome::Selected
472                    } else {
473                        TableOutcome::Continue
474                    }
475                } else {
476                    TableOutcome::Continue
477                }
478            }
479            _ => TableOutcome::Continue,
480        });
481
482        let mut sas = ScrollAreaState::new()
483            .area(self.inner)
484            .h_scroll(&mut self.hscroll)
485            .v_scroll(&mut self.vscroll);
486
487        match sas.handle(event, MouseOnly) {
488            ScrollOutcome::Up(v) => {
489                if self.scroll_up(v) {
490                    TableOutcome::Changed
491                } else {
492                    TableOutcome::Unchanged
493                }
494            }
495            ScrollOutcome::Down(v) => {
496                if self.scroll_down(v) {
497                    TableOutcome::Changed
498                } else {
499                    TableOutcome::Unchanged
500                }
501            }
502            ScrollOutcome::VPos(v) => {
503                if self.set_row_offset(self.vscroll.limited_offset(v)) {
504                    TableOutcome::Changed
505                } else {
506                    TableOutcome::Unchanged
507                }
508            }
509            ScrollOutcome::Left(v) => {
510                if self.scroll_left(v) {
511                    TableOutcome::Changed
512                } else {
513                    TableOutcome::Unchanged
514                }
515            }
516            ScrollOutcome::Right(v) => {
517                if self.scroll_right(v) {
518                    TableOutcome::Changed
519                } else {
520                    TableOutcome::Unchanged
521                }
522            }
523            ScrollOutcome::HPos(v) => {
524                if self.set_x_offset(self.hscroll.limited_offset(v)) {
525                    TableOutcome::Changed
526                } else {
527                    TableOutcome::Unchanged
528                }
529            }
530
531            ScrollOutcome::Continue => TableOutcome::Continue,
532            ScrollOutcome::Unchanged => TableOutcome::Unchanged,
533            ScrollOutcome::Changed => TableOutcome::Changed,
534        }
535    }
536}
537
538/// Handle all events.
539/// Table events are only processed if focus is true.
540/// Mouse events are processed if they are in range.
541pub fn handle_events(
542    state: &mut TableState<RowSetSelection>,
543    focus: bool,
544    event: &Event,
545) -> TableOutcome {
546    state.focus.set(focus);
547    state.handle(event, Regular)
548}
549
550/// Handle only mouse-events.
551pub fn handle_mouse_events(state: &mut TableState<RowSetSelection>, event: &Event) -> TableOutcome {
552    state.handle(event, MouseOnly)
553}