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::context_item;
16use super::super::context_menu::{self, ContextMenu};
17use super::super::edge_scroll::{Edge, EdgeScroll, Zone};
18use super::super::rows::RowScroll;
19use super::super::tab_model::{Direction, drop_target};
20use super::{Flat, Tree, TreeNode};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct TreeMove {
27 pub key: String,
29 pub parent: Option<String>,
31 pub from: usize,
33 pub to: usize,
35}
36
37impl TreeMove {
38 pub fn apply<T>(&self, siblings: &mut Vec<T>) {
42 let mut unused = 0;
43 TabEdit::Move { from: self.from, to: self.to }.apply(siblings, &mut unused);
44 }
45}
46
47pub(super) struct Arrange<'k> {
51 pub(super) key: &'k str,
52 pub(super) parent: Option<&'k str>,
53 pub(super) order: Option<(usize, usize)>,
54}
55
56#[derive(Debug, Clone)]
58struct Press {
59 key: String,
60 start: (i32, i32),
61 pointer: (i32, i32),
62 dragging: bool,
63}
64
65#[derive(Debug, Default)]
67struct TreePointer {
68 pressed: Option<Press>,
69 edge: EdgeScroll,
71}
72
73pub(super) struct Drag {
75 pub(super) key: String,
76 pub(super) pointer: (i32, i32),
77}
78
79#[derive(Debug, Default)]
81struct MenuNode(Option<String>);
82
83impl<Msg: 'static> Tree<Msg> {
84 pub(super) fn siblings(&self, key: &str) -> Option<(Option<&str>, &[TreeNode])> {
86 fn find<'a>(
87 nodes: &'a [TreeNode],
88 parent: Option<&'a str>,
89 key: &str,
90 ) -> Option<(Option<&'a str>, &'a [TreeNode])> {
91 if nodes.iter().any(|node| node.key == key) {
92 return Some((parent, nodes));
93 }
94 nodes.iter().find_map(|node| find(&node.children, Some(&node.key), key))
95 }
96 find(&self.roots, None, key)
97 }
98
99 fn move_node(&self, cx: &mut EventCx<'_, Msg>, key: &str, parent: Option<&str>, from: usize, to: usize) {
101 if from != to
102 && let Some(message) = &self.on_move
103 {
104 cx.emit(message(TreeMove { key: key.to_owned(), parent: parent.map(str::to_owned), from, to }));
105 }
106 }
107
108 pub(super) fn move_key(&self, cx: &mut EventCx<'_, Msg>, key: &KeyEvent) -> bool {
110 let mods = key.chord.mods;
111 let up = key.chord.key == Key::Up;
112 if self.on_move.is_none() || !mods.ctrl || !mods.shift || mods.alt || !(up || key.chord.key == Key::Down) {
113 return false;
114 }
115 let Some(selected) = self.selected.as_deref() else {
116 return true;
117 };
118 let Some((parent, siblings)) = self.siblings(selected) else {
119 return true;
120 };
121 let Some(from) = siblings.iter().position(|node| node.key == selected) else {
122 return true;
123 };
124 let to = if up { from.saturating_sub(1) } else { (from + 1).min(siblings.len() - 1) };
125 self.move_node(cx, selected, parent, from, to);
126 true
127 }
128
129 pub(super) fn resting(&self, key: &str) -> Vec<Flat<'_>> {
131 let parent = self.siblings(key).and_then(|(parent, _)| parent);
132 self.flatten_with(Some(&Arrange { key, parent, order: None }))
133 }
134
135 pub(super) fn landing(&self, key: &str, pointer: (i32, i32), area: Rect, offset: usize) -> Option<(usize, usize)> {
139 let flat = self.resting(key);
140 let row = flat.iter().position(|row| row.node.key == key)?;
141 let parent = flat[row].parent;
142 let from = flat[row].index;
143 let visible = usize::from(area.height);
144 let slots: Vec<(usize, Rect)> = flat
145 .iter()
146 .enumerate()
147 .skip(offset)
148 .take(visible)
149 .filter(|(_, other)| other.parent == parent)
150 .map(|(at, other)| {
151 let y = area.y + i32::try_from(at - offset).unwrap_or(0);
152 (other.index, Rect::new(area.x, y, area.width, 1))
153 })
154 .collect();
155 Some((from, drop_target(&slots, from, pointer, Direction::Down)))
156 }
157
158 pub(super) fn drag(&self, cx: &mut PaintCx<'_>) -> Option<Drag> {
160 self.on_move.as_ref()?;
161 let press = cx.memory::<TreePointer>().pressed.clone()?;
162 (press.dragging && self.siblings(&press.key).is_some())
163 .then_some(Drag { key: press.key, pointer: press.pointer })
164 }
165
166 pub(super) fn reorder_pointer(
171 &self,
172 cx: &mut EventCx<'_, Msg>,
173 mouse: &MouseEvent,
174 flat: &[Flat<'_>],
175 index: Option<usize>,
176 ) -> Option<bool> {
177 let at = (mouse.x, mouse.y);
178 match mouse.kind {
179 MouseKind::Down(MouseButton::Left) => {
180 let row = &flat[index?];
181 self.select(cx, flat, index?);
182 cx.capture_pointer();
183 let press = Press { key: row.node.key.clone(), start: at, pointer: at, dragging: false };
184 *cx.memory::<TreePointer>() = TreePointer { pressed: Some(press), edge: EdgeScroll::default() };
185 Some(true)
186 }
187 MouseKind::Drag(MouseButton::Left) => {
188 let memory = cx.memory::<TreePointer>();
189 let press = memory.pressed.as_mut()?;
190 press.pointer = at;
191 if (at.1 - press.start.1).abs() >= 1 {
192 press.dragging = true;
193 }
194 let dragging = press.dragging;
195 self.edge_scroll(cx, mouse.y, flat.len(), dragging);
196 Some(true)
197 }
198 MouseKind::Up(MouseButton::Left) => {
199 let memory = cx.memory::<TreePointer>();
200 memory.edge = EdgeScroll::default();
201 let press = memory.pressed.take()?;
202 let area = cx.area();
203 if press.dragging {
204 let offset = cx.memory::<RowScroll>().offset;
205 if let Some((from, to)) = self.landing(&press.key, at, Self::rows_area(area, flat.len()), offset) {
206 let parent = self.siblings(&press.key).and_then(|(parent, _)| parent.map(str::to_owned));
207 self.move_node(cx, &press.key, parent.as_deref(), from, to);
208 }
209 } else if let Some(row) = flat.iter().position(|row| row.node.key == press.key) {
210 self.open_or_activate(cx, row, flat[row].node);
211 }
212 Some(true)
213 }
214 _ => None,
215 }
216 }
217
218 pub(super) fn rows_area(area: Rect, total: usize) -> Rect {
220 let overflows = total > usize::from(area.height);
221 Rect::new(area.x, area.y, area.width.saturating_sub(u16::from(overflows)), area.height)
222 }
223
224 fn edge_scroll(&self, cx: &mut EventCx<'_, Msg>, y: i32, total: usize, dragging: bool) {
226 let area = cx.area();
227 let last = area.bottom() - 1;
228 let zone = if y <= area.y {
229 Some(Zone { edge: Edge::Back, beyond: u16::try_from(area.y - y).unwrap_or(u16::MAX) })
230 } else if y >= last {
231 Some(Zone { edge: Edge::Forward, beyond: u16::try_from(y - last).unwrap_or(u16::MAX) })
232 } else {
233 None
234 };
235 let visible = usize::from(area.height);
236 let mut edge = cx.memory::<TreePointer>().edge;
237 edge.drive(cx, zone.filter(|_| dragging), |cx, edge| {
238 let memory = cx.memory::<RowScroll>();
239 memory.offset = match edge {
240 Edge::Back => memory.offset.checked_sub(1)?,
241 Edge::Forward => Some(memory.offset + 1).filter(|next| *next + visible <= total)?,
242 };
243 Some(memory.offset)
244 });
245 cx.memory::<TreePointer>().edge = edge;
246 }
247
248 fn menu_for(&self, key: &str) -> Option<(ContextMenu<usize>, Vec<Msg>)> {
250 let items = self.menu.as_ref()?;
251 let (items, messages) = context_item::keyed(items(key));
252 Some((ContextMenu::new(items), messages))
253 }
254
255 fn open_menu(&self, cx: &mut EventCx<'_, Msg>, key: &str, anchor: Rect, keyboard: bool) {
256 let Some((menu, _)) = self.menu_for(key) else {
257 return;
258 };
259 cx.memory::<MenuNode>().0 = Some(key.to_owned());
260 cx.with_messages(|cx: &mut EventCx<'_, usize>| menu.open(cx, anchor, keyboard));
261 }
262
263 pub(super) fn menu_event(&self, cx: &mut EventCx<'_, Msg>, event: &Event, flat: &[Flat<'_>]) -> bool {
271 if self.menu.is_none() || flat.is_empty() {
272 return false;
273 }
274 let right_press = match event {
275 Event::Mouse(mouse) if mouse.kind == MouseKind::Down(MouseButton::Right) => Some((mouse.x, mouse.y)),
276 _ => None,
277 };
278 if context_menu::is_open_in(cx) {
279 let node = cx.memory::<MenuNode>().0.clone().filter(|key| flat.iter().any(|row| row.node.key == *key));
280 let elsewhere = right_press.is_some_and(|(x, y)| !context_menu::contains(cx, x, y));
281 match node.and_then(|key| self.menu_for(&key)).filter(|_| !elsewhere) {
284 Some((menu, messages)) => {
285 let (used, chosen) = cx.with_messages(|cx| menu.event(cx, event));
286 if let Some(message) = chosen.last().and_then(|chosen| messages.into_iter().nth(*chosen)) {
287 cx.emit(message);
288 }
289 return used;
290 }
291 None => {
292 cx.with_messages(|cx: &mut EventCx<'_, usize>| ContextMenu::<usize>::close(cx));
293 }
294 }
295 }
296 let area = cx.area();
297 let visible = usize::from(area.height);
298 match event {
299 Event::Mouse(_) => {
300 let Some((x, y)) = right_press else {
301 return false;
302 };
303 let offset = cx.memory::<RowScroll>().offset;
304 let row = usize::try_from(y - area.y).ok().map(|r| offset + r).filter(|i| *i < flat.len());
305 let Some(row) = row.filter(|_| x < Self::rows_area(area, flat.len()).right()) else {
306 return false;
307 };
308 self.open_menu(cx, &flat[row].node.key, Rect::new(x, y, 1, 1), false);
309 true
310 }
311 Event::Key(key) if context_menu::is_menu_key(key) => {
312 let Some(row) = self.selected_index(flat) else {
313 return false;
314 };
315 let memory = cx.memory::<RowScroll>();
316 if row < memory.offset {
317 memory.offset = row;
318 } else if visible > 0 && row >= memory.offset + visible {
319 memory.offset = row + 1 - visible;
320 }
321 let y = area.y + i32::try_from(row - memory.offset).unwrap_or(0);
322 let anchor = Rect::new(area.x, y, Self::rows_area(area, flat.len()).width, 1);
323 self.open_menu(cx, &flat[row].node.key, anchor, true);
324 true
325 }
326 _ => false,
327 }
328 }
329
330 pub(super) fn menu_node(&self, cx: &mut PaintCx<'_>) -> Option<String> {
333 if self.menu.is_none() || !context_menu::is_open(cx) {
334 return None;
335 }
336 cx.memory::<MenuNode>().0.clone()
337 }
338
339 pub(super) fn paint_menu(&self, cx: &mut PaintCx<'_>, anchor: Rect) {
341 if let Some((menu, _)) = self.menu_node(cx).and_then(|key| self.menu_for(&key)) {
342 menu.paint_overlay(cx, anchor);
343 }
344 }
345}