Skip to main content

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