freya_components/
drag_drop.rs

1use freya_core::{
2    prelude::*,
3    scope_id::ScopeId,
4};
5use torin::prelude::*;
6
7fn use_drag<T: 'static>() -> State<Option<T>> {
8    match try_consume_root_context() {
9        Some(s) => s,
10        None => {
11            let state = State::<Option<T>>::create_in_scope(None, ScopeId::ROOT);
12            provide_context_for_scope_id(state, ScopeId::ROOT);
13            state
14        }
15    }
16}
17
18/// Properties for the [`DragZone`] component.
19#[derive(Clone, PartialEq)]
20pub struct DragZone<T: Clone + 'static + PartialEq> {
21    /// Element visible when dragging the element. This follows the cursor.
22    drag_element: Option<Element>,
23    /// Inner children for the DropZone.
24    children: Element,
25    /// Data that will be handled to the destination [`DropZone`].
26    data: T,
27    /// Show the children when dragging. Defaults to `true`.
28    show_while_dragging: bool,
29}
30
31impl<T: Clone + PartialEq + 'static> DragZone<T> {
32    pub fn new(data: T, children: impl Into<Element>) -> Self {
33        Self {
34            data,
35            children: children.into(),
36            drag_element: None,
37            show_while_dragging: true,
38        }
39    }
40
41    pub fn show_while_dragging(mut self, show_while_dragging: bool) -> Self {
42        self.show_while_dragging = show_while_dragging;
43        self
44    }
45
46    pub fn drag_element(mut self, drag_element: impl Into<Element>) -> Self {
47        self.drag_element = Some(drag_element.into());
48        self
49    }
50}
51
52impl<T: Clone + PartialEq> Render for DragZone<T> {
53    fn render(&self) -> impl IntoElement {
54        let mut drags = use_drag::<T>();
55        let mut position = use_state::<Option<CursorPoint>>(|| None);
56        let data = self.data.clone();
57
58        let on_global_mouse_move = move |e: Event<MouseEventData>| {
59            if position.read().is_some() {
60                position.set(Some(e.global_location));
61            }
62        };
63
64        let on_pointer_down = move |e: Event<PointerEventData>| {
65            if e.data().button() != Some(MouseButton::Left) {
66                return;
67            }
68            position.set(Some(e.global_location()));
69            *drags.write() = Some(data.clone());
70        };
71
72        let on_global_mouse_up = move |_: Event<MouseEventData>| {
73            if position.read().is_some() {
74                position.set(None);
75                *drags.write() = None;
76            }
77        };
78
79        rect()
80            .on_global_mouse_up(on_global_mouse_up)
81            .on_global_mouse_move(on_global_mouse_move)
82            .on_pointer_down(on_pointer_down)
83            .maybe_child((position.read().zip(self.drag_element.clone())).map(
84                |(position, drag_element)| {
85                    let (x, y) = position.to_f32().to_tuple();
86                    rect()
87                        .position(Position::new_global())
88                        .width(Size::px(0.))
89                        .height(Size::px(0.))
90                        // Extend by 1. so that the cursor click can reach the drop zone
91                        .offset_x(x + 1.)
92                        .offset_y(y + 1.)
93                        .child(drag_element)
94                },
95            ))
96            .maybe_child(
97                (self.show_while_dragging || position.read().is_none())
98                    .then(|| self.children.clone()),
99            )
100    }
101}
102
103#[derive(PartialEq, Clone)]
104pub struct DropZone<T: 'static + PartialEq + Clone> {
105    children: Element,
106    on_drop: EventHandler<T>,
107    width: Size,
108    height: Size,
109}
110
111impl<T: PartialEq + Clone + 'static> DropZone<T> {
112    pub fn new(children: impl Into<Element>, on_drop: impl Into<EventHandler<T>>) -> Self {
113        Self {
114            children: children.into(),
115            on_drop: on_drop.into(),
116            width: Size::auto(),
117            height: Size::auto(),
118        }
119    }
120}
121
122impl<T: Clone + PartialEq + 'static> Render for DropZone<T> {
123    fn render(&self) -> impl IntoElement {
124        let mut drags = use_drag::<T>();
125        let on_drop = self.on_drop.clone();
126
127        let on_mouse_up = move |e: Event<MouseEventData>| {
128            e.stop_propagation();
129            if let Some(current_drags) = &*drags.read() {
130                on_drop.call(current_drags.clone());
131            }
132            if drags.read().is_some() {
133                *drags.write() = None;
134            }
135        };
136
137        rect()
138            .on_mouse_up(on_mouse_up)
139            .width(self.width.clone())
140            .height(self.height.clone())
141            .child(self.children.clone())
142    }
143}