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