rat_ftable/
rowsetselection.rs

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