1use crate::event::{MouseButton, MouseEvent, MouseKind};
11use crate::geometry::Rect;
12use crate::widget::{EventCx, PaintCx};
13
14use super::click::{Click, LastPress};
15use super::select_box::SelectBox;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct RowDrop {
22 pub rows: Vec<usize>,
25 pub into: usize,
27}
28
29type ChooseMessage<Msg> = Box<dyn Fn(Vec<usize>) -> Msg>;
31
32type DropMessage<Msg> = Box<dyn Fn(RowDrop) -> Msg>;
34
35type DropFilter = Box<dyn Fn(usize) -> bool>;
37
38pub(crate) enum Spot {
40 Row(usize),
42 Free,
44 Outside,
46}
47
48pub(crate) trait PickedRows<Msg> {
51 fn spot(&self, cx: &mut EventCx<'_, Msg>, x: i32, y: i32) -> Spot;
53 fn covered(&self, cx: &mut EventCx<'_, Msg>, rect: Rect) -> Vec<usize>;
55 fn cursor(&self) -> Option<usize>;
57 fn select(&self, cx: &mut EventCx<'_, Msg>, index: usize);
59 fn open(&self, cx: &mut EventCx<'_, Msg>, index: usize, at: (i32, i32));
61}
62
63pub(crate) struct Picking<Msg> {
66 pub(crate) activate_on: Click,
68 pub(crate) chosen: Vec<usize>,
70 pub(crate) on_choose: Option<ChooseMessage<Msg>>,
72 pub(crate) box_select: bool,
74 pub(crate) dropping: Option<(DropMessage<Msg>, DropFilter)>,
76 pub(crate) copy_drop: Option<DropMessage<Msg>>,
78}
79
80impl<Msg> Default for Picking<Msg> {
81 fn default() -> Self {
82 Self {
83 activate_on: Click::Single,
84 chosen: Vec::new(),
85 on_choose: None,
86 box_select: false,
87 dropping: None,
88 copy_drop: None,
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
95struct RowPress {
96 index: usize,
97 start: (i32, i32),
98 pointer: (i32, i32),
99 dragging: bool,
100 carried: Vec<usize>,
102 reduce: bool,
105}
106
107#[derive(Debug, Clone)]
109enum Held {
110 Row(RowPress),
111 Box(SelectBox<usize>),
112}
113
114#[derive(Debug, Default)]
116struct PointerRows {
117 presses: LastPress<usize>,
118 held: Option<Held>,
119 anchor: Option<usize>,
121}
122
123impl<Msg: 'static> Picking<Msg> {
124 pub(crate) fn is_multi(&self) -> bool {
126 self.on_choose.is_some()
127 }
128
129 pub(crate) fn is_chosen(&self, index: usize) -> bool {
131 self.is_multi() && self.chosen.contains(&index)
132 }
133
134 fn choose(&self, cx: &mut EventCx<'_, Msg>, rows: Vec<usize>) {
136 if let Some(message) = &self.on_choose
137 && rows != self.chosen
138 {
139 cx.emit(message(rows));
140 }
141 }
142
143 pub(crate) fn select_one(&self, cx: &mut EventCx<'_, Msg>, rows: &impl PickedRows<Msg>, index: usize) {
145 rows.select(cx, index);
146 if self.is_multi() {
147 cx.memory::<PointerRows>().anchor = Some(index);
148 self.choose(cx, vec![index]);
149 }
150 }
151
152 pub(crate) fn toggle(&self, cx: &mut EventCx<'_, Msg>, rows: &impl PickedRows<Msg>, index: usize) -> bool {
155 if !self.is_multi() {
156 return false;
157 }
158 let mut chosen = self.chosen.clone();
159 match chosen.iter().position(|row| *row == index) {
160 Some(at) => {
161 chosen.remove(at);
162 }
163 None => chosen.push(index),
164 }
165 rows.select(cx, index);
166 cx.memory::<PointerRows>().anchor = Some(index);
167 self.choose(cx, chosen);
168 true
169 }
170
171 fn select_range(&self, cx: &mut EventCx<'_, Msg>, rows: &impl PickedRows<Msg>, index: usize) {
174 let start = cx.memory::<PointerRows>().anchor.or_else(|| rows.cursor()).unwrap_or(index);
175 cx.memory::<PointerRows>().anchor = Some(start);
176 rows.select(cx, index);
177 self.choose(cx, (start.min(index)..=start.max(index)).collect());
178 }
179
180 pub(crate) fn takes_drop(&self, carried: &[usize], into: usize) -> bool {
182 self.dropping.as_ref().is_some_and(|(_, accepts)| accepts(into)) && !carried.contains(&into)
183 }
184
185 pub(crate) fn mouse(
188 &self,
189 cx: &mut EventCx<'_, Msg>,
190 mouse: &MouseEvent,
191 rows: &impl PickedRows<Msg>,
192 ) -> Option<bool> {
193 match mouse.kind {
194 MouseKind::Down(MouseButton::Left) => self.press(cx, mouse, rows),
195 MouseKind::Drag(MouseButton::Left) => self.drag(cx, (mouse.x, mouse.y), rows),
196 MouseKind::Up(MouseButton::Left) => self.release(cx, mouse, rows),
197 _ => None,
198 }
199 }
200
201 fn press(&self, cx: &mut EventCx<'_, Msg>, mouse: &MouseEvent, rows: &impl PickedRows<Msg>) -> Option<bool> {
202 let at = (mouse.x, mouse.y);
203 let mods = mouse.mods;
204 let index = match rows.spot(cx, mouse.x, mouse.y) {
205 Spot::Outside => return None,
206 Spot::Free if self.box_select && self.is_multi() => {
207 let drawn = SelectBox::new(at, mods.ctrl && !mods.alt, &self.chosen);
208 let covered = rows.covered(cx, drawn.rect());
209 self.choose(cx, drawn.selection(covered));
210 let memory = cx.memory::<PointerRows>();
211 memory.presses.forget();
212 memory.held = Some(Held::Box(drawn));
213 cx.capture_pointer();
214 return Some(true);
215 }
216 Spot::Free => return None,
217 Spot::Row(index) => index,
218 };
219 if self.is_multi() && !mods.alt && mods.ctrl != mods.shift {
220 cx.memory::<PointerRows>().presses.forget();
221 if mods.ctrl {
222 self.toggle(cx, rows, index);
223 } else {
224 self.select_range(cx, rows, index);
225 }
226 return Some(true);
227 }
228 let now = cx.now();
229 if self.activate_on == Click::Double && cx.memory::<PointerRows>().presses.press(index, now) {
230 rows.select(cx, index);
231 rows.open(cx, index, at);
232 return Some(true);
233 }
234 let reduce = self.dropping.is_some() && self.chosen.len() > 1 && self.is_chosen(index);
237 if reduce {
238 rows.select(cx, index);
239 } else {
240 self.select_one(cx, rows, index);
241 }
242 if self.dropping.is_none() {
243 if self.activate_on == Click::Single {
244 rows.open(cx, index, at);
245 }
246 return Some(true);
247 }
248 let carried = if reduce { sorted(&self.chosen) } else { vec![index] };
249 let press = RowPress { index, start: at, pointer: at, dragging: false, carried, reduce };
250 cx.memory::<PointerRows>().held = Some(Held::Row(press));
251 cx.capture_pointer();
252 Some(true)
253 }
254
255 fn drag(&self, cx: &mut EventCx<'_, Msg>, at: (i32, i32), rows: &impl PickedRows<Msg>) -> Option<bool> {
256 let PointerRows { presses, held, .. } = cx.memory::<PointerRows>();
257 match held.as_mut()? {
258 Held::Box(drawn) => {
259 drawn.stretch(at);
260 let drawn = drawn.clone();
261 let covered = rows.covered(cx, drawn.rect());
262 self.choose(cx, drawn.selection(covered));
263 }
264 Held::Row(press) => {
265 press.pointer = at;
266 if !press.dragging && at != press.start {
267 press.dragging = true;
268 presses.forget();
270 }
271 }
272 }
273 Some(true)
274 }
275
276 fn release(&self, cx: &mut EventCx<'_, Msg>, mouse: &MouseEvent, rows: &impl PickedRows<Msg>) -> Option<bool> {
277 let at = (mouse.x, mouse.y);
278 match cx.memory::<PointerRows>().held.take()? {
279 Held::Box(mut drawn) => {
280 drawn.stretch(at);
281 let covered = rows.covered(cx, drawn.rect());
282 self.choose(cx, drawn.selection(covered));
283 }
284 Held::Row(press) if press.dragging => {
285 let Spot::Row(into) = rows.spot(cx, mouse.x, mouse.y) else { return Some(true) };
286 if !self.takes_drop(&press.carried, into) {
287 return Some(true);
288 }
289 let drop = RowDrop { rows: press.carried, into };
290 let message = match (&self.copy_drop, &self.dropping) {
292 (Some(copy), _) if mouse.mods.ctrl => copy(drop),
293 (_, Some((moving, _))) => moving(drop),
294 (_, None) => return Some(true),
295 };
296 cx.emit(message);
297 }
298 Held::Row(press) => {
299 if press.reduce {
300 self.select_one(cx, rows, press.index);
301 }
302 if self.activate_on == Click::Single {
303 rows.open(cx, press.index, at);
304 }
305 }
306 }
307 Some(true)
308 }
309}
310
311fn sorted(rows: &[usize]) -> Vec<usize> {
313 let mut rows = rows.to_vec();
314 rows.sort_unstable();
315 rows
316}
317
318pub(crate) fn dragged(cx: &mut PaintCx<'_>) -> Option<((i32, i32), Vec<usize>)> {
320 match &cx.memory::<PointerRows>().held {
321 Some(Held::Row(press)) if press.dragging => Some((press.pointer, press.carried.clone())),
322 _ => None,
323 }
324}
325
326pub(crate) fn drawn_box(cx: &mut PaintCx<'_>) -> Option<Rect> {
328 match &cx.memory::<PointerRows>().held {
329 Some(Held::Box(drawn)) => Some(drawn.rect()),
330 _ => None,
331 }
332}