use std::cell::RefCell;
use std::mem::ManuallyDrop;
use dioxus::prelude::*;
use crate::core::hooks::SettleFlag;
use crate::core::model::DndScope;
use crate::core::monitor::CancelReason;
use crate::core::registry::ZoneRegistry;
use crate::core::state::{DndContext, DragState};
use crate::core::types::{Point, ZoneId};
use super::drag::ActiveDrag;
use super::geometry::{WindowGeometry, WindowKey};
use super::settle::SettleClaim;
thread_local! {
static WORLD_OWNERS: RefCell<Vec<ManuallyDrop<DndScope>>> = const { RefCell::new(Vec::new()) };
}
fn default_bridging(no_bridge: Option<&str>) -> bool {
match no_bridge {
None | Some("") | Some("0") => true,
Some(_) => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ZoneLocation {
pub window: WindowKey,
pub zone: ZoneId,
}
pub struct WindowRecord<T: Clone + 'static> {
pub key: WindowKey,
pub geometry: WindowGeometry,
pub registry: ZoneRegistry<T>,
pub(crate) settle: SettleFlag<T>,
pub(crate) refresh: Callback<()>,
}
impl<T: Clone + 'static> Copy for WindowRecord<T> {}
impl<T: Clone + 'static> Clone for WindowRecord<T> {
fn clone(&self) -> Self {
*self
}
}
pub struct DndWorld<T: Clone + 'static> {
pub(super) ctx: DndContext<T>,
windows: Signal<Vec<WindowRecord<T>>>,
pub(super) active: Signal<Option<ActiveDrag>>,
pub(super) global_pointer: Signal<Option<Point>>,
pub(super) over_location: Signal<Option<ZoneLocation>>,
pub(super) settle_claim: Signal<Option<SettleClaim>>,
bridging: Signal<bool>,
}
impl<T: Clone + 'static> Copy for DndWorld<T> {}
impl<T: Clone + 'static> Clone for DndWorld<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Clone + 'static> PartialEq for DndWorld<T> {
fn eq(&self, other: &Self) -> bool {
self.windows == other.windows
}
}
impl<T: Clone + 'static> DndWorld<T> {
pub fn new() -> Self {
let scope = DndScope::new();
let world = scope.with(|| Self {
ctx: DndContext::managed(Store::new(DragState::default()), Signal::new(String::new())),
windows: Signal::new(Vec::new()),
active: Signal::new(None),
global_pointer: Signal::new(None),
over_location: Signal::new(None),
settle_claim: Signal::new(None),
bridging: Signal::new(default_bridging(
std::env::var("DIOXUS_DND_NO_BRIDGE").ok().as_deref(),
)),
});
WORLD_OWNERS.with_borrow_mut(|owners| owners.push(ManuallyDrop::new(scope)));
world
}
pub fn vdom(self, root: fn() -> Element) -> VirtualDom {
VirtualDom::new(root).with_root_context(self)
}
pub fn context(&self) -> DndContext<T> {
self.ctx
}
pub fn set_bridging(&self, enabled: bool) {
let mut bridging = self.bridging;
if *bridging.peek() != enabled {
bridging.set(enabled);
}
}
pub fn bridging_enabled(&self) -> bool {
self.bridging.try_peek().map(|b| *b).unwrap_or(true)
}
pub(crate) fn join(
&self,
geometry: WindowGeometry,
registry: ZoneRegistry<T>,
settle: SettleFlag<T>,
refresh: Callback<()>,
) -> WindowKey {
let key = WindowKey::auto();
let mut windows = self.windows;
windows.write().push(WindowRecord {
key,
geometry,
registry,
settle,
refresh,
});
key
}
pub(crate) fn leave(&self, key: WindowKey) {
if self.windows.try_peek().is_err() {
return;
}
let mut ctx = self.ctx;
{
let mut windows = self.windows;
windows.write().retain(|w| w.key != key);
}
let qualified_over = *self.over_location.peek();
if qualified_over.is_some_and(|over| over.window == key) {
if let Some(over) = ctx.over() {
ctx.leave(over);
}
let mut over_location = self.over_location;
over_location.set(None);
} else if qualified_over.is_none() {
if let Some(over) = ctx.over() {
let reachable = self
.windows
.peek()
.iter()
.any(|w| w.registry.contains(over));
if !reachable {
ctx.leave(over);
}
}
}
let active_drag = *self.active.peek();
if active_drag.is_some_and(|active| active.origin == key) {
if ctx.dragging() {
match active_drag.and_then(|active| active.session) {
Some(session) if ctx.is_session(session) => {
ctx.abandon_session(session, CancelReason::SourceUnmounted);
}
_ => ctx.cancel_with_reason(CancelReason::SourceUnmounted),
}
self.clear_world_state();
return;
}
if ctx.settling().is_some() {
match self.settle_presenter() {
Some(presenter) if presenter != key => {}
Some(_) => {
self.finish_settle_from(key);
}
None => {
ctx.finish_settle();
self.clear_world_state();
}
}
return;
}
self.clear_world_state();
return;
}
if self.settle_presenter_is(key) {
self.finish_settle_from(key);
}
}
pub fn record(&self, key: WindowKey) -> Option<WindowRecord<T>> {
self.windows
.try_peek()
.ok()?
.iter()
.find(|w| w.key == key)
.copied()
}
pub fn windows(&self) -> Vec<WindowRecord<T>> {
self.windows
.try_read()
.map(|w| w.to_vec())
.unwrap_or_default()
}
pub fn window_under(&self, global: Point) -> Option<WindowRecord<T>> {
self.windows
.try_peek()
.ok()?
.iter()
.filter(|w| w.geometry.contains_global(global))
.max_by_key(|w| w.geometry.focus_stamp())
.copied()
}
pub fn resolve_global(&self, global: Point) -> Option<(WindowRecord<T>, Point)> {
let rec = self.window_under(global)?;
let local = rec.geometry.to_client(global)?;
Some((rec, local))
}
pub fn refresh_all_rects(&self) {
let Ok(windows) = self.windows.try_peek() else {
return;
};
for rec in windows.iter() {
rec.refresh.call(());
}
}
}
impl<T: Clone + 'static> Default for DndWorld<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bridging_defaults_on_unless_no_bridge_is_meaningfully_set() {
assert!(default_bridging(None));
assert!(default_bridging(Some("")));
assert!(default_bridging(Some("0")));
assert!(!default_bridging(Some("1")));
assert!(!default_bridging(Some("true")));
}
}