qframe/widgets/tree/
drop.rs1use std::time::Duration;
9
10use crate::geometry::Rect;
11use crate::widget::EventCx;
12
13use super::super::edge_scroll::DELAY;
14use super::{Flat, Tree, TreeNode};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct TreeDrop {
20 pub keys: Vec<String>,
23 pub into: Option<String>,
26}
27
28type DropMessage<Msg> = Box<dyn Fn(TreeDrop) -> Msg>;
30
31type DropFilter = Box<dyn Fn(&str) -> bool>;
33
34pub(super) struct Dropping<Msg> {
36 pub(super) message: DropMessage<Msg>,
37 pub(super) accepts: DropFilter,
38}
39
40impl<Msg> Dropping<Msg> {
41 pub(super) fn new(message: impl Fn(TreeDrop) -> Msg + 'static, accepts: impl Fn(&str) -> bool + 'static) -> Self {
42 Self { message: Box::new(message), accepts: Box::new(accepts) }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub(super) enum Aim {
49 Into(Option<String>),
51 Refused(String),
54 Reorder(Option<(usize, usize)>),
57 Nothing,
59}
60
61#[derive(Debug, Default)]
63pub(super) struct Spring {
64 pending: Option<(String, Option<Duration>)>,
66}
67
68impl<Msg: 'static> Tree<Msg> {
69 fn find(&self, key: &str) -> Option<&TreeNode> {
71 fn walk<'a>(nodes: &'a [TreeNode], key: &str) -> Option<&'a TreeNode> {
72 nodes.iter().find_map(|node| if node.key == key { Some(node) } else { walk(&node.children, key) })
73 }
74 walk(&self.roots, key)
75 }
76
77 pub(super) fn carried(&self, key: &str) -> Vec<String> {
80 fn walk(nodes: &[TreeNode], chosen: &[String], out: &mut Vec<String>) {
81 for node in nodes {
82 if chosen.contains(&node.key) {
83 out.push(node.key.clone());
84 } else {
85 walk(&node.children, chosen, out);
86 }
87 }
88 }
89 if self.dropping.is_none() || !self.is_multi() || !self.is_chosen(key) {
90 return vec![key.to_owned()];
91 }
92 let mut out = Vec::new();
93 walk(&self.roots, &self.chosen, &mut out);
94 out
95 }
96
97 pub(super) fn reorders(&self, keys: &[String]) -> bool {
100 self.on_move.is_some() && keys.len() == 1
101 }
102
103 pub(super) fn drag_layout(&self, keys: &[String]) -> Vec<Flat<'_>> {
106 match keys {
107 [key] if self.reorders(keys) => self.resting(key),
108 _ => self.flatten(),
109 }
110 }
111
112 fn refuses(&self, keys: &[String], into: Option<&str>) -> bool {
115 fn holds(node: &TreeNode, key: &str) -> bool {
116 node.children.iter().any(|child| child.key == key || holds(child, key))
117 }
118 let parent = |key: &str| self.siblings(key).map(|(parent, _)| parent);
119 if keys.iter().all(|key| parent(key) == Some(into)) {
120 return true;
121 }
122 into.is_some_and(|into| {
123 keys.iter().any(|key| key == into || self.find(key).is_some_and(|node| holds(node, into)))
124 })
125 }
126
127 pub(super) fn aim(&self, keys: &[String], pointer: (i32, i32), area: Rect, offset: usize) -> Aim {
131 if let Some(dropping) = &self.dropping
132 && area.contains(pointer.0, pointer.1)
133 {
134 let flat = self.drag_layout(keys);
135 let index = offset + usize::try_from(pointer.1 - area.y).unwrap_or(0);
136 match flat.get(index) {
137 Some(row) => {
138 let key = &row.node.key;
139 let own_slot = self.reorders(keys) && keys.first() == Some(key);
140 if (dropping.accepts)(key) && !own_slot {
141 return if self.refuses(keys, Some(key)) {
142 Aim::Refused(key.clone())
143 } else {
144 Aim::Into(Some(key.clone()))
145 };
146 }
147 }
148 None if !self.refuses(keys, None) => return Aim::Into(None),
149 None => {}
150 }
151 }
152 match keys {
153 [key] if self.reorders(keys) => Aim::Reorder(self.landing(key, pointer, area, offset)),
154 _ => Aim::Nothing,
155 }
156 }
157
158 pub(super) fn release(&self, cx: &mut EventCx<'_, Msg>, keys: Vec<String>, aim: Aim) {
160 match aim {
161 Aim::Into(into) => {
162 if let Some(dropping) = &self.dropping {
163 cx.emit((dropping.message)(TreeDrop { keys, into }));
164 }
165 }
166 Aim::Reorder(Some((from, to))) => {
167 let Some(key) = keys.first() else { return };
168 let parent = self.siblings(key).and_then(|(parent, _)| parent.map(str::to_owned));
169 self.move_node(cx, key, parent.as_deref(), from, to);
170 }
171 Aim::Reorder(None) | Aim::Refused(_) | Aim::Nothing => {}
172 }
173 }
174
175 pub(super) fn spring(&self, cx: &mut EventCx<'_, Msg>, aim: &Aim) -> Option<Duration> {
180 let now = cx.now();
181 let node = match aim {
182 Aim::Into(Some(key)) => self.find(key).filter(|node| node.expandable && !node.expanded),
183 _ => None,
184 };
185 let Some(node) = node else {
186 cx.memory::<Spring>().pending = None;
187 return None;
188 };
189 let pending = cx.memory::<Spring>().pending.clone();
190 match pending {
191 Some((key, due)) if key == node.key => {
192 let due = due?;
193 if now < due {
194 return Some(due - now);
195 }
196 cx.memory::<Spring>().pending = Some((key, None));
197 self.expand(cx, node, true);
198 None
199 }
200 _ => {
201 cx.memory::<Spring>().pending = Some((node.key.clone(), Some(now + DELAY)));
202 Some(DELAY)
203 }
204 }
205 }
206}