1use crate::event::{Event, KeyEvent, MouseButton, MouseEvent, MouseKind};
10use crate::geometry::Rect;
11use crate::keymap::Key;
12use crate::widget::{EventCx, PaintCx, Widget};
13
14use super::super::TabEdit;
15use super::super::click::Click;
16use super::super::context_item;
17use super::super::context_menu::{self, ContextMenu};
18use super::super::edge_scroll::{Edge, EdgeScroll, Zone};
19use super::super::rows::RowScroll;
20use super::super::tab_model::{Direction, drop_target};
21use super::drop::{Aim, Spring};
22use super::{Flat, Tree, TreeNode};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct TreeMove {
29 pub key: String,
31 pub parent: Option<String>,
33 pub from: usize,
35 pub to: usize,
37}
38
39impl TreeMove {
40 pub fn apply<T>(&self, siblings: &mut Vec<T>) {
44 let mut unused = 0;
45 TabEdit::Move { from: self.from, to: self.to }.apply(siblings, &mut unused);
46 }
47}
48
49pub(super) struct Arrange<'k> {
53 pub(super) key: &'k str,
54 pub(super) parent: Option<&'k str>,
55 pub(super) order: Option<(usize, usize)>,
56}
57
58#[derive(Debug, Clone)]
60struct Press {
61 key: String,
62 keys: Vec<String>,
64 start: (i32, i32),
65 pointer: (i32, i32),
66 dragging: bool,
67 reduce: bool,
70}
71
72#[derive(Debug, Default)]
74struct TreePointer {
75 pressed: Option<Press>,
76 edge: EdgeScroll,
78}
79
80pub(super) struct Drag {
82 pub(super) key: String,
84 pub(super) keys: Vec<String>,
86 pub(super) pointer: (i32, i32),
87}
88
89#[derive(Debug, Default)]
91struct MenuNode(Option<String>);
92
93impl<Msg: 'static> Tree<Msg> {
94 pub(super) fn siblings(&self, key: &str) -> Option<(Option<&str>, &[TreeNode])> {
96 fn find<'a>(
97 nodes: &'a [TreeNode],
98 parent: Option<&'a str>,
99 key: &str,
100 ) -> Option<(Option<&'a str>, &'a [TreeNode])> {
101 if nodes.iter().any(|node| node.key == key) {
102 return Some((parent, nodes));
103 }
104 nodes.iter().find_map(|node| find(&node.children, Some(&node.key), key))
105 }
106 find(&self.roots, None, key)
107 }
108
109 pub(super) fn move_node(&self, cx: &mut EventCx<'_, Msg>, key: &str, parent: Option<&str>, from: usize, to: usize) {
111 if from != to
112 && let Some(message) = &self.on_move
113 {
114 cx.emit(message(TreeMove { key: key.to_owned(), parent: parent.map(str::to_owned), from, to }));
115 }
116 }
117
118 pub(super) fn move_key(&self, cx: &mut EventCx<'_, Msg>, key: &KeyEvent) -> bool {
120 let mods = key.chord.mods;
121 let up = key.chord.key == Key::Up;
122 if self.on_move.is_none() || !mods.ctrl || !mods.shift || mods.alt || !(up || key.chord.key == Key::Down) {
123 return false;
124 }
125 let Some(selected) = self.selected.as_deref() else {
126 return true;
127 };
128 let Some((parent, siblings)) = self.siblings(selected) else {
129 return true;
130 };
131 let Some(from) = siblings.iter().position(|node| node.key == selected) else {
132 return true;
133 };
134 let to = if up { from.saturating_sub(1) } else { (from + 1).min(siblings.len() - 1) };
135 self.move_node(cx, selected, parent, from, to);
136 true
137 }
138
139 pub(super) fn resting(&self, key: &str) -> Vec<Flat<'_>> {
141 let parent = self.siblings(key).and_then(|(parent, _)| parent);
142 self.flatten_with(Some(&Arrange { key, parent, order: None }))
143 }
144
145 pub(super) fn landing(&self, key: &str, pointer: (i32, i32), area: Rect, offset: usize) -> Option<(usize, usize)> {
149 let flat = self.resting(key);
150 let row = flat.iter().position(|row| row.node.key == key)?;
151 let parent = flat[row].parent;
152 let from = flat[row].index;
153 let visible = usize::from(area.height);
154 let slots: Vec<(usize, Rect)> = flat
155 .iter()
156 .enumerate()
157 .skip(offset)
158 .take(visible)
159 .filter(|(_, other)| other.parent == parent)
160 .map(|(at, other)| {
161 let y = area.y + i32::try_from(at - offset).unwrap_or(0);
162 (other.index, Rect::new(area.x, y, area.width, 1))
163 })
164 .collect();
165 Some((from, drop_target(&slots, from, pointer, Direction::Down)))
166 }
167
168 pub(super) fn drag(&self, cx: &mut PaintCx<'_>) -> Option<Drag> {
170 if self.on_move.is_none() && self.dropping.is_none() {
171 return None;
172 }
173 let press = cx.memory::<TreePointer>().pressed.clone()?;
174 (press.dragging && self.siblings(&press.key).is_some()).then_some(Drag {
175 key: press.key,
176 keys: press.keys,
177 pointer: press.pointer,
178 })
179 }
180
181 pub(super) fn drag_pointer(
187 &self,
188 cx: &mut EventCx<'_, Msg>,
189 mouse: &MouseEvent,
190 flat: &[Flat<'_>],
191 index: Option<usize>,
192 ) -> Option<bool> {
193 let at = (mouse.x, mouse.y);
194 match mouse.kind {
195 MouseKind::Down(MouseButton::Left) => {
196 let index = index?;
197 if self.modified_press(cx, flat, index, mouse.mods) {
198 return Some(true);
199 }
200 let key = flat[index].node.key.clone();
201 if self.double_press(cx, &key) {
202 self.select_one(cx, flat, index);
203 self.open_or_activate(cx, index, flat[index].node);
204 return Some(true);
205 }
206 let reduce = self.is_among_many(&key);
209 if reduce {
210 self.select(cx, flat, index);
211 } else {
212 self.select_one(cx, flat, index);
213 }
214 cx.capture_pointer();
215 *cx.memory::<Spring>() = Spring::default();
216 let keys = self.carried(&key);
217 let press = Press { key, keys, start: at, pointer: at, dragging: false, reduce };
218 *cx.memory::<TreePointer>() = TreePointer { pressed: Some(press), edge: EdgeScroll::default() };
219 Some(true)
220 }
221 MouseKind::Drag(MouseButton::Left) => {
222 let memory = cx.memory::<TreePointer>();
223 let press = memory.pressed.as_mut()?;
224 press.pointer = at;
225 let starts = !press.dragging && (at.1 - press.start.1).abs() >= 1;
226 if starts {
227 press.dragging = true;
228 }
229 let dragging = press.dragging;
230 let keys = press.keys.clone();
231 let carried_alone = starts && press.reduce && self.dropping.is_none();
233 if carried_alone {
234 press.reduce = false;
235 }
236 if starts {
237 self.forget_press(cx);
239 }
240 if carried_alone {
241 self.choose(cx, keys.clone());
242 }
243 self.edge_scroll(cx, mouse.y, flat.len(), dragging);
244 if dragging && self.dropping.is_some() {
245 let offset = cx.memory::<RowScroll>().offset;
246 let aim = self.aim(&keys, at, Self::rows_area(cx.area(), flat.len()), offset);
247 self.wake_for_spring(cx, &aim);
248 }
249 Some(true)
250 }
251 MouseKind::Up(MouseButton::Left) => {
252 let memory = cx.memory::<TreePointer>();
253 memory.edge = EdgeScroll::default();
254 let press = memory.pressed.take()?;
255 let area = cx.area();
256 *cx.memory::<Spring>() = Spring::default();
257 if press.dragging {
258 let offset = cx.memory::<RowScroll>().offset;
259 let aim = self.aim(&press.keys, at, Self::rows_area(area, flat.len()), offset);
260 self.release(cx, press.keys, aim, mouse.mods.ctrl);
261 } else if let Some(row) = flat.iter().position(|row| row.node.key == press.key) {
262 if press.reduce {
263 self.select_one(cx, flat, row);
264 }
265 if self.activate_on == Click::Single {
266 self.open_or_activate(cx, row, flat[row].node);
267 }
268 }
269 Some(true)
270 }
271 _ => None,
272 }
273 }
274
275 fn wake_for_spring(&self, cx: &mut EventCx<'_, Msg>, aim: &Aim) {
278 let now = cx.now();
279 let edge = cx.memory::<TreePointer>().edge.due();
280 match (self.spring(cx, aim), edge) {
281 (Some(wait), Some(due)) => cx.repeat_pointer(wait.min(due.saturating_sub(now))),
282 (Some(wait), None) => cx.repeat_pointer(wait),
283 (None, Some(_)) => {}
285 (None, None) => cx.stop_pointer_repeat(),
286 }
287 }
288
289 pub(super) fn rows_area(area: Rect, total: usize) -> Rect {
291 let overflows = total > usize::from(area.height);
292 Rect::new(area.x, area.y, area.width.saturating_sub(u16::from(overflows)), area.height)
293 }
294
295 fn edge_scroll(&self, cx: &mut EventCx<'_, Msg>, y: i32, total: usize, dragging: bool) {
297 let area = cx.area();
298 let last = area.bottom() - 1;
299 let zone = if y <= area.y {
300 Some(Zone { edge: Edge::Back, beyond: u16::try_from(area.y - y).unwrap_or(u16::MAX) })
301 } else if y >= last {
302 Some(Zone { edge: Edge::Forward, beyond: u16::try_from(y - last).unwrap_or(u16::MAX) })
303 } else {
304 None
305 };
306 let visible = usize::from(area.height);
307 let mut edge = cx.memory::<TreePointer>().edge;
308 edge.drive(cx, zone.filter(|_| dragging), |cx, edge| {
309 let memory = cx.memory::<RowScroll>();
310 memory.offset = match edge {
311 Edge::Back => memory.offset.checked_sub(1)?,
312 Edge::Forward => Some(memory.offset + 1).filter(|next| *next + visible <= total)?,
313 };
314 Some(memory.offset)
315 });
316 cx.memory::<TreePointer>().edge = edge;
317 }
318
319 fn menu_for(&self, key: &str) -> Option<(ContextMenu<usize>, Vec<Msg>)> {
321 let items = self.menu.as_ref()?;
322 let (items, messages) = context_item::keyed(items(key));
323 Some((ContextMenu::new(items), messages))
324 }
325
326 fn open_menu(&self, cx: &mut EventCx<'_, Msg>, key: &str, anchor: Rect, keyboard: bool) {
327 let Some((menu, _)) = self.menu_for(key) else {
328 return;
329 };
330 cx.memory::<MenuNode>().0 = Some(key.to_owned());
331 cx.with_messages(|cx: &mut EventCx<'_, usize>| menu.open(cx, anchor, keyboard));
332 }
333
334 pub(super) fn menu_event(&self, cx: &mut EventCx<'_, Msg>, event: &Event, flat: &[Flat<'_>]) -> bool {
342 if self.menu.is_none() || flat.is_empty() {
343 return false;
344 }
345 let right_press = match event {
346 Event::Mouse(mouse) if mouse.kind == MouseKind::Down(MouseButton::Right) => Some((mouse.x, mouse.y)),
347 _ => None,
348 };
349 if context_menu::is_open_in(cx) {
350 let node = cx.memory::<MenuNode>().0.clone().filter(|key| flat.iter().any(|row| row.node.key == *key));
351 let elsewhere = right_press.is_some_and(|(x, y)| !context_menu::contains(cx, x, y));
352 match node.and_then(|key| self.menu_for(&key)).filter(|_| !elsewhere) {
355 Some((menu, messages)) => {
356 let (used, chosen) = cx.with_messages(|cx| menu.event(cx, event));
357 if let Some(message) = chosen.last().and_then(|chosen| messages.into_iter().nth(*chosen)) {
358 cx.emit(message);
359 }
360 return used;
361 }
362 None => {
363 cx.with_messages(|cx: &mut EventCx<'_, usize>| ContextMenu::<usize>::close(cx));
364 }
365 }
366 }
367 let area = cx.area();
368 let visible = usize::from(area.height);
369 match event {
370 Event::Mouse(_) => {
371 let Some((x, y)) = right_press else {
372 return false;
373 };
374 let offset = cx.memory::<RowScroll>().offset;
375 let row = usize::try_from(y - area.y).ok().map(|r| offset + r).filter(|i| *i < flat.len());
376 let Some(row) = row.filter(|_| x < Self::rows_area(area, flat.len()).right()) else {
377 return false;
378 };
379 if self.is_multi() && !self.is_chosen(&flat[row].node.key) {
383 self.select_one(cx, flat, row);
384 }
385 self.open_menu(cx, &flat[row].node.key, Rect::new(x, y, 1, 1), false);
386 true
387 }
388 Event::Key(key) if context_menu::is_menu_key(key) => {
389 let Some(row) = self.selected_index(flat) else {
390 return false;
391 };
392 let memory = cx.memory::<RowScroll>();
393 if row < memory.offset {
394 memory.offset = row;
395 } else if visible > 0 && row >= memory.offset + visible {
396 memory.offset = row + 1 - visible;
397 }
398 let y = area.y + i32::try_from(row - memory.offset).unwrap_or(0);
399 let anchor = Rect::new(area.x, y, Self::rows_area(area, flat.len()).width, 1);
400 self.open_menu(cx, &flat[row].node.key, anchor, true);
401 true
402 }
403 _ => false,
404 }
405 }
406
407 pub(super) fn menu_node(&self, cx: &mut PaintCx<'_>) -> Option<String> {
410 if self.menu.is_none() || !context_menu::is_open(cx) {
411 return None;
412 }
413 cx.memory::<MenuNode>().0.clone()
414 }
415
416 pub(super) fn paint_menu(&self, cx: &mut PaintCx<'_>, anchor: Rect) {
418 if let Some((menu, _)) = self.menu_node(cx).and_then(|key| self.menu_for(&key)) {
419 menu.paint_overlay(cx, anchor);
420 }
421 }
422}