use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::collections::HashMap;
use dioxus::prelude::*;
use crate::core::components::deliver_drop;
use crate::core::hooks::SettleFlag;
use crate::core::{
use_dnd, use_zone_registry, DndContext, DragMode, DropEffect, Point, Rect, ZoneId, ZoneRegistry,
};
thread_local! {
static SIMS: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
}
#[component]
pub fn DragSimProbe<T: Clone + PartialEq + 'static>(
#[props(default)]
phantom: std::marker::PhantomData<T>,
) -> Element {
let _ = phantom;
let sim = DragSim {
dnd: use_dnd::<T>(),
registry: use_zone_registry::<T>(),
settle: try_use_context::<SettleFlag<T>>(),
};
use_hook(move || {
SIMS.with_borrow_mut(|m| {
m.insert(TypeId::of::<T>(), Box::new(sim));
});
});
rsx! {}
}
pub fn drag_sim<T: Clone + PartialEq + 'static>() -> DragSim<T> {
SIMS.with_borrow(|m| {
m.get(&TypeId::of::<T>())
.and_then(|b| b.downcast_ref::<DragSim<T>>())
.copied()
})
.expect("no DragSim captured: mount DragSimProbe::<T> inside the provider and rebuild first")
}
pub struct DragSim<T: Clone + 'static> {
dnd: DndContext<T>,
registry: ZoneRegistry<T>,
settle: Option<SettleFlag<T>>,
}
impl<T: Clone + 'static> Copy for DragSim<T> {}
impl<T: Clone + 'static> Clone for DragSim<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Clone + PartialEq + 'static> DragSim<T> {
pub fn place(&self, dom: &VirtualDom, zone: ZoneId, rect: Rect) {
dom.in_runtime(|| {
let record = self
.registry
.get(zone)
.unwrap_or_else(|| panic!("place: no zone {} registered", zone.0));
let mut slot = record.rect;
slot.set(Some(rect));
});
}
pub fn pick_up(&mut self, dom: &VirtualDom, payload: T) {
self.pick_up_from(dom, payload, None);
}
pub fn pick_up_from(&mut self, dom: &VirtualDom, payload: T, from: Option<ZoneId>) {
let mut dnd = self.dnd;
dom.in_runtime(|| {
dnd.start(
payload,
from,
Point::default(),
Point::default(),
DropEffect::Move,
DragMode::Pointer,
);
});
}
pub fn move_to(&mut self, dom: &VirtualDom, point: Point) {
let mut dnd = self.dnd;
let registry = self.registry;
dom.in_runtime(|| {
dnd.update_pointer(point);
match registry.hit_test(point) {
Some(z) => dnd.enter(z),
None => {
if let Some(over) = dnd.over() {
dnd.leave(over);
}
}
}
});
}
pub fn release(&mut self, dom: &VirtualDom) -> Option<ZoneId> {
self.release_as(dom, DropEffect::Move)
}
pub fn release_as(&mut self, dom: &VirtualDom, effect: DropEffect) -> Option<ZoneId> {
let mut dnd = self.dnd;
let registry = self.registry;
let settle = self.settle;
dom.in_runtime(|| {
let point = dnd.pointer();
let target = registry.hit_test(point).or_else(|| {
dnd.payload()
.and_then(|p| registry.hit_test_closest(point, &p, 48.0))
});
let delivered = target
.filter(|t| deliver_drop(registry, &mut dnd, settle, *t, point, effect))
.is_some();
if !delivered {
dnd.cancel();
return None;
}
target
})
}
pub fn cancel(&mut self, dom: &VirtualDom) {
let mut dnd = self.dnd;
dom.in_runtime(|| dnd.cancel());
}
pub fn over(&self, dom: &VirtualDom) -> Option<ZoneId> {
dom.in_runtime(|| self.dnd.over())
}
pub fn dragging(&self, dom: &VirtualDom) -> bool {
dom.in_runtime(|| self.dnd.dragging())
}
pub fn payload(&self, dom: &VirtualDom) -> Option<T> {
dom.in_runtime(|| self.dnd.payload())
}
pub fn announcement(&self, dom: &VirtualDom) -> String {
dom.in_runtime(|| self.dnd.announcement())
}
}
pub fn rerender(dom: &mut VirtualDom) {
dom.process_events();
dom.render_immediate(&mut dioxus::core::NoOpMutations);
}
pub fn simulate_drag<T: Clone + PartialEq + 'static>(
dom: &mut VirtualDom,
payload: T,
from: Option<ZoneId>,
path: &[Point],
) -> Option<ZoneId> {
let mut sim = drag_sim::<T>();
sim.pick_up_from(dom, payload, from);
rerender(dom);
for p in path {
sim.move_to(dom, *p);
rerender(dom);
}
let delivered = sim.release(dom);
rerender(dom);
delivered
}