#![doc = include_str!("../docs/api/testing.md")]
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::collections::HashMap;
use dioxus::prelude::*;
use crate::core::components::{deliver_drop, DropCompletion, SettleRoute};
use crate::core::hooks::SettleFlag;
use crate::core::world::{JoinedWindow, WorldHit, WorldMembership};
use crate::core::{
use_dnd, use_zone_registry, DndContext, DropEffect, Point, Rect, WindowKey, 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 completions = use_signal(Vec::<bool>::new);
let completion = use_callback(move |dropped| {
let mut completions = completions;
completions.write().push(dropped);
});
let sim = DragSim {
dnd: use_dnd::<T>(),
registry: use_zone_registry::<T>(),
settle: try_use_context::<SettleFlag<T>>(),
membership: try_use_context::<WorldMembership<T>>().and_then(|m| m.0),
completion,
completions,
};
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>>,
membership: Option<JoinedWindow<T>>,
completion: Callback<bool>,
completions: Signal<Vec<bool>>,
}
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(|| {
assert!(
self.registry.contains(zone),
"place: no zone {} registered",
zone.0
);
let mut registry = self.registry;
registry.set_rect(zone, rect);
});
}
pub fn window_key(&self) -> Option<WindowKey> {
self.membership.map(|j| j.key)
}
pub fn place_in(&self, dom: &VirtualDom, window: WindowKey, zone: ZoneId, rect: Rect) {
let world = self
.membership
.expect("place_in: this provider joined no DndWorld")
.world;
dom.in_runtime(|| {
let rec = world
.record(window)
.unwrap_or_else(|| panic!("place_in: no window {} joined", window.0));
assert!(
rec.registry.contains(zone),
"place_in: no zone {} in window {}",
zone.0,
window.0
);
let mut registry = rec.registry;
registry.set_rect(zone, 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;
let membership = self.membership;
dom.in_runtime(|| {
dnd.start_tracked(
payload,
from,
Point::default(),
Point::default(),
DropEffect::Move,
self.completion,
);
if let Some(j) = membership {
j.world.begin_from(j.key);
}
});
}
pub fn move_to(&mut self, dom: &VirtualDom, point: Point) {
let mut dnd = self.dnd;
let registry = self.registry;
let membership = self.membership;
dom.in_runtime(|| {
dnd.update_pointer(point);
match membership {
Some(joined) => match joined.zone_under(point) {
WorldHit::Zone(location) => joined.enter(location),
WorldHit::Window => joined.clear_hover(),
WorldHit::Unresolved => match registry.hit_test(point) {
Some(zone) => joined.enter(joined.location(zone)),
None => joined.clear_hover(),
},
},
None => match registry.hit_test(point) {
Some(zone) => dnd.enter(zone),
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;
let membership = self.membership;
dom.in_runtime(|| {
let point = dnd.pointer();
let session = dnd.active_session();
if let Some(j) = membership {
let _ = j.zone_under(point);
if let Some((rec, local)) = j.foreign_window_under(point) {
let target = rec.registry.hit_test(local).or_else(|| {
dnd.payload()
.and_then(|p| rec.registry.hit_test_closest(local, &p, 48.0))
});
let delivered = target
.filter(|t| {
deliver_drop(
rec.registry,
&mut dnd,
SettleRoute {
flag: Some(rec.settle),
owner: Some((&j.world, rec.key)),
},
DropCompletion::World {
world: &j.world,
session,
},
*t,
local,
effect,
)
})
.is_some();
if !delivered {
match session {
Some(session) => {
j.world.finish_session(session, false);
}
None => j.world.finish_untracked(false),
}
return None;
}
return target;
}
}
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| match membership {
Some(j) => deliver_drop(
registry,
&mut dnd,
SettleRoute {
flag: settle,
owner: Some((&j.world, j.key)),
},
DropCompletion::World {
world: &j.world,
session,
},
*t,
point,
effect,
),
None => deliver_drop(
registry,
&mut dnd,
SettleRoute {
flag: settle,
owner: None,
},
match session {
Some(session) => DropCompletion::Local(session),
None => DropCompletion::None,
},
*t,
point,
effect,
),
})
.is_some();
if !delivered {
match membership {
Some(j) => match session {
Some(session) => {
j.world.finish_session(session, false);
}
None => j.world.finish_untracked(false),
},
None => match session {
Some(session) => {
dnd.cancel_session(session);
}
None => dnd.cancel(),
},
}
return None;
}
target
})
}
pub fn cancel(&mut self, dom: &VirtualDom) {
let mut dnd = self.dnd;
let membership = self.membership;
dom.in_runtime(|| {
let session = dnd.active_session();
match membership {
Some(j) => match session {
Some(session) => {
j.world.finish_session(session, false);
}
None => j.world.finish_untracked(false),
},
None => match session {
Some(session) => {
dnd.cancel_session(session);
}
None => dnd.cancel(),
},
}
});
}
pub fn completions(&self, dom: &VirtualDom) -> Vec<bool> {
dom.in_runtime(|| self.completions.read().clone())
}
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
}