use dioxus::html::MountedData;
use dioxus::prelude::*;
use std::rc::Rc;
use crate::core::hooks::{
use_bridge_world, use_dnd, use_zone_id, use_zone_registry, BridgeGeometry,
};
use crate::core::registry::ZoneRecord;
use crate::core::types::{edge_of, DragMode, DropOutcome, EdgeSet, Rect, ZoneId};
use crate::core::world::use_joined_window;
#[derive(Clone, Copy, PartialEq)]
pub struct ParentZone(pub ZoneId);
#[component]
pub fn DropZone<T: Clone + PartialEq + 'static>(
#[props(default)]
id: Option<ZoneId>,
#[props(default)]
label: Option<String>,
#[props(default)]
accepts: Option<Callback<T, bool>>,
#[props(default)]
edge: Option<EdgeSet>,
on_drop: EventHandler<DropOutcome<T>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let dnd = use_dnd::<T>();
let joined = use_joined_window::<T>();
let mut registry = use_zone_registry::<T>();
let auto_id = use_zone_id();
let zone_id = id.unwrap_or(auto_id);
let parent = try_use_context::<ParentZone>().map(|p| p.0);
use_context_provider(|| ParentZone(zone_id));
let registration = use_hook(|| {
registry.register(ZoneRecord {
id: zone_id,
parent,
label: label.clone(),
on_drop: Callback::new(move |mut o: DropOutcome<T>| {
if let Some(set) = edge {
if o.mode == DragMode::Pointer {
if let Some(r) = registry.cached_rect(zone_id) {
o.edge = Some(edge_of(o.client, r, set));
}
}
}
on_drop.call(o)
}),
accepts,
mounted: None,
rect: None,
})
});
use_drop(move || {
registry.unregister(zone_id);
});
registry.sync_label(zone_id, label.clone());
let acceptable = move || -> bool {
match dnd.payload() {
Some(p) => accepts.map(|cb| cb.call(p)).unwrap_or(true),
None => false,
}
};
let is_over = move || match joined {
Some(joined) => joined.is_over(zone_id),
None => dnd.over() == Some(zone_id),
};
let live_edge = move || -> Option<&'static str> {
let set = edge?;
if !is_over() || dnd.mode() != DragMode::Pointer || !acceptable() {
return None;
}
let r = registry.cached_rect(zone_id)?;
let pointer = joined
.and_then(|joined| joined.local_pointer())
.unwrap_or_else(|| dnd.pointer());
Some(edge_of(pointer, r, set).as_str())
};
rsx! {
div {
"data-active": if dnd.dragging() && acceptable() { "true" },
"data-over": if is_over() && acceptable() { "true" },
"data-edge": live_edge(),
onmounted: move |evt: Event<MountedData>| {
let m: Rc<MountedData> = evt.data();
let mut registry = registry;
registry.set_mounted(registration, m.clone());
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
registry.set_rect_if_present(registration, Rect::new(
r.origin.x,
r.origin.y,
r.size.width,
r.size.height,
));
}
});
},
..attributes,
{children}
}
}
}
#[component]
pub fn BridgeDropZone<A: Clone + PartialEq + 'static, B: Clone + PartialEq + 'static>(
#[props(default)]
id: Option<ZoneId>,
#[props(default)]
label: Option<String>,
#[props(default)]
accepts_a: Option<Callback<A, bool>>,
#[props(default)]
accepts_b: Option<Callback<B, bool>>,
on_drop_a: EventHandler<DropOutcome<A>>,
on_drop_b: EventHandler<DropOutcome<B>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let auto_id = use_zone_id();
let zone_id = id.unwrap_or(auto_id);
let parent = try_use_context::<ParentZone>().map(|p| p.0);
use_context_provider(|| ParentZone(zone_id));
let geometry = use_hook(BridgeGeometry::default);
let a = use_bridge_world::<A>(
zone_id,
parent,
label.clone(),
accepts_a,
on_drop_a,
geometry.clone(),
);
let b = use_bridge_world::<B>(
zone_id,
parent,
label,
accepts_b,
on_drop_b,
geometry.clone(),
);
rsx! {
div {
"data-active": if a.active || b.active { "true" },
"data-over": if a.over || b.over { "true" },
onmounted: move |evt: Event<MountedData>| {
let m: Rc<MountedData> = evt.data();
geometry.set_mounted(&m);
let geometry = geometry.clone();
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
let rect = Rect::new(
r.origin.x,
r.origin.y,
r.size.width,
r.size.height,
);
geometry.set_rect_if_present(rect);
}
});
},
..attributes,
{children}
}
}
}
#[macro_export]
macro_rules! bridge_drop_zone {
(
$(#[$meta:meta])*
$vis:vis $name:ident {
$( ($ty:ty, $accepts:ident, $on_drop:ident) ),+ $(,)?
}
) => {
$(#[$meta])*
#[::dioxus::prelude::component]
#[allow(non_snake_case)]
$vis fn $name(
#[props(default)]
id: ::std::option::Option<$crate::core::ZoneId>,
#[props(default)]
label: ::std::option::Option<::std::string::String>,
$(
#[props(default)]
$accepts: ::std::option::Option<::dioxus::prelude::Callback<$ty, bool>>,
$on_drop: ::dioxus::prelude::EventHandler<$crate::core::DropOutcome<$ty>>,
)+
#[props(extends = div, extends = GlobalAttributes)]
attributes: ::std::vec::Vec<::dioxus::prelude::Attribute>,
children: ::dioxus::prelude::Element,
) -> ::dioxus::prelude::Element {
use ::dioxus::prelude::*;
let auto_id = $crate::core::use_zone_id();
let zone_id = id.unwrap_or(auto_id);
let parent = try_use_context::<$crate::core::ParentZone>().map(|p| p.0);
use_context_provider(|| $crate::core::ParentZone(zone_id));
let geometry = use_hook($crate::core::BridgeGeometry::default);
let mut active = false;
let mut over = false;
$(
let world = $crate::core::use_bridge_world::<$ty>(
zone_id,
parent,
label.clone(),
$accepts,
$on_drop,
geometry.clone(),
);
active |= world.active;
over |= world.over;
)+
rsx! {
div {
"data-active": if active { "true" },
"data-over": if over { "true" },
onmounted: move |evt: Event<::dioxus::html::MountedData>| {
let m = evt.data();
geometry.set_mounted(&m);
let geometry = geometry.clone();
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
let rect = $crate::core::Rect::new(
r.origin.x,
r.origin.y,
r.size.width,
r.size.height,
);
geometry.set_rect_if_present(rect);
}
});
},
..attributes,
{children}
}
}
}
};
}