use std::cell::RefCell;
use std::rc::Rc;
use dioxus::html::MountedData;
use dioxus::prelude::*;
use super::registry::{RectRefresh, ZoneRecord, ZoneRegistration, ZoneRegistry};
use super::state::{DndContext, DragState};
use super::types::{DragId, DropOutcome, Point, Rect, ZoneId};
use super::world::{use_joined_window, DndWorld, JoinedWindow, WindowGeometry, WorldMembership};
pub(crate) struct SettleFlag<T> {
armed: Signal<Option<u64>>,
marker: std::marker::PhantomData<T>,
}
impl<T> Copy for SettleFlag<T> {}
impl<T> Clone for SettleFlag<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> SettleFlag<T> {
pub(crate) fn arm(self, capability: u64) {
let mut armed = self.armed;
if let Ok(mut value) = armed.try_write() {
if *value != Some(capability) {
*value = Some(capability);
}
};
}
pub(crate) fn is_armed(self) -> bool {
matches!(self.armed.try_peek().as_deref(), Ok(Some(_)))
}
pub(crate) fn release(self, capability: u64) -> bool {
let mut armed = self.armed;
let Ok(mut value) = armed.try_write() else {
return false;
};
if *value != Some(capability) {
return false;
}
*value = None;
true
}
}
pub fn use_dnd_provider<T: Clone + 'static>() -> DndContext<T> {
let state = use_store(DragState::<T>::default);
let announcement = use_signal(String::new);
let registry = use_context_provider(|| ZoneRegistry::<T>::from_signal(Signal::new(Vec::new())));
let settle_flag = use_context_provider(|| SettleFlag::<T> {
armed: Signal::new(None),
marker: std::marker::PhantomData,
});
let membership = use_hook(move || {
let joined = try_consume_context::<DndWorld<T>>()
.filter(|_| try_consume_context::<WorldMembership<T>>().is_none())
.map(|world| {
let geometry = try_consume_context::<WindowGeometry>().unwrap_or_default();
let key = world.join(
geometry,
registry,
settle_flag,
Callback::new(move |_| registry.refresh_rects()),
);
JoinedWindow {
world,
key,
geometry,
}
});
provide_context(WorldMembership::<T>(joined));
joined
});
use_drop(move || {
if let Some(j) = membership {
j.world.leave(j.key);
}
});
let ctx = use_context_provider(move || match membership {
Some(j) => j.world.context(),
None => DndContext::from_parts(state, announcement),
});
use_rect_refresh_provider();
use_rect_refresh_thunk(move |_| {
if ctx.dragging() {
registry.refresh_rects();
}
});
ctx
}
pub(crate) fn use_rect_refresh_provider() -> RectRefresh {
let bus = use_hook(|| {
try_consume_context::<RectRefresh>()
.unwrap_or_else(|| RectRefresh::from_signal(Signal::new(Vec::new())))
});
use_context_provider(|| bus);
bus
}
pub(crate) fn use_rect_refresh_thunk(thunk: impl FnMut(()) + 'static) {
let joined = use_hook(move || {
try_consume_context::<RectRefresh>().map(|mut bus| {
let key = DragId::auto().0;
bus.register(key, Callback::new(thunk));
(bus, key)
})
});
use_drop(move || {
if let Some((mut bus, key)) = joined {
bus.unregister(key);
}
});
}
pub fn use_rect_refresh() -> RectRefresh {
use_context()
}
pub fn use_dnd<T: Clone + 'static>() -> DndContext<T> {
use_context()
}
pub fn use_zone_registry<T: Clone + 'static>() -> ZoneRegistry<T> {
use_context()
}
pub fn use_zone_id() -> ZoneId {
use_hook(ZoneId::auto)
}
#[derive(Clone, Default)]
pub struct BridgeGeometry {
writers: Rc<RefCell<Vec<BridgeGeometryWriter>>>,
}
#[derive(Clone)]
struct BridgeGeometryWriter {
mounted: Rc<dyn Fn(Rc<MountedData>)>,
rect: Rc<dyn Fn(Rect)>,
}
impl std::fmt::Debug for BridgeGeometry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BridgeGeometry")
.field("worlds", &self.writers.borrow().len())
.finish()
}
}
impl PartialEq for BridgeGeometry {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.writers, &other.writers)
}
}
impl BridgeGeometry {
pub fn set_mounted(&self, mounted: &Rc<MountedData>) {
for writer in self.writers.borrow().iter() {
(writer.mounted)(mounted.clone());
}
}
pub fn set_rect_if_present(&self, rect: Rect) {
for writer in self.writers.borrow().iter() {
(writer.rect)(rect);
}
}
fn register<T: Clone + 'static>(
&self,
registry: ZoneRegistry<T>,
registration: ZoneRegistration,
) {
self.writers.borrow_mut().push(BridgeGeometryWriter {
mounted: Rc::new(move |mounted| {
let mut registry = registry;
registry.set_mounted(registration, mounted);
}),
rect: Rc::new(move |rect| {
let mut registry = registry;
registry.set_rect_if_present(registration, rect);
}),
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BridgeWorld {
pub active: bool,
pub over: bool,
}
pub fn use_bridge_world<T: Clone + PartialEq + 'static>(
zone_id: ZoneId,
parent: Option<ZoneId>,
label: Option<String>,
accepts: Option<Callback<T, bool>>,
on_drop: EventHandler<DropOutcome<T>>,
geometry: BridgeGeometry,
) -> BridgeWorld {
let dnd = use_dnd::<T>();
let joined = use_joined_window::<T>();
let mut reg = use_zone_registry::<T>();
let registration = use_hook(|| {
reg.register(ZoneRecord {
id: zone_id,
parent,
label: label.clone(),
on_drop: Callback::new(move |o| on_drop.call(o)),
accepts,
mounted: None,
rect: None,
})
});
use_drop(move || reg.unregister(zone_id));
reg.sync_label(zone_id, label);
use_hook(move || {
geometry.register(reg, registration);
});
let acceptable = match dnd.payload() {
Some(p) => accepts.map(|cb| cb.call(p)).unwrap_or(true),
None => false,
};
BridgeWorld {
active: dnd.dragging() && acceptable,
over: match joined {
Some(joined) => joined.is_over(zone_id),
None => dnd.over() == Some(zone_id),
} && acceptable,
}
}
pub fn client_point(evt: &DragEvent) -> Point {
let c = evt.client_coordinates();
Point::new(c.x, c.y)
}
pub fn element_point(evt: &DragEvent) -> Point {
let c = evt.element_coordinates();
Point::new(c.x, c.y)
}