Skip to main content

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}