rat_ftable/
rowsetselection.rs

1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use crossterm::event::KeyModifiers;
4use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, flow};
5use rat_focus::HasFocus;
6use rat_scrolled::ScrollAreaState;
7use rat_scrolled::event::ScrollOutcome;
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
85impl RowSetSelection {
86    /// New selection.
87    pub fn new() -> RowSetSelection {
88        RowSetSelection {
89            anchor_row: None,
90            lead_row: None,
91            selected: HashSet::new(),
92        }
93    }
94
95    /// Clear the selection.
96    pub fn clear(&mut self) {
97        self.anchor_row = None;
98        self.lead_row = None;
99        self.selected.clear();
100    }
101
102    /// Current lead.
103    pub fn lead(&self) -> Option<usize> {
104        self.lead_row
105    }
106
107    /// Current anchor.
108    pub fn anchor(&self) -> Option<usize> {
109        self.anchor_row
110    }
111
112    /// Set of all selected rows. Clones the retired set and adds the current anchor..lead range.
113    pub fn selected(&self) -> HashSet<usize> {
114        let mut selected = self.selected.clone();
115        Self::fill(self.anchor_row, self.lead_row, &mut selected);
116        selected
117    }
118
119    /// Has some selection.
120    pub fn has_selection(&self) -> bool {
121        self.lead_row.is_some() || !self.selected.is_empty()
122    }
123
124    /// Set a new lead. Maybe extend the range.
125    pub fn set_lead(&mut self, lead: Option<usize>, extend: bool) -> bool {
126        let old_selection = (self.anchor_row, self.lead_row);
127        self.extend(extend);
128        self.lead_row = lead;
129        old_selection != (self.anchor_row, self.lead_row)
130    }
131
132    /// Transfers the range anchor to lead to the selection set and reset both.
133    pub fn retire_selection(&mut self) {
134        Self::fill(self.anchor_row, self.lead_row, &mut self.selected);
135        self.anchor_row = None;
136        self.lead_row = None;
137    }
138
139    /// Add to selection. Only works for retired selections, not for the
140    /// active anchor-lead range.
141    pub fn add(&mut self, idx: usize) {
142        self.selected.insert(idx);
143    }
144
145    /// Remove from selection. Only works for retired selections, not for the
146    /// active anchor-lead range.
147    pub fn remove(&mut self, idx: usize) {
148        self.selected.remove(&idx);
149    }
150
151    /// Set a new lead, at the same time limit the lead to max.
152    pub fn move_to(&mut self, lead: usize, max: usize, extend: bool) -> bool {
153        let old_selection = (self.anchor_row, self.lead_row);
154        self.extend(extend);
155        if lead <= max {
156            self.lead_row = Some(lead);
157        } else {
158            self.lead_row = Some(max);
159        }
160        old_selection != (self.anchor_row, self.lead_row)
161    }
162
163    /// Select next. Maybe extend the range.
164    pub fn move_down(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
165        let old_selection = (self.anchor_row, self.lead_row);
166        self.extend(extend);
167        self.lead_row = Some(self.lead_row.map_or(0, |v| min(v + n, maximum)));
168        old_selection != (self.anchor_row, self.lead_row)
169    }
170
171    /// Select next. Maybe extend the range.
172    pub fn move_up(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
173        let old_selection = (self.anchor_row, self.lead_row);
174        self.extend(extend);
175        self.lead_row = Some(self.lead_row.map_or(maximum, |v| v.saturating_sub(n)));
176        old_selection != (self.anchor_row, self.lead_row)
177    }
178
179    fn extend(&mut self, extend: bool) {
180        if extend {
181            if self.anchor_row.is_none() {
182                self.anchor_row = self.lead_row;
183            }
184        } else {
185            self.anchor_row = None;
186            self.selected.clear();
187        }
188    }
189
190    #[allow(clippy::collapsible_else_if)]
191    fn fill(anchor: Option<usize>, lead: Option<usize>, selection: &mut HashSet<usize>) {
192        if let Some(mut anchor) = anchor {
193            if let Some(mut lead) = lead {
194                if lead < anchor {
195                    mem::swap(&mut lead, &mut anchor);
196                }
197
198                for n in anchor..=lead {
199                    selection.insert(n);
200                }
201            }
202        } else {
203            if let Some(lead) = lead {
204                selection.insert(lead);
205            }
206        }
207    }
208}
209
210impl HandleEvent<crossterm::event::Event, Regular, TableOutcome> for TableState<RowSetSelection> {
211    fn handle(&mut self, event: &crossterm::event::Event, _: Regular) -> TableOutcome {
212        let res = if self.is_focused() {
213            match event {
214                ct_event!(keycode press Up) => {
215                    if self.move_up(1, false) {
216                        TableOutcome::Selected
217                    } else {
218                        TableOutcome::Unchanged
219                    }
220                }
221                ct_event!(keycode press Down) => {
222                    if self.move_down(1, false) {
223                        TableOutcome::Selected
224                    } else {
225                        TableOutcome::Unchanged
226                    }
227                }
228                ct_event!(keycode press CONTROL-Up)
229                | ct_event!(keycode press CONTROL-Home)
230                | ct_event!(keycode press Home) => {
231                    if self.move_to(0, false) {
232                        TableOutcome::Selected
233                    } else {
234                        TableOutcome::Unchanged
235                    }
236                }
237                ct_event!(keycode press CONTROL-Down)
238                | ct_event!(keycode press CONTROL-End)
239                | ct_event!(keycode press End) => {
240                    if self.move_to(self.rows.saturating_sub(1), false) {
241                        TableOutcome::Selected
242                    } else {
243                        TableOutcome::Unchanged
244                    }
245                }
246                ct_event!(keycode press PageUp) => {
247                    if self.move_up(max(1, self.page_len().saturating_sub(1)), false) {
248                        TableOutcome::Selected
249                    } else {
250                        TableOutcome::Unchanged
251                    }
252                }
253                ct_event!(keycode press PageDown) => {
254                    if self.move_down(max(1, self.page_len().saturating_sub(1)), false) {
255                        TableOutcome::Selected
256                    } else {
257                        TableOutcome::Unchanged
258                    }
259                }
260                ct_event!(keycode press SHIFT-Up) => {
261                    if self.move_up(1, true) {
262                        TableOutcome::Selected
263                    } else {
264                        TableOutcome::Unchanged
265                    }
266                }
267                ct_event!(keycode press SHIFT-Down) => {
268                    if self.move_down(1, true) {
269                        TableOutcome::Selected
270                    } else {
271                        TableOutcome::Unchanged
272                    }
273                }
274                ct_event!(keycode press CONTROL_SHIFT-Up)
275                | ct_event!(keycode press CONTROL_SHIFT-Home)
276                | ct_event!(keycode press SHIFT-Home) => {
277                    if self.move_to(0, true) {
278                        TableOutcome::Selected
279                    } else {
280                        TableOutcome::Unchanged
281                    }
282                }
283                ct_event!(keycode press CONTROL_SHIFT-Down)
284                | ct_event!(keycode press CONTROL_SHIFT-End)
285                | ct_event!(keycode press SHIFT-End) => {
286                    if self.move_to(self.rows.saturating_sub(1), true) {
287                        TableOutcome::Selected
288                    } else {
289                        TableOutcome::Unchanged
290                    }
291                }
292                ct_event!(keycode press SHIFT-PageUp) => {
293                    if self.move_up(max(1, self.page_len().saturating_sub(1)), true) {
294                        TableOutcome::Selected
295                    } else {
296                        TableOutcome::Unchanged
297                    }
298                }
299                ct_event!(keycode press SHIFT-PageDown) => {
300                    if self.move_down(max(1, self.page_len().saturating_sub(1)), true) {
301                        TableOutcome::Selected
302                    } else {
303                        TableOutcome::Unchanged
304                    }
305                }
306                ct_event!(keycode press Left) => {
307                    if self.scroll_left(1) {
308                        TableOutcome::Changed
309                    } else {
310                        TableOutcome::Unchanged
311                    }
312                }
313                ct_event!(keycode press Right) => {
314                    if self.scroll_right(1) {
315                        TableOutcome::Changed
316                    } else {
317                        TableOutcome::Unchanged
318                    }
319                }
320                ct_event!(keycode press CONTROL-Left) => {
321                    if self.scroll_to_x(0) {
322                        TableOutcome::Changed
323                    } else {
324                        TableOutcome::Unchanged
325                    }
326                }
327                ct_event!(keycode press CONTROL-Right) => {
328                    if self.scroll_to_x(self.x_max_offset()) {
329                        TableOutcome::Changed
330                    } else {
331                        TableOutcome::Unchanged
332                    }
333                }
334                _ => TableOutcome::Continue,
335            }
336        } else {
337            TableOutcome::Continue
338        };
339
340        if res == TableOutcome::Continue {
341            self.handle(event, MouseOnly)
342        } else {
343            res
344        }
345    }
346}
347
348impl HandleEvent<crossterm::event::Event, MouseOnly, TableOutcome> for TableState<RowSetSelection> {
349    fn handle(&mut self, event: &crossterm::event::Event, _: MouseOnly) -> TableOutcome {
350        flow!(match event {
351            ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
352                if self.mouse.drag(self.table_area, m)
353                    || self.mouse.drag2(self.table_area, m, KeyModifiers::CONTROL) =>
354            {
355                if self.move_to(self.row_at_drag((m.column, m.row)), true) {
356                    TableOutcome::Selected
357                } else {
358                    TableOutcome::Unchanged
359                }
360            }
361            ct_event!(mouse down Left for column, row) => {
362                let pos = (*column, *row);
363                if self.table_area.contains(pos.into()) {
364                    if let Some(new_row) = self.row_at_clicked(pos) {
365                        if self.move_to(new_row, false) {
366                            TableOutcome::Selected
367                        } else {
368                            TableOutcome::Unchanged
369                        }
370                    } else {
371                        TableOutcome::Continue
372                    }
373                } else {
374                    TableOutcome::Continue
375                }
376            }
377            ct_event!(mouse down ALT-Left for column, row) => {
378                let pos = (*column, *row);
379                if self.area.contains(pos.into()) {
380                    if let Some(new_row) = self.row_at_clicked(pos) {
381                        if self.move_to(new_row, true) {
382                            TableOutcome::Selected
383                        } else {
384                            TableOutcome::Unchanged
385                        }
386                    } else {
387                        TableOutcome::Continue
388                    }
389                } else {
390                    TableOutcome::Continue
391                }
392            }
393            ct_event!(mouse down CONTROL-Left for column, row) => {
394                let pos = (*column, *row);
395                if self.area.contains(pos.into()) {
396                    if let Some(new_row) = self.row_at_clicked(pos) {
397                        self.retire_selection();
398                        if self.selection.is_selected_row(new_row) {
399                            self.selection.remove(new_row);
400                        } else {
401                            self.move_to(new_row, true);
402                        }
403                        TableOutcome::Selected
404                    } else {
405                        TableOutcome::Continue
406                    }
407                } else {
408                    TableOutcome::Continue
409                }
410            }
411            _ => TableOutcome::Continue,
412        });
413
414        let mut sas = ScrollAreaState::new()
415            .area(self.inner)
416            .h_scroll(&mut self.hscroll)
417            .v_scroll(&mut self.vscroll);
418
419        match sas.handle(event, MouseOnly) {
420            ScrollOutcome::Up(v) => {
421                if self.scroll_up(v) {
422                    TableOutcome::Changed
423                } else {
424                    TableOutcome::Unchanged
425                }
426            }
427            ScrollOutcome::Down(v) => {
428                if self.scroll_down(v) {
429                    TableOutcome::Changed
430                } else {
431                    TableOutcome::Unchanged
432                }
433            }
434            ScrollOutcome::VPos(v) => {
435                if self.set_row_offset(self.vscroll.limited_offset(v)) {
436                    TableOutcome::Changed
437                } else {
438                    TableOutcome::Unchanged
439                }
440            }
441            ScrollOutcome::Left(v) => {
442                if self.scroll_left(v) {
443                    TableOutcome::Changed
444                } else {
445                    TableOutcome::Unchanged
446                }
447            }
448            ScrollOutcome::Right(v) => {
449                if self.scroll_right(v) {
450                    TableOutcome::Changed
451                } else {
452                    TableOutcome::Unchanged
453                }
454            }
455            ScrollOutcome::HPos(v) => {
456                if self.set_x_offset(self.hscroll.limited_offset(v)) {
457                    TableOutcome::Changed
458                } else {
459                    TableOutcome::Unchanged
460                }
461            }
462
463            ScrollOutcome::Continue => TableOutcome::Continue,
464            ScrollOutcome::Unchanged => TableOutcome::Unchanged,
465            ScrollOutcome::Changed => TableOutcome::Changed,
466        }
467    }
468}
469
470/// Handle all events.
471/// Table events are only processed if focus is true.
472/// Mouse events are processed if they are in range.
473pub fn handle_events(
474    state: &mut TableState<RowSetSelection>,
475    focus: bool,
476    event: &crossterm::event::Event,
477) -> TableOutcome {
478    state.focus.set(focus);
479    state.handle(event, Regular)
480}
481
482/// Handle only mouse-events.
483pub fn handle_mouse_events(
484    state: &mut TableState<RowSetSelection>,
485    event: &crossterm::event::Event,
486) -> TableOutcome {
487    state.handle(event, MouseOnly)
488}