use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_elements::events::MouseEvent;
use freya_hooks::use_node_ref;
use torin::prelude::CursorPoint;
#[derive(Props)]
pub struct DragProviderProps<'a> {
children: Element<'a>,
}
#[allow(non_snake_case)]
pub fn DragProvider<'a, T: 'static>(cx: Scope<'a, DragProviderProps<'a>>) -> Element<'a> {
use_shared_state_provider::<Option<T>>(cx, || None);
render!(&cx.props.children)
}
#[derive(Props)]
pub struct DragZoneProps<'a, T> {
drag_element: Element<'a>,
children: Element<'a>,
data: T,
}
#[allow(non_snake_case)]
pub fn DragZone<'a, T: 'static + Clone>(cx: Scope<'a, DragZoneProps<'a, T>>) -> Element<'a> {
let drags = use_shared_state::<Option<T>>(cx);
let dragging = use_state(cx, || false);
let pos = use_state(cx, CursorPoint::default);
let (node_reference, size) = use_node_ref(cx);
let onglobalmouseover = move |e: MouseEvent| {
if *dragging.get() {
let size = size.read();
let coord = e.get_screen_coordinates();
pos.set(
(
coord.x - size.area.min_x() as f64,
coord.y - size.area.min_y() as f64,
)
.into(),
);
}
};
let onmousedown = move |e: MouseEvent| {
let size = size.read();
let coord = e.get_screen_coordinates();
pos.set(
(
coord.x - size.area.min_x() as f64,
coord.y - size.area.min_y() as f64,
)
.into(),
);
dragging.set(true);
*drags.unwrap().write() = Some(cx.props.data.clone());
};
let onglobalclick = move |_: MouseEvent| {
if *dragging.get() {
dragging.set(false);
pos.set((0.0, 0.0).into());
*drags.unwrap().write() = None;
}
};
render!(
if *dragging.get() {
render!(
rect {
width: "0",
height: "0",
offset_x: "{pos.x}",
offset_y: "{pos.y}",
&cx.props.drag_element
}
)
}
rect {
reference: node_reference,
onglobalclick: onglobalclick,
onglobalmouseover: onglobalmouseover,
onmousedown: onmousedown,
&cx.props.children
}
)
}
#[derive(Props)]
pub struct DropZoneProps<'a, T> {
children: Element<'a>,
ondrop: EventHandler<'a, T>,
}
#[allow(non_snake_case)]
pub fn DropZone<'a, T: 'static + Clone>(cx: Scope<'a, DropZoneProps<'a, T>>) -> Element<'a> {
let drags = use_shared_state::<Option<T>>(cx);
let onclick = move |_: MouseEvent| {
if let Some(drags) = drags {
if let Some(current_drags) = &*drags.read() {
cx.props.ondrop.call(current_drags.clone());
}
if drags.read().is_some() {
*drags.write() = None;
}
}
};
render!(
rect {
onclick: onclick,
&cx.props.children
}
)
}
#[cfg(test)]
mod test {
use freya::prelude::*;
use freya_testing::{launch_test, FreyaEvent, MouseButton};
#[tokio::test]
pub async fn drag_drop() {
fn drop_app(cx: Scope) -> Element {
let state = use_state::<bool>(cx, || false);
render!(
DragProvider::<bool> {
rect {
height: "50%",
width: "100%",
DragZone {
data: true,
drag_element: render!(
label {
width: "200",
"Moving"
}
),
label {
"Move"
}
}
},
DropZone {
ondrop: move |data: bool| {
state.set(data);
},
rect {
height: "50%",
width: "100%",
label {
"Enabled: {state}"
}
}
}
}
)
}
let mut utils = launch_test(drop_app);
let root = utils.root();
utils.wait_for_update().await;
utils.push_event(FreyaEvent::Mouse {
name: "mousedown".to_string(),
cursor: (5.0, 5.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
utils.push_event(FreyaEvent::Mouse {
name: "mouseover".to_string(),
cursor: (5.0, 5.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
utils.push_event(FreyaEvent::Mouse {
name: "mouseover".to_string(),
cursor: (5.0, 300.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
assert_eq!(root.get(0).get(0).get(0).get(0).text(), Some("Moving"));
utils.push_event(FreyaEvent::Mouse {
name: "click".to_string(),
cursor: (5.0, 300.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
assert_eq!(
root.get(1).get(0).get(0).get(0).text(),
Some("Enabled: true")
);
}
}