1use dioxus::prelude::*;
2use freya_elements::{
3 self as dioxus_elements,
4 events::MouseEvent,
5 MouseButton,
6};
7use freya_hooks::use_node_signal;
8use torin::prelude::CursorPoint;
9
10#[derive(Props, Clone, PartialEq)]
12pub struct DragProviderProps {
13 children: Element,
15}
16
17#[allow(non_snake_case)]
19pub fn DragProvider<T: 'static>(DragProviderProps { children }: DragProviderProps) -> Element {
20 use_context_provider::<Signal<Option<T>>>(|| Signal::new(None));
21 rsx!({ children })
22}
23
24#[derive(Props, Clone, PartialEq)]
26pub struct DragZoneProps<T: Clone + 'static + PartialEq> {
27 drag_element: Element,
29 children: Element,
31 data: T,
33 #[props(default = false)]
35 hide_while_dragging: bool,
36}
37
38#[allow(non_snake_case)]
40pub fn DragZone<T: 'static + Clone + PartialEq>(
41 DragZoneProps {
42 data,
43 children,
44 drag_element,
45 hide_while_dragging,
46 }: DragZoneProps<T>,
47) -> Element {
48 let mut drags = use_context::<Signal<Option<T>>>();
49 let mut dragging = use_signal(|| false);
50 let mut pos = use_signal(CursorPoint::default);
51 let (node_reference, size) = use_node_signal();
52
53 let onglobalmousemove = move |e: MouseEvent| {
54 if *dragging.read() {
55 let size = size.read();
56 let coord = e.get_screen_coordinates();
57 pos.set(
58 (
59 coord.x - size.area.min_x() as f64,
60 coord.y - size.area.min_y() as f64,
61 )
62 .into(),
63 );
64 }
65 };
66
67 let onmousedown = move |e: MouseEvent| {
68 if e.data.trigger_button != Some(MouseButton::Left) {
69 return;
70 }
71 let size = size.read();
72 let coord = e.get_screen_coordinates();
73 pos.set(
74 (
75 coord.x - size.area.min_x() as f64,
76 coord.y - size.area.min_y() as f64,
77 )
78 .into(),
79 );
80 dragging.set(true);
81 *drags.write() = Some(data.clone());
82 };
83
84 let onglobalclick = move |_: MouseEvent| {
85 if *dragging.read() {
86 dragging.set(false);
87 pos.set((0.0, 0.0).into());
88 *drags.write() = None;
89 }
90 };
91
92 rsx!(
93 rect {
94 reference: node_reference,
95 onglobalclick,
96 onglobalmousemove,
97 onmousedown,
98 if *dragging.read() {
99 rect {
100 position: "absolute",
101 width: "0",
102 height: "0",
103 offset_x: "{pos.read().x}",
104 offset_y: "{pos.read().y}",
105 {drag_element}
106 }
107 }
108 if !hide_while_dragging || !dragging() {
109 {children}
110 }
111 }
112 )
113}
114
115#[derive(Props, PartialEq, Clone)]
117pub struct DropZoneProps<T: 'static + PartialEq + Clone> {
118 children: Element,
120 ondrop: EventHandler<T>,
122 #[props(default = "auto".to_string())]
124 width: String,
125 #[props(default = "auto".to_string())]
127 height: String,
128}
129
130#[allow(non_snake_case)]
132pub fn DropZone<T: 'static + Clone + PartialEq>(props: DropZoneProps<T>) -> Element {
133 let mut drags = use_context::<Signal<Option<T>>>();
134
135 let onmouseup = move |e: MouseEvent| {
136 e.stop_propagation();
137 if let Some(current_drags) = &*drags.read() {
138 props.ondrop.call(current_drags.clone());
139 }
140 if drags.read().is_some() {
141 *drags.write() = None;
142 }
143 };
144
145 rsx!(
146 rect {
147 onmouseup,
148 width: props.width,
149 height: props.height,
150 {props.children}
151 }
152 )
153}
154
155#[cfg(test)]
156mod test {
157 use freya::prelude::*;
158 use freya_testing::prelude::*;
159
160 #[tokio::test]
161 pub async fn drag_drop() {
162 fn drop_app() -> Element {
163 let mut state = use_signal::<bool>(|| false);
164
165 rsx!(
166 DragProvider::<bool> {
167 rect {
168 height: "50%",
169 width: "100%",
170 DragZone {
171 data: true,
172 drag_element: rsx!(
173 label {
174 width: "200",
175 "Moving"
176 }
177 ),
178 label {
179 "Move"
180 }
181 }
182 }
183 DropZone {
184 ondrop: move |data: bool| {
185 state.set(data);
186 },
187 rect {
188 height: "50%",
189 width: "100%",
190 label {
191 "Enabled: {state.read()}"
192 }
193 }
194 }
195 }
196 )
197 }
198
199 let mut utils = launch_test(drop_app);
200 let root = utils.root();
201 utils.wait_for_update().await;
202
203 utils.push_event(TestEvent::Mouse {
204 name: EventName::MouseDown,
205 cursor: (5.0, 5.0).into(),
206 button: Some(MouseButton::Left),
207 });
208
209 utils.wait_for_update().await;
210
211 utils.move_cursor((5., 5.)).await;
212
213 utils.move_cursor((5., 300.)).await;
214
215 assert_eq!(
216 root.get(0).get(0).get(0).get(0).get(0).text(),
217 Some("Moving")
218 );
219 assert_eq!(root.get(0).get(0).get(1).get(0).text(), Some("Move"));
220
221 utils.push_event(TestEvent::Mouse {
222 name: EventName::MouseUp,
223 cursor: (5.0, 300.0).into(),
224 button: Some(MouseButton::Left),
225 });
226
227 utils.wait_for_update().await;
228
229 assert_eq!(
230 root.get(1).get(0).get(0).get(0).text(),
231 Some("Enabled: true")
232 );
233 }
234
235 #[tokio::test]
236 pub async fn drag_drop_hide_while_dragging() {
237 fn drop_app() -> Element {
238 let mut state = use_signal::<bool>(|| false);
239
240 rsx!(
241 DragProvider::<bool> {
242 rect {
243 height: "50%",
244 width: "100%",
245 DragZone {
246 data: true,
247 hide_while_dragging: true,
248 drag_element: rsx!(
249 label {
250 width: "200",
251 "Moving"
252 }
253 ),
254 label {
255 "Move"
256 }
257 }
258 },
259 DropZone {
260 ondrop: move |data: bool| {
261 state.set(data);
262 },
263 rect {
264 height: "50%",
265 width: "100%",
266 label {
267 "Enabled: {state.read()}"
268 }
269 }
270 }
271 }
272 )
273 }
274
275 let mut utils = launch_test(drop_app);
276 let root = utils.root();
277 utils.wait_for_update().await;
278
279 utils.push_event(TestEvent::Mouse {
280 name: EventName::MouseDown,
281 cursor: (5.0, 5.0).into(),
282 button: Some(MouseButton::Left),
283 });
284
285 utils.wait_for_update().await;
286
287 utils.move_cursor((5., 5.)).await;
288
289 utils.move_cursor((5., 300.)).await;
290
291 assert_eq!(
292 root.get(0).get(0).get(0).get(0).get(0).text(),
293 Some("Moving")
294 );
295 assert!(!root.get(0).get(0).get(1).is_visible());
296
297 utils.push_event(TestEvent::Mouse {
298 name: EventName::MouseUp,
299 cursor: (5.0, 300.0).into(),
300 button: Some(MouseButton::Left),
301 });
302
303 utils.wait_for_update().await;
304
305 assert_eq!(
306 root.get(1).get(0).get(0).get(0).text(),
307 Some("Enabled: true")
308 );
309 }
310}