1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! A box drawn with the pointer over a widget's rows, selecting the rows it covers.
//!
//! A press on the free space of a [`Tree`](super::Tree), a [`Table`](super::Table) or a
//! [`CardGrid`](super::CardGrid) that selects by box starts one; dragging stretches it from the
//! press to the pointer, and the rows it covers become the selection as it goes, so what will be
//! selected is always what is shown. With Ctrl held at the press the covered rows join the
//! selection there was instead of replacing it. A press that never moves is a click on the free
//! space, which covers no row: it clears the selection, as it does in a file explorer.
//!
//! The box is a tone, never a frame: the cells it covers take the tone of selected text, so it
//! reads as the same thing a text selection is.
use crate::geometry::Rect;
use crate::widget::PaintCx;
/// A box being drawn, kept in the widget's memory while the button is held. `K` is how the widget
/// names a row: a node key or an index.
#[derive(Debug, Clone)]
pub(crate) struct SelectBox<K> {
start: (i32, i32),
pointer: (i32, i32),
/// The selection the covered rows join: the one there was when Ctrl was held at the press,
/// none otherwise.
base: Vec<K>,
}
impl<K: Clone + PartialEq> SelectBox<K> {
/// A box pressed at `at`, adding to `base` when `add` and replacing it otherwise.
pub(crate) fn new(at: (i32, i32), add: bool, base: &[K]) -> Self {
Self { start: at, pointer: at, base: if add { base.to_vec() } else { Vec::new() } }
}
/// Stretches the box to the pointer at `at`.
pub(crate) fn stretch(&mut self, at: (i32, i32)) {
self.pointer = at;
}
/// The cells the box covers, from the press to the pointer, both included.
pub(crate) fn rect(&self) -> Rect {
let (left, right) = (self.start.0.min(self.pointer.0), self.start.0.max(self.pointer.0));
let (top, bottom) = (self.start.1.min(self.pointer.1), self.start.1.max(self.pointer.1));
let size = |low: i32, high: i32| u16::try_from(high - low + 1).unwrap_or(u16::MAX);
Rect::new(left, top, size(left, right), size(top, bottom))
}
/// The selection with the rows the box covers, `covered`, in their order after the ones kept.
pub(crate) fn selection(&self, covered: impl IntoIterator<Item = K>) -> Vec<K> {
let mut keys = self.base.clone();
for key in covered {
if !keys.contains(&key) {
keys.push(key);
}
}
keys
}
}
/// Paints the box `rect` inside `area`: the covered cells take the tone of selected text, their
/// glyphs kept and brightened where they would no longer read on it.
pub(crate) fn paint(cx: &mut PaintCx<'_>, rect: Rect, area: Rect) {
let rect = rect.intersect(area);
if rect.is_empty() {
return;
}
let style = cx.style("text-selection", None, &[]).text();
let bg = style.bg.unwrap_or_else(|| cx.color("active"));
let readable = style.fg.unwrap_or_else(|| cx.color("text"));
cx.fill_keeping_text_readable(rect, bg, readable);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_box_runs_from_the_press_to_the_pointer_whichever_way_it_is_dragged() {
let mut drawn = SelectBox::<usize>::new((10, 8), false, &[]);
assert_eq!(drawn.rect(), Rect::new(10, 8, 1, 1), "a press alone covers its own cell");
drawn.stretch((4, 3));
assert_eq!(drawn.rect(), Rect::new(4, 3, 7, 6));
}
#[test]
fn ctrl_adds_the_covered_rows_to_the_selection_and_a_plain_box_replaces_it() {
let plain = SelectBox::new((0, 0), false, &[1, 2]);
assert_eq!(plain.selection([2, 5]), [2, 5]);
let added = SelectBox::new((0, 0), true, &[1, 2]);
assert_eq!(added.selection([2, 5]), [1, 2, 5], "a row already selected is not added twice");
assert_eq!(added.selection([]), [1, 2], "covering nothing keeps what there was");
}
}