Skip to main content

freya_components/
drag_drop.rs

1use freya_core::{
2    prelude::*,
3    scope_id::ScopeId,
4};
5use torin::prelude::*;
6
7#[derive(Clone, Copy)]
8enum DragPhase {
9    Idle,
10    Pressing(CursorPoint),
11    Dragging(CursorPoint),
12}
13
14fn use_drag<T: 'static>() -> State<Option<T>> {
15    match try_consume_root_context() {
16        Some(s) => s,
17        None => {
18            let state = State::<Option<T>>::create_in_scope(None, ScopeId::ROOT);
19            provide_context_for_scope_id(state, ScopeId::ROOT);
20            state
21        }
22    }
23}
24
25/// Properties for the [`DragZone`] component.
26#[derive(Clone, PartialEq)]
27pub struct DragZone<T: Clone + 'static + PartialEq> {
28    /// Element visible when dragging the element. This follows the cursor.
29    drag_element: Option<Element>,
30    /// Inner children for the DropZone.
31    children: Element,
32    /// Data that will be handled to the destination [`DropZone`].
33    data: T,
34    /// Show the children when dragging. Defaults to `true`.
35    show_while_dragging: bool,
36    /// Minimum distance in pixels the cursor must move before dragging starts. Defaults to `4.0`.
37    drag_threshold: f64,
38    key: DiffKey,
39}
40
41impl<T: Clone + PartialEq + 'static> KeyExt for DragZone<T> {
42    fn write_key(&mut self) -> &mut DiffKey {
43        &mut self.key
44    }
45}
46
47impl<T: Clone + PartialEq + 'static> DragZone<T> {
48    pub fn new(data: T, children: impl Into<Element>) -> Self {
49        Self {
50            data,
51            children: children.into(),
52            drag_element: None,
53            show_while_dragging: true,
54            drag_threshold: 4.0,
55            key: DiffKey::default(),
56        }
57    }
58
59    pub fn show_while_dragging(mut self, show_while_dragging: bool) -> Self {
60        self.show_while_dragging = show_while_dragging;
61        self
62    }
63
64    pub fn drag_element(mut self, drag_element: impl Into<Element>) -> Self {
65        self.drag_element = Some(drag_element.into());
66        self
67    }
68
69    pub fn drag_threshold(mut self, drag_threshold: f64) -> Self {
70        self.drag_threshold = drag_threshold;
71        self
72    }
73}
74
75impl<T: Clone + PartialEq> Component for DragZone<T> {
76    fn render(&self) -> impl IntoElement {
77        let mut drags = use_drag::<T>();
78        let mut phase = use_state(|| DragPhase::Idle);
79        let data = self.data.clone();
80        let drag_threshold = self.drag_threshold;
81
82        let on_global_pointer_move = move |e: Event<PointerEventData>| match phase() {
83            DragPhase::Dragging(_) => {
84                phase.set(DragPhase::Dragging(e.global_location()));
85            }
86            DragPhase::Pressing(press_point) => {
87                let current = e.global_location();
88                let dx = current.x - press_point.x;
89                let dy = current.y - press_point.y;
90
91                if (dx * dx + dy * dy).sqrt() >= drag_threshold {
92                    phase.set(DragPhase::Dragging(current));
93                    *drags.write() = Some(data.clone());
94                }
95            }
96            DragPhase::Idle => {}
97        };
98
99        let on_pointer_down = move |e: Event<PointerEventData>| {
100            if e.data().button() != Some(MouseButton::Left) {
101                return;
102            }
103            phase.set(DragPhase::Pressing(e.global_location()));
104        };
105
106        let on_global_pointer_press = move |_: Event<PointerEventData>| {
107            if !matches!(phase(), DragPhase::Idle) {
108                phase.set(DragPhase::Idle);
109                *drags.write() = None;
110            }
111        };
112
113        let dragging_position = match phase() {
114            DragPhase::Dragging(pos) => Some(pos),
115            _ => None,
116        };
117
118        rect()
119            .on_global_pointer_press(on_global_pointer_press)
120            .on_global_pointer_move(on_global_pointer_move)
121            .on_pointer_down(on_pointer_down)
122            .maybe_child((dragging_position.zip(self.drag_element.clone())).map(
123                |(position, drag_element)| {
124                    let (x, y) = position.to_f32().to_tuple();
125                    rect()
126                        .position(Position::new_global())
127                        .width(Size::px(0.))
128                        .height(Size::px(0.))
129                        // Extend by 1. so that the cursor click can reach the drop zone
130                        .offset_x(x + 1.)
131                        .offset_y(y + 1.)
132                        .child(drag_element)
133                },
134            ))
135            .maybe_child(
136                (self.show_while_dragging || dragging_position.is_none())
137                    .then(|| self.children.clone()),
138            )
139    }
140
141    fn render_key(&self) -> DiffKey {
142        self.key.clone().or(self.default_key())
143    }
144}
145
146#[derive(PartialEq, Clone)]
147pub struct DropZone<T: 'static + PartialEq + Clone> {
148    children: Element,
149    on_drop: EventHandler<T>,
150    width: Size,
151    height: Size,
152    key: DiffKey,
153}
154
155impl<T: Clone + PartialEq + 'static> KeyExt for DropZone<T> {
156    fn write_key(&mut self) -> &mut DiffKey {
157        &mut self.key
158    }
159}
160
161impl<T: PartialEq + Clone + 'static> DropZone<T> {
162    pub fn new(children: impl Into<Element>, on_drop: impl Into<EventHandler<T>>) -> Self {
163        Self {
164            children: children.into(),
165            on_drop: on_drop.into(),
166            width: Size::auto(),
167            height: Size::auto(),
168            key: DiffKey::default(),
169        }
170    }
171}
172
173impl<T: Clone + PartialEq + 'static> Component for DropZone<T> {
174    fn render(&self) -> impl IntoElement {
175        let mut drags = use_drag::<T>();
176        let on_drop = self.on_drop.clone();
177
178        let on_mouse_up = move |e: Event<MouseEventData>| {
179            e.stop_propagation();
180            if let Some(current_drags) = &*drags.read() {
181                on_drop.call(current_drags.clone());
182            }
183            if drags.read().is_some() {
184                *drags.write() = None;
185            }
186        };
187
188        rect()
189            .on_mouse_up(on_mouse_up)
190            .width(self.width.clone())
191            .height(self.height.clone())
192            .child(self.children.clone())
193    }
194
195    fn render_key(&self) -> DiffKey {
196        self.key.clone().or(self.default_key())
197    }
198}