1use std::cell::RefCell;
4use std::rc::Rc;
5
6use dioxus::html::MountedData;
7use dioxus::prelude::*;
8
9use super::components::{drop_query, resolve_drag_hover};
10use super::registry::{RectRefresh, ZoneRecord, ZoneRegistration, ZoneRegistry};
11use super::state::{DndContext, DragState};
12use super::types::{DragId, DropEffect, DropOutcome, Point, Rect, ZoneId};
13use super::world::{
14 use_joined_window, DndWorld, JoinedWindow, WindowGeometry, WorldHit, WorldMembership,
15};
16
17pub(crate) struct SettleFlag<T> {
23 armed: Signal<Option<u64>>,
24 marker: std::marker::PhantomData<T>,
25}
26
27impl<T> Copy for SettleFlag<T> {}
28impl<T> Clone for SettleFlag<T> {
29 fn clone(&self) -> Self {
30 *self
31 }
32}
33
34impl<T> SettleFlag<T> {
35 pub(crate) fn arm(self, capability: u64) {
36 let mut armed = self.armed;
37 if let Ok(mut value) = armed.try_write() {
38 if *value != Some(capability) {
39 *value = Some(capability);
40 }
41 };
42 }
43
44 pub(crate) fn is_armed(self) -> bool {
45 matches!(self.armed.try_peek().as_deref(), Ok(Some(_)))
46 }
47
48 pub(crate) fn release(self, capability: u64) -> bool {
51 let mut armed = self.armed;
52 let Ok(mut value) = armed.try_write() else {
53 return false;
54 };
55 if *value != Some(capability) {
56 return false;
57 }
58 *value = None;
59 true
60 }
61}
62
63pub fn use_dnd_provider<T: Clone + 'static>() -> DndContext<T> {
74 let state = use_store(DragState::<T>::default);
77 let announcement = use_signal(String::new);
78 let registry = use_context_provider(|| ZoneRegistry::<T>::from_signal(Signal::new(Vec::new())));
79 let settle_flag = use_context_provider(|| SettleFlag::<T> {
80 armed: Signal::new(None),
81 marker: std::marker::PhantomData,
82 });
83 let membership = use_hook(move || {
89 let joined = try_consume_context::<DndWorld<T>>()
90 .filter(|_| try_consume_context::<WorldMembership<T>>().is_none())
91 .map(|world| {
92 let geometry = try_consume_context::<WindowGeometry>().unwrap_or_default();
93 let key = world.join(
94 geometry,
95 registry,
96 settle_flag,
97 Callback::new(move |_| registry.refresh_rects()),
98 );
99 JoinedWindow {
100 world,
101 key,
102 geometry,
103 }
104 });
105 provide_context(WorldMembership::<T>(joined));
106 joined
107 });
108 use_drop(move || {
109 if let Some(j) = membership {
110 j.world.leave(j.key);
111 }
112 });
113 let ctx = use_context_provider(move || match membership {
114 Some(j) => j.world.context(),
115 None => DndContext::managed(state, announcement),
116 });
117
118 use_rect_refresh_provider();
123 use_rect_refresh_thunk(move |_| {
127 if !ctx.dragging() {
128 return;
129 }
130 let drag_id = ctx.drag_id();
131 let session = ctx.drag_session_id();
132 registry.refresh_rects_then(move || {
133 if !ctx.alive()
137 || !ctx.dragging()
138 || ctx.drag_id() != drag_id
139 || ctx.drag_session_id() != session
140 {
141 return;
142 }
143
144 let proposed = ctx.proposed_effect();
145 let point = membership
146 .and_then(|joined| joined.local_pointer())
147 .unwrap_or_else(|| ctx.pointer());
148 match membership {
149 Some(joined) => {
150 let query = ctx
151 .payload()
152 .map(|payload| drop_query(&ctx, payload, proposed));
153 match query
154 .as_ref()
155 .map(|query| joined.zone_under_query(point, query))
156 .unwrap_or(WorldHit::Unresolved)
157 {
158 WorldHit::Zone(location) => joined.enter(location),
159 WorldHit::Window => joined.clear_hover(),
160 WorldHit::Unresolved => {
161 match resolve_drag_hover(registry, &ctx, point, proposed) {
162 Some(zone) => joined.enter(joined.location(zone)),
163 None => joined.clear_hover(),
164 }
165 }
166 }
167 }
168 None => match resolve_drag_hover(registry, &ctx, point, proposed) {
169 Some(zone) => {
170 let mut ctx = ctx;
171 ctx.enter(zone);
172 }
173 None => {
174 if let Some(over) = ctx.over() {
175 let mut ctx = ctx;
176 ctx.leave(over);
177 }
178 }
179 },
180 }
181 });
182 });
183
184 ctx
185}
186
187pub(crate) fn use_rect_refresh_provider() -> RectRefresh {
193 let bus = use_hook(|| {
194 try_consume_context::<RectRefresh>()
196 .unwrap_or_else(|| RectRefresh::from_signal(Signal::new(Vec::new())))
197 });
198 use_context_provider(|| bus);
199 bus
200}
201
202pub(crate) fn use_rect_refresh_thunk(thunk: impl FnMut(()) + 'static) {
207 let joined = use_hook(move || {
208 try_consume_context::<RectRefresh>().map(|mut bus| {
209 let key = DragId::auto().0;
210 bus.register(key, Callback::new(thunk));
211 (bus, key)
212 })
213 });
214 use_drop(move || {
215 if let Some((mut bus, key)) = joined {
216 bus.unregister(key);
217 }
218 });
219}
220
221pub fn use_rect_refresh() -> RectRefresh {
229 use_context()
230}
231
232pub fn use_dnd<T: Clone + 'static>() -> DndContext<T> {
237 use_context()
238}
239
240pub fn use_zone_registry<T: Clone + 'static>() -> ZoneRegistry<T> {
243 use_context()
244}
245
246pub fn use_zone_id() -> ZoneId {
248 use_hook(ZoneId::auto)
249}
250
251#[derive(Clone, Default)]
258pub struct BridgeGeometry {
259 state: Rc<RefCell<BridgeGeometryState>>,
260}
261
262#[derive(Default)]
263struct BridgeGeometryState {
264 next_writer: u64,
265 writers: Vec<BridgeGeometryWriter>,
266 mounted: Option<Rc<MountedData>>,
267 rect: Option<Rect>,
268}
269
270#[derive(Clone)]
271struct BridgeGeometryWriter {
272 id: u64,
273 mounted: Rc<dyn Fn(Rc<MountedData>)>,
274 rect: Rc<dyn Fn(Rect)>,
275}
276
277struct BridgeGeometryRegistration {
278 state: Rc<RefCell<BridgeGeometryState>>,
279 id: u64,
280}
281
282impl Drop for BridgeGeometryRegistration {
283 fn drop(&mut self) {
284 self.state
285 .borrow_mut()
286 .writers
287 .retain(|writer| writer.id != self.id);
288 }
289}
290
291impl std::fmt::Debug for BridgeGeometry {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 f.debug_struct("BridgeGeometry")
294 .field("worlds", &self.state.borrow().writers.len())
295 .finish()
296 }
297}
298
299impl PartialEq for BridgeGeometry {
300 fn eq(&self, other: &Self) -> bool {
301 Rc::ptr_eq(&self.state, &other.state)
302 }
303}
304
305impl BridgeGeometry {
306 pub fn set_mounted(&self, mounted: &Rc<MountedData>) {
308 let writers = {
309 let mut state = self.state.borrow_mut();
310 state.mounted = Some(mounted.clone());
311 state.writers.clone()
312 };
313 for writer in writers {
314 (writer.mounted)(mounted.clone());
315 }
316 }
317
318 pub fn set_rect_if_present(&self, rect: Rect) {
321 let writers = {
322 let mut state = self.state.borrow_mut();
323 state.rect = Some(rect);
324 state.writers.clone()
325 };
326 for writer in writers {
327 (writer.rect)(rect);
328 }
329 }
330
331 fn register<T: Clone + 'static>(
332 &self,
333 registry: ZoneRegistry<T>,
334 registration: ZoneRegistration,
335 ) -> BridgeGeometryRegistration {
336 let mut state = self.state.borrow_mut();
337 let id = state.next_writer;
338 state.next_writer = state.next_writer.wrapping_add(1);
339 let writer = BridgeGeometryWriter {
340 id,
341 mounted: Rc::new(move |mounted| {
342 let mut registry = registry;
343 registry.set_mounted(registration, mounted);
344 }),
345 rect: Rc::new(move |rect| {
346 let mut registry = registry;
347 registry.set_rect_if_present(registration, rect);
348 }),
349 };
350 let mounted = state.mounted.clone();
351 let rect = state.rect;
352 state.writers.push(writer.clone());
353 drop(state);
354 if let Some(mounted) = mounted {
355 (writer.mounted)(mounted);
356 }
357 if let Some(rect) = rect {
358 (writer.rect)(rect);
359 }
360 BridgeGeometryRegistration {
361 state: self.state.clone(),
362 id,
363 }
364 }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub struct BridgeWorld {
372 pub active: bool,
374 pub over: bool,
376}
377
378pub fn use_bridge_world<T: Clone + PartialEq + 'static>(
390 zone_id: ZoneId,
391 parent: Option<ZoneId>,
392 label: Option<String>,
393 accepts: Option<Callback<T, bool>>,
394 on_drop: EventHandler<DropOutcome<T>>,
395 geometry: BridgeGeometry,
396) -> BridgeWorld {
397 let dnd = use_dnd::<T>();
398 let joined = use_joined_window::<T>();
399 let mut reg = use_zone_registry::<T>();
400 let on_drop = use_callback(move |outcome| on_drop.call(outcome));
401 let registered_accepts = use_callback(move |payload| {
406 accepts
407 .map(|callback| callback.call(payload))
408 .unwrap_or(true)
409 });
410 let initial_label = label.clone();
411 let initial_geometry = geometry.clone();
412 let registrations = use_hook(move || {
413 let registration = reg.register(ZoneRecord {
414 id: zone_id,
415 parent,
416 label: initial_label,
417 on_drop,
418 accepts: Some(registered_accepts),
419 mounted: None,
420 rect: None,
421 });
422 let writer = initial_geometry.register(reg, registration);
423 Rc::new(RefCell::new(Some((zone_id, parent, registration, writer))))
424 });
425 let effect_registrations = registrations.clone();
426 let effect_geometry = geometry.clone();
427 use_effect(use_reactive!(|(zone_id, parent, label)| {
428 let unchanged = effect_registrations.borrow().as_ref().is_some_and(
429 |(current_id, current_parent, ..)| *current_id == zone_id && *current_parent == parent,
430 );
431 if unchanged {
432 reg.sync_label(zone_id, label);
433 return;
434 }
435
436 if let Some((_, _, old_registration, old_writer)) = effect_registrations.borrow_mut().take()
437 {
438 reg.unregister_registration(old_registration);
439 drop(old_writer);
440 }
441 let registration = reg.register(ZoneRecord {
442 id: zone_id,
443 parent,
444 label,
445 on_drop,
446 accepts: Some(registered_accepts),
447 mounted: None,
448 rect: None,
449 });
450 let writer = effect_geometry.register(reg, registration);
451 *effect_registrations.borrow_mut() = Some((zone_id, parent, registration, writer));
452 }));
453 use_drop(move || {
454 if let Some((_, _, registration, writer)) = registrations.borrow_mut().take() {
455 reg.unregister_registration(registration);
456 drop(writer);
457 }
458 });
459
460 let acceptable = dnd.proposed_effect() != DropEffect::None
461 && match dnd.payload() {
462 Some(p) => accepts.map(|cb| cb.call(p)).unwrap_or(true),
463 None => false,
464 };
465 BridgeWorld {
466 active: dnd.dragging() && acceptable,
467 over: match joined {
468 Some(joined) => joined.is_over(zone_id),
469 None => dnd.over() == Some(zone_id),
470 } && acceptable,
471 }
472}
473
474pub fn client_point(evt: &DragEvent) -> Point {
478 let c = evt.client_coordinates();
479 Point::new(c.x, c.y)
480}
481
482pub fn element_point(evt: &DragEvent) -> Point {
485 let c = evt.element_coordinates();
486 Point::new(c.x, c.y)
487}