freya_components/
drag_drop.rs1use freya_core::{
2 prelude::*,
3 scope_id::ScopeId,
4};
5use torin::prelude::*;
6
7#[derive(Clone, Copy)]
8enum DragPhase {
9 Idle,
10 Pressing {
11 press_point: CursorPoint,
12 offset: CursorPoint,
13 },
14 Dragging {
15 position: CursorPoint,
16 offset: CursorPoint,
17 },
18}
19
20pub fn use_drag<T: 'static>() -> State<Option<T>> {
26 match try_consume_root_context() {
27 Some(s) => s,
28 None => {
29 let state = State::<Option<T>>::create_in_scope(None, ScopeId::ROOT);
30 provide_context_for_scope_id(state, ScopeId::ROOT);
31 state
32 }
33 }
34}
35
36#[derive(Clone, PartialEq)]
47pub struct DragZone<T: Clone + 'static + PartialEq> {
48 children: Vec<Element>,
49 drag_element: Option<Element>,
50 data: T,
51 show_while_dragging: bool,
52 drag_threshold: f64,
53 enabled: bool,
54 layout: LayoutData,
55 key: DiffKey,
56}
57
58impl<T: Clone + PartialEq + 'static> ChildrenExt for DragZone<T> {
59 fn children_mut(&mut self) -> &mut Vec<Element> {
60 &mut self.children
61 }
62}
63
64impl<T: Clone + PartialEq + 'static> KeyExt for DragZone<T> {
65 fn key_mut(&mut self) -> &mut DiffKey {
66 &mut self.key
67 }
68}
69
70impl<T: Clone + PartialEq + 'static> LayoutExt for DragZone<T> {
71 fn layout_mut(&mut self) -> &mut LayoutData {
72 &mut self.layout
73 }
74}
75
76impl<T: Clone + PartialEq + 'static> ContainerExt for DragZone<T> {}
77
78impl<T: Clone + PartialEq + 'static> DragZone<T> {
79 pub fn new(data: T) -> Self {
81 Self {
82 data,
83 children: Vec::new(),
84 drag_element: None,
85 show_while_dragging: true,
86 drag_threshold: 4.0,
87 enabled: true,
88 layout: LayoutData::default(),
89 key: DiffKey::default(),
90 }
91 }
92
93 pub fn show_while_dragging(mut self, show_while_dragging: bool) -> Self {
95 self.show_while_dragging = show_while_dragging;
96 self
97 }
98
99 pub fn drag_element(mut self, drag_element: impl Into<Element>) -> Self {
101 self.drag_element = Some(drag_element.into());
102 self
103 }
104
105 pub fn drag_threshold(mut self, drag_threshold: f64) -> Self {
107 self.drag_threshold = drag_threshold;
108 self
109 }
110
111 pub fn enabled(mut self, enabled: bool) -> Self {
113 self.enabled = enabled;
114 self
115 }
116}
117
118impl<T: Clone + PartialEq> Component for DragZone<T> {
119 fn render(&self) -> impl IntoElement {
120 let mut drags = use_drag::<T>();
121 let mut phase = use_state(|| DragPhase::Idle);
122 let data = self.data.clone();
123 let drag_threshold = self.drag_threshold;
124
125 let on_global_pointer_move = move |e: Event<PointerEventData>| match phase() {
126 DragPhase::Dragging { offset, .. } => {
127 phase.set(DragPhase::Dragging {
128 position: e.global_location(),
129 offset,
130 });
131 }
132 DragPhase::Pressing {
133 press_point,
134 offset,
135 } => {
136 let current = e.global_location();
137 let dx = current.x - press_point.x;
138 let dy = current.y - press_point.y;
139
140 if (dx * dx + dy * dy).sqrt() >= drag_threshold {
141 phase.set(DragPhase::Dragging {
142 position: current,
143 offset,
144 });
145 *drags.write() = Some(data.clone());
146 }
147 }
148 DragPhase::Idle => {}
149 };
150
151 let on_pointer_down = move |e: Event<PointerEventData>| {
152 if !e.data().is_primary() {
153 return;
154 }
155 phase.set(DragPhase::Pressing {
156 press_point: e.global_location(),
157 offset: e.element_location(),
158 });
159 };
160
161 let on_global_pointer_up = move |_: Event<PointerEventData>| {
162 if !matches!(phase(), DragPhase::Idle) {
163 phase.set(DragPhase::Idle);
164 *drags.write() = None;
165 }
166 };
167
168 let dragging = match phase() {
169 DragPhase::Dragging { position, offset } => Some((position, offset)),
170 _ => None,
171 };
172
173 rect()
174 .layout(self.layout.clone())
175 .on_global_pointer_up(on_global_pointer_up)
176 .on_global_pointer_move(on_global_pointer_move)
177 .maybe(self.enabled, |el| el.on_pointer_down(on_pointer_down))
178 .maybe_child((dragging.zip(self.drag_element.clone())).map(
179 |((position, offset), drag_element)| {
180 let (x, y) = (position - offset).to_f32().to_tuple();
181 rect()
182 .position(Position::new_global().left(x + 1.).top(y + 1.))
183 .layer(Layer::Overlay)
184 .interactive(false)
185 .min_width(Size::px(offset.x as f32))
186 .cross_align(Alignment::End)
187 .child(
188 rect()
189 .horizontal()
190 .min_height(Size::px(offset.y as f32))
191 .cross_align(Alignment::End)
192 .child(drag_element),
193 )
194 },
195 ))
196 .maybe(self.show_while_dragging || dragging.is_none(), |el| {
197 el.children(self.children.clone())
198 })
199 }
200
201 fn render_key(&self) -> DiffKey {
202 self.key.clone().or(self.default_key())
203 }
204}
205
206#[derive(PartialEq, Clone)]
217pub struct DropZone<T: 'static + PartialEq + Clone> {
218 children: Vec<Element>,
219 on_drop: EventHandler<T>,
220 on_drag_over: Option<EventHandler<bool>>,
221 layout: LayoutData,
222 key: DiffKey,
223}
224
225impl<T: Clone + PartialEq + 'static> ChildrenExt for DropZone<T> {
226 fn children_mut(&mut self) -> &mut Vec<Element> {
227 &mut self.children
228 }
229}
230
231impl<T: Clone + PartialEq + 'static> KeyExt for DropZone<T> {
232 fn key_mut(&mut self) -> &mut DiffKey {
233 &mut self.key
234 }
235}
236
237impl<T: Clone + PartialEq + 'static> LayoutExt for DropZone<T> {
238 fn layout_mut(&mut self) -> &mut LayoutData {
239 &mut self.layout
240 }
241}
242
243impl<T: Clone + PartialEq + 'static> ContainerExt for DropZone<T> {}
244
245impl<T: PartialEq + Clone + 'static> DropZone<T> {
246 pub fn new(on_drop: impl Into<EventHandler<T>>) -> Self {
248 Self {
249 children: Vec::new(),
250 on_drop: on_drop.into(),
251 on_drag_over: None,
252 layout: LayoutData::default(),
253 key: DiffKey::default(),
254 }
255 }
256
257 pub fn on_drag_over(mut self, on_drag_over: impl Into<EventHandler<bool>>) -> Self {
260 self.on_drag_over = Some(on_drag_over.into());
261 self
262 }
263}
264
265impl<T: Clone + PartialEq + 'static> Component for DropZone<T> {
266 fn render(&self) -> impl IntoElement {
267 let mut drags = use_drag::<T>();
268 let on_drop = self.on_drop.clone();
269 let on_drag_over = self.on_drag_over.clone();
270
271 let on_mouse_up = {
272 let on_drop = on_drop.clone();
273 let on_drag_over = on_drag_over.clone();
274 move |e: Event<MouseEventData>| {
275 e.stop_propagation();
276 let payload = (*drags.read()).clone();
277 if let Some(payload) = payload {
278 on_drop.call(payload);
279 *drags.write() = None;
280 if let Some(on_drag_over) = &on_drag_over {
281 on_drag_over.call(false);
282 }
283 }
284 }
285 };
286
287 let on_touch_end = {
288 let on_drag_over = on_drag_over.clone();
289 move |e: Event<TouchEventData>| {
290 e.stop_propagation();
291 let payload = (*drags.read()).clone();
292 if let Some(payload) = payload {
293 on_drop.call(payload);
294 *drags.write() = None;
295 if let Some(on_drag_over) = &on_drag_over {
296 on_drag_over.call(false);
297 }
298 }
299 }
300 };
301
302 rect()
303 .layout(self.layout.clone())
304 .on_mouse_up(on_mouse_up)
305 .on_touch_end(on_touch_end)
306 .map(on_drag_over, move |el, on_drag_over| {
307 el.on_pointer_enter({
308 let on_drag_over = on_drag_over.clone();
309 move |_| {
310 if drags.read().is_some() {
311 on_drag_over.call(true);
312 }
313 }
314 })
315 .on_pointer_leave(move |_| {
316 if drags.read().is_some() {
317 on_drag_over.call(false);
318 }
319 })
320 })
321 .children(self.children.clone())
322 }
323
324 fn render_key(&self) -> DiffKey {
325 self.key.clone().or(self.default_key())
326 }
327}