Skip to main content

dioxus_dnd/core/
monitor.rs

1//! Application-wide drag lifecycle monitoring.
2
3use std::collections::VecDeque;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use dioxus::prelude::*;
7
8use super::{
9    DragId, DragMode, DragSessionId, DropEffect, DropOutcome, Point, PointerKind, Rect, ZoneId,
10};
11
12static NEXT_MONITOR: AtomicU64 = AtomicU64::new(1);
13
14#[derive(Debug, Clone, PartialEq)]
15#[non_exhaustive]
16pub struct DragSnapshot<T> {
17    pub id: DragId,
18    pub session: Option<DragSessionId>,
19    pub payload: T,
20    pub source: Option<ZoneId>,
21    pub over: Option<ZoneId>,
22    pub pointer: Point,
23    pub grab: Point,
24    pub effect: DropEffect,
25    pub mode: DragMode,
26    pub pointer_kind: PointerKind,
27    pub source_rect: Option<Rect>,
28}
29
30#[derive(Debug, Clone, PartialEq)]
31#[non_exhaustive]
32pub struct DropReceipt<T> {
33    pub drag: DragSnapshot<T>,
34    pub outcome: DropOutcome<T>,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum CancelReason {
40    User,
41    PointerCancelled,
42    NoTarget,
43    SourceUnmounted,
44    Replaced,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48#[non_exhaustive]
49pub enum DndEvent<T> {
50    Started(DragSnapshot<T>),
51    Moved(DragSnapshot<T>),
52    TargetChanged {
53        drag: DragSnapshot<T>,
54        previous: Option<ZoneId>,
55        current: Option<ZoneId>,
56    },
57    Dropped(DropReceipt<T>),
58    Cancelled {
59        drag: DragSnapshot<T>,
60        reason: CancelReason,
61    },
62}
63
64pub(crate) struct DndMonitor<T: 'static> {
65    listeners: Signal<Vec<(u64, Callback<DndEvent<T>>)>>,
66    pending: Signal<VecDeque<DndEvent<T>>>,
67    dispatching: Signal<bool>,
68}
69
70impl<T> Copy for DndMonitor<T> {}
71impl<T> Clone for DndMonitor<T> {
72    fn clone(&self) -> Self {
73        *self
74    }
75}
76impl<T> PartialEq for DndMonitor<T> {
77    fn eq(&self, other: &Self) -> bool {
78        self.listeners == other.listeners
79            && self.pending == other.pending
80            && self.dispatching == other.dispatching
81    }
82}
83
84impl<T: Clone + 'static> DndMonitor<T> {
85    pub(crate) fn new() -> Self {
86        Self {
87            listeners: Signal::new(Vec::new()),
88            pending: Signal::new(VecDeque::new()),
89            dispatching: Signal::new(false),
90        }
91    }
92
93    pub(crate) fn subscribe(&mut self, callback: Callback<DndEvent<T>>) -> u64 {
94        let id = NEXT_MONITOR.fetch_add(1, Ordering::Relaxed);
95        self.listeners.write().push((id, callback));
96        id
97    }
98
99    pub(crate) fn unsubscribe(&mut self, id: u64) {
100        if let Ok(mut listeners) = self.listeners.try_write() {
101            listeners.retain(|(candidate, _)| *candidate != id);
102        }
103    }
104
105    pub(crate) fn has_listeners(&self) -> bool {
106        self.listeners
107            .try_peek()
108            .is_ok_and(|listeners| !listeners.is_empty())
109    }
110
111    pub(crate) fn emit_lazy(&self, build: impl FnOnce() -> Option<DndEvent<T>>) {
112        if !self.has_listeners() {
113            return;
114        }
115        if let Some(event) = build() {
116            self.emit(event);
117        }
118    }
119
120    pub(crate) fn emit(&self, event: DndEvent<T>) {
121        if !self.has_listeners() {
122            return;
123        }
124        let mut pending = self.pending;
125        let Ok(mut queue) = pending.try_write() else {
126            return;
127        };
128        queue.push_back(event);
129        drop(queue);
130
131        if self
132            .dispatching
133            .try_peek()
134            .is_ok_and(|dispatching| *dispatching)
135        {
136            return;
137        }
138
139        let mut dispatching = self.dispatching;
140        let Ok(mut active) = dispatching.try_write() else {
141            return;
142        };
143        *active = true;
144        drop(active);
145        let _guard = DispatchGuard(dispatching);
146
147        loop {
148            let mut pending = self.pending;
149            let event = pending
150                .try_write()
151                .ok()
152                .and_then(|mut queue| queue.pop_front());
153            let Some(event) = event else {
154                break;
155            };
156            let callbacks: Vec<_> = self
157                .listeners
158                .try_peek()
159                .map(|listeners| listeners.iter().map(|(_, callback)| *callback).collect())
160                .unwrap_or_default();
161            for callback in callbacks {
162                callback.call(event.clone());
163            }
164        }
165    }
166}
167
168struct DispatchGuard(Signal<bool>);
169
170impl Drop for DispatchGuard {
171    fn drop(&mut self) {
172        if let Ok(mut dispatching) = self.0.try_write() {
173            *dispatching = false;
174        }
175    }
176}
177
178/// Observe every drag lifecycle event from the nearest provider.
179pub fn use_dnd_monitor<T: Clone + 'static>(handler: impl FnMut(DndEvent<T>) + 'static) {
180    let mut dnd = crate::core::use_dnd::<T>();
181    let callback = use_callback(handler);
182    let id = use_hook(move || dnd.monitor_mut().subscribe(callback));
183    use_drop(move || dnd.monitor_mut().unsubscribe(id));
184}