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`, leaving the clipboard as it was, if no row is selected, the model
49    /// offers none of them to copy ([`GridModel::copy_rows`]), or the clipboard would not take
50    /// them.
51    ///
52    /// The text of every selected row the model offers (all of them, unless it fetches its
53    /// rows) is built at once, on the calling thread: a million rows take a noticeable time and
54    /// as much memory as their text.
55    pub fn copy(&mut self, cx: &mut EventCx<'_>) -> bool {
56        self.catch_up();
57        let runs = self.copied_runs();
58        if runs.is_empty() {
59            return false;
60        }
61        let text = self.text_of(runs, &super::palette(cx.theme()));
62        cx.clipboard().set_text(&text).is_ok()
63    }
64
65    /// The selected rows' cells as the grid shows them (with `palette`'s formatting, where
66    /// the model's text depends on it), a tab between cells and a line break between rows:
67    /// the shape spreadsheets paste into cells. Tabs and line breaks within a cell become
68    /// spaces. Only the rows the model offers to copy are written
69    /// ([`GridModel::copy_rows`]).
70    pub fn selected_text(&mut self, palette: &Palette) -> String {
71        self.catch_up();
72        let runs = self.copied_runs();
73        self.text_of(runs, palette)
74    }
75
76    /// The selected rows a copy holds, in runs: those the model offers, within the selection
77    /// (a model narrows a copy, never widens it).
78    fn copied_runs(&self) -> Vec<Range<usize>> {
79        let selected = self.selected_rows();
80        if selected.is_empty() {
81            return Vec::new();
82        }
83        self.model
84            .copy_rows(selected.clone())
85            .into_iter()
86            .map(|run| run.start.max(selected.start)..run.end.min(selected.end))
87            .filter(|run| !run.is_empty())
88            .collect()
89    }
90
91    /// The text of the rows of `runs`, as [`selected_text`](Self::selected_text) writes it.
92    fn text_of(&self, runs: Vec<Range<usize>>, palette: &Palette) -> String {
93        let columns = self.model.columns().len();
94        let mut out = String::new();
95        let mut cell = CellText::new();
96        let rows = runs.into_iter().flatten();
97        for (index, row) in rows.enumerate() {
98            if index > 0 {
99                out.push('\n');
100            }
101            for column in 0..columns {
102                if column > 0 {
103                    out.push('\t');
104                }
105                cell.clear();
106                self.model.cell(row, column, &mut cell, palette);
107                out.extend(cell.as_str().chars().map(|c| match c {
108                    '\t' | '\n' | '\r' => ' ',
109                    c => c,
110                }));
111            }
112        }
113        out
114    }
115
116    /// Takes in what the model changed since the last frame, as painting does.
117    fn catch_up(&mut self) {
118        self.model.prepare();
119        self.follow_model();
120        self.clamp();
121    }
122
123    /// Selects `row` alone (reporting it if it was not selected) and shows it.
124    pub(super) fn select_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
125        self.anchor = None;
126        self.all = false;
127        self.move_selection(cx, row);
128    }
129
130    /// Selects from where the selection started (the selected row, if it was one row) to
131    /// `row`: Shift with a click or a key.
132    pub(super) fn extend_to(&mut self, cx: &mut EventCx<'_>, row: usize) {
133        self.all = false;
134        if self.anchor.is_none() {
135            self.anchor = Some(self.selected.unwrap_or_else(|| self.selection_of(row)));
136        }
137        self.move_selection(cx, row);
138    }
139
140    /// Makes `row` the selected row, keeping the anchor, and shows it.
141    fn move_selection(&mut self, cx: &mut EventCx<'_>, row: usize) {
142        if self.selected() != Some(row) {
143            self.selected = Some(self.selection_of(row));
144            cx.emit(ActionKind::Selected(row));
145        }
146        self.reveal_row(row);
147        cx.request_paint();
148    }
149}