dioxus_dnd/core/
monitor.rs1use 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
178pub 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}