dioxus_dnd/core/hooks.rs
1//! Hooks for providing and consuming the drag context.
2
3use dioxus::prelude::*;
4
5use super::registry::{RectRefresh, ZoneRegistry};
6use super::state::{DndContext, DragState};
7use super::types::{DragId, Point, ZoneId};
8
9/// Marker flag: a settle-enabled `DragOverlay<T>` is mounted somewhere in
10/// this provider's subtree, so `Draggable<T>` should route successful
11/// pointer drops through [`DndContext::take_settling`] instead of
12/// [`DndContext::take`]. Typed so nested providers of different payloads
13/// can't arm each other.
14pub(crate) struct SettleFlag<T> {
15 pub(crate) armed: Signal<bool>,
16 marker: std::marker::PhantomData<T>,
17}
18
19impl<T> Copy for SettleFlag<T> {}
20impl<T> Clone for SettleFlag<T> {
21 fn clone(&self) -> Self {
22 *self
23 }
24}
25
26/// Provide a `DndContext<T>` (and its zone registry) to this component's
27/// subtree. Call once, high up (or use the
28/// [`crate::core::components::DndProvider`] component).
29pub fn use_dnd_provider<T: Clone + 'static>() -> DndContext<T> {
30 let state = use_store(DragState::<T>::default);
31 let announcement = use_signal(String::new);
32 let registry = use_context_provider(|| ZoneRegistry::<T>::from_signal(Signal::new(Vec::new())));
33 let ctx = use_context_provider(move || DndContext::from_parts(state, announcement));
34 use_context_provider(|| SettleFlag::<T> {
35 armed: Signal::new(false),
36 marker: std::marker::PhantomData,
37 });
38
39 // One rect-refresh channel per provider *tree*: the outermost provider
40 // creates it, nested providers inherit and re-provide the same one. A
41 // scroll surface anywhere below then reaches every registry above it
42 // through a single type-erased handle.
43 use_rect_refresh_provider();
44 // Re-measure this registry on ping - but only mid-drag. Rects are
45 // measured fresh at every pickup, so an idle provider has nothing to
46 // keep current, and the gate makes scroll-event pings free while idle.
47 use_rect_refresh_thunk(move |_| {
48 if ctx.dragging() {
49 registry.refresh_rects();
50 }
51 });
52
53 ctx
54}
55
56/// Create-or-inherit the tree's [`RectRefresh`] channel and provide it to
57/// descendants. The outermost participant (a `DndProvider`, an
58/// [`crate::autoscroll::AutoScroll`]) owns the signal; everyone below
59/// shares it, so self-contained components like `SortableList` can join
60/// even with no provider anywhere.
61pub(crate) fn use_rect_refresh_provider() -> RectRefresh {
62 let bus = use_hook(|| {
63 // Plain context lookup (not the memoizing hook - we're inside one).
64 try_consume_context::<RectRefresh>()
65 .unwrap_or_else(|| RectRefresh::from_signal(Signal::new(Vec::new())))
66 });
67 use_context_provider(|| bus);
68 bus
69}
70
71/// Register a re-measure thunk on the tree's channel for this component's
72/// lifetime; it leaves the channel on unmount. Quietly does nothing when no
73/// channel exists above (nothing could ever ping it). The thunk must gate
74/// itself on its own drag state - pings arrive for every scroll.
75pub(crate) fn use_rect_refresh_thunk(thunk: impl FnMut(()) + 'static) {
76 let joined = use_hook(move || {
77 try_consume_context::<RectRefresh>().map(|mut bus| {
78 let key = DragId::auto().0;
79 bus.register(key, Callback::new(thunk));
80 (bus, key)
81 })
82 });
83 use_drop(move || {
84 if let Some((mut bus, key)) = joined {
85 bus.unregister(key);
86 }
87 });
88}
89
90/// The provider tree's [`RectRefresh`] channel: ping `refresh_all()` after
91/// you move layout under a live drag (scrolling a custom container,
92/// collapsing a panel) so hit-testing and `data-over` track the new
93/// geometry. [`crate::autoscroll::AutoScroll`] pings it for you.
94///
95/// # Panics
96/// Panics if no ancestor provided a drag context.
97pub fn use_rect_refresh() -> RectRefresh {
98 use_context()
99}
100
101/// Grab the nearest `DndContext<T>` from context.
102///
103/// # Panics
104/// Panics if no ancestor provided a context for this payload type.
105pub fn use_dnd<T: Clone + 'static>() -> DndContext<T> {
106 use_context()
107}
108
109/// Grab the zone registry (mounted drop zones, in order). Provided alongside
110/// the context by [`use_dnd_provider`].
111pub fn use_zone_registry<T: Clone + 'static>() -> ZoneRegistry<T> {
112 use_context()
113}
114
115/// A stable, auto-generated [`ZoneId`] for this component instance.
116pub fn use_zone_id() -> ZoneId {
117 use_hook(ZoneId::auto)
118}
119
120/// Client (viewport) coordinates of a native drag event as a [`Point`].
121/// In-app drags don't produce `DragEvent`s; this serves the boundary
122/// modules ([`crate::files`], [`crate::external`]) and custom native zones.
123pub fn client_point(evt: &DragEvent) -> Point {
124 let c = evt.client_coordinates();
125 Point::new(c.x, c.y)
126}
127
128/// Element-relative coordinates of a native drag event as a [`Point`].
129/// See [`client_point`] for when these apply.
130pub fn element_point(evt: &DragEvent) -> Point {
131 let c = evt.element_coordinates();
132 Point::new(c.x, c.y)
133}