Skip to main content

densa_widgets/grid/
selection.rs

1//! Selecting rows, one or a range, and copying them.
2
3use std::ops::Range;
4
5use densa_grid::{CellText, GridModel, Palette};
6use densa_ui::{ActionKind, EventCx};
7
8use super::DataGrid;
9
10impl<M: GridModel> DataGrid<M> {
11    /// The selected rows: one, the range from where a Shift selection started to the
12    /// [selected](Self::selected) row, or all of them; empty for none.
13    pub fn selected_rows(&self) -> Range<usize> {
14        let rows = self.model.rows();
15        if self.all {
16            return 0..rows;
17        }
18        let Some(focus) = self.selected() else {
19            return 0..0;
20        };
21        let anchor = self.anchor.map_or(focus, |a| a.row);
22        // Until the next layout or paint finds its records again, a row can be past the end.
23        let end = (anchor.max(focus) + 1).min(rows);
24        anchor.min(focus).min(end)..end
25    }
26
27    /// Selects the rows from `anchor` to `row` (either way round), `row` being the
28    /// [selected](Self::selected) one, and scrolls it into view. Rows past the last are
29    /// taken as the last.
30    pub fn select_rows(&mut self, cx: &mut EventCx<'_>, anchor: usize, row: usize) {
31        let Some(last) = self.model.rows().checked_sub(1) else {
32            return;
33        };
34        self.all = false;
35        self.anchor = None;
36        self.select(cx, Some(row.min(last)));
37        self.anchor = Some(self.selection_of(anchor.min(last)));
38    }
39
40    /// Selects every row, and those added later, until the selection changes. The
41    /// [selected](Self::selected) row stays as it was, and nothing scrolls.
42    pub fn select_all(&mut self, cx: &mut EventCx<'_>) {
43        self.all = true;
44        cx.request_paint();
45    }
46
47    /// Puts the selected rows on the clipboard, as [`selected_text`](Self::selected_text)
48    /// writes them; `false` if no row is selected or the clipboard would not take them.
49    ///
50    /// The text of every selected row is built at once, on the calling thread: a million
51    /// rows take a noticeable time and as much memory as their text.
52    pub fn copy(&mut self, cx: &mut EventCx<'_>) -> bool {
53        self.catch_up();
54        if self.selected_rows().is_empty() {
55            return false;
56        }
57        let text = self.selected_text(&super::palette(cx.theme()));
58        cx.clipboard().set_text(&text).is_ok()
59    }
60
61    /// The selected rows' cells as the grid shows them (with `palette`'s formatting, where
62    /// the model's text depends on it), a tab between cells and a line break between rows:
63    /// the shape spreadsheets paste into cells. Tabs and line breaks within a cell become
64    /// spaces.
65    pub fn selected_text(&mut self, palette: &Palette) -> String {
66        self.catch_up();
67        let columns = self.model.columns().len();
68        let mut out = String::new();
69        let mut cell = CellText::new();
70        for (index, row) in self.selected_rows().enumerate() {
71            if index > 0 {
72                out.push('\n');
73            }
74            for column in 0..columns {
75                if column > 0 {
76                    out.push('\t');
77                }
78                cell.clear();
79                self.model.cell(row, column, &mut cell, palette);
80                out.extend(cell.as_str().chars().map(|c| match c {
81                    '\t' | '\n' | '\r' => ' ',
82                    c => c,
83                }));
84            }
85        }
86        out
87    }
88
89    /// Takes in what the model changed since the last frame, as painting does.
90    fn catch_up(&mut self) {
91        self.model.prepare();
92        self.follow_model();
93        self.clamp();
94    }
95
96    /// Selects `row` alone (reporting it if it was not selected) and shows it.
97    pub(super) fn select_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
98        self.anchor = None;
99        self.all = false;
100        self.move_selection(cx, row);
101    }
102
103    /// Selects from where the selection started (the selected row, if it was one row) to
104    /// `row`: Shift with a click or a key.
105    pub(super) fn extend_to(&mut self, cx: &mut EventCx<'_>, row: usize) {
106        self.all = false;
107        if self.anchor.is_none() {
108            self.anchor = Some(self.selected.unwrap_or_else(|| self.selection_of(row)));
109        }
110        self.move_selection(cx, row);
111    }
112
113    /// Makes `row` the selected row, keeping the anchor, and shows it.
114    fn move_selection(&mut self, cx: &mut EventCx<'_>, row: usize) {
115        if self.selected() != Some(row) {
116            self.selected = Some(self.selection_of(row));
117            cx.emit(ActionKind::Selected(row));
118        }
119        self.reveal_row(row);
120        cx.request_paint();
121    }
122}