use dioxus::prelude::*;
use crate::core::{
transition, use_dnd, use_zone_registry, DragMode, Draggable, DropEffect, DropOutcome,
GestureEffect, GestureEvent, GesturePhase, Point, ZoneId,
};
pub(crate) fn pointer_client(evt: &PointerEvent) -> Point {
let c = evt.client_coordinates();
Point::new(c.x, c.y)
}
#[component]
pub fn PointerDraggable<T: Clone + PartialEq + 'static>(
payload: T,
#[props(default)]
zone: Option<ZoneId>,
#[props(default)]
effect: DropEffect,
#[props(default)]
disabled: bool,
#[props(default)]
label: Option<String>,
#[props(default = 8.0)]
threshold: f64,
#[props(default)]
on_drag_start: Option<EventHandler<()>>,
#[props(default)]
on_drag_end: Option<EventHandler<bool>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let mut dnd = use_dnd::<T>();
let registry = use_zone_registry::<T>();
let mut phase = use_signal(|| GesturePhase::Idle);
let mut step = move |event: GestureEvent, threshold: f64| -> GestureEffect {
let (next, fx) = transition(*phase.peek(), event, threshold);
phase.set(next);
fx
};
let touch_payload = payload.clone();
let mut deliver_to = move |target: ZoneId, point: Point, effect: DropEffect| -> bool {
let Some(record) = registry.get(target) else {
return false;
};
let Some(p) = dnd.payload() else {
return false;
};
if !record.accepts_payload(&p) {
return false;
}
let origin = (*record.rect.peek())
.map(|r| r.origin())
.unwrap_or_default();
if let Some((p, from)) = dnd.take() {
record.on_drop.call(DropOutcome {
payload: p,
from,
to: target,
effect,
client: point,
element: point - origin,
});
return true;
}
false
};
rsx! {
div {
style: "touch-action: none;",
onpointerdown: move |evt: PointerEvent| {
if disabled || !evt.is_primary() || evt.pointer_type() == "mouse" {
return;
}
let _ = step(
GestureEvent::Down { at: pointer_client(&evt), pointer_id: evt.pointer_id() },
threshold,
);
},
onpointermove: move |evt: PointerEvent| {
let event = GestureEvent::Move {
at: pointer_client(&evt),
pointer_id: evt.pointer_id(),
};
match step(event, threshold) {
GestureEffect::Begin { origin, at } => {
dnd.start(
touch_payload.clone(),
zone,
at,
at - origin, effect,
DragMode::Pointer,
);
registry.refresh_rects();
if let Some(h) = &on_drag_start {
h.call(());
}
}
GestureEffect::Track { at } => {
dnd.update_pointer(at);
match registry.hit_test(at) {
Some(z) => dnd.enter(z),
None => {
if let Some(over) = dnd.over() {
dnd.leave(over);
}
}
}
}
_ => {}
}
},
onpointerup: move |evt: PointerEvent| {
let event = GestureEvent::Up {
at: pointer_client(&evt),
pointer_id: evt.pointer_id(),
};
let GestureEffect::Drop { at: point } = step(event, threshold) else {
return; };
if let Some(target) = registry.hit_test(point) {
let dropped = deliver_to(target, point, effect);
if !dropped {
dnd.cancel();
}
if let Some(h) = &on_drag_end {
h.call(dropped);
}
return;
}
let on_drag_end = on_drag_end;
spawn(async move {
registry.measure_all().await;
let target = dnd
.payload()
.and_then(|p| registry.hit_test_closest(point, &p, 48.0));
let dropped = match target {
Some(t) => deliver_to(t, point, effect),
None => false,
};
if !dropped {
dnd.cancel();
}
if let Some(h) = &on_drag_end {
h.call(dropped);
}
});
},
onpointercancel: move |_| {
if step(GestureEvent::Cancel, threshold) == GestureEffect::Abort {
dnd.cancel();
if let Some(h) = &on_drag_end {
h.call(false);
}
}
},
..attributes,
Draggable::<T> {
payload,
zone,
effect,
disabled,
label,
on_drag_start,
on_drag_end,
{children}
}
}
}
}