1use crate::ffi::CursorStyle;
2use crate::ffi::PointerEventType;
3use crate::node::NodeRef;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::rc::Rc;
7
8const DRAG_DROP_TEXT_FORMAT: &str = "text/plain";
9
10#[repr(u32)]
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum DragDropEffects {
13 #[default]
14 None = 0,
15 Copy = 1,
16 Move = 2,
17 Link = 4,
18}
19
20fn normalize_effect(candidate: DragDropEffects, allowed: DragDropEffects) -> DragDropEffects {
21 let masked = (candidate as u32) & (allowed as u32);
22 if masked == DragDropEffects::None as u32 {
23 return DragDropEffects::None;
24 }
25 if (masked & DragDropEffects::Move as u32) != 0 {
26 return DragDropEffects::Move;
27 }
28 if (masked & DragDropEffects::Copy as u32) != 0 {
29 return DragDropEffects::Copy;
30 }
31 if (masked & DragDropEffects::Link as u32) != 0 {
32 return DragDropEffects::Link;
33 }
34 DragDropEffects::None
35}
36
37#[derive(Clone, Debug, Default, PartialEq, Eq)]
38pub struct DragDataObject {
39 formats: HashMap<String, String>,
40}
41
42impl DragDataObject {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn set_text(mut self, value: impl Into<String>) -> Self {
48 self.formats
49 .insert(String::from(DRAG_DROP_TEXT_FORMAT), value.into());
50 self
51 }
52
53 pub fn set_format(mut self, format: impl Into<String>, value: impl Into<String>) -> Self {
54 self.formats.insert(format.into(), value.into());
55 self
56 }
57
58 pub fn has_format(&self, format: &str) -> bool {
59 self.formats.contains_key(format)
60 }
61
62 pub fn get_text(&self) -> Option<String> {
63 self.get_format(DRAG_DROP_TEXT_FORMAT)
64 }
65
66 pub fn get_format(&self, format: &str) -> Option<String> {
67 self.formats.get(format).cloned()
68 }
69}
70
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
72pub struct DragCompletedEventArgs {
73 pub effect: DragDropEffects,
74}
75
76#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
77pub struct DropProposal {
78 pub effect: DragDropEffects,
79 pub show_insertion_marker: bool,
80}
81
82impl DropProposal {
83 pub fn new(effect: DragDropEffects, show_insertion_marker: bool) -> Self {
84 Self {
85 effect,
86 show_insertion_marker,
87 }
88 }
89
90 pub fn none() -> Self {
91 Self::default()
92 }
93}
94
95type DragCompletedCallback = Rc<dyn Fn(DragCompletedEventArgs)>;
96
97struct DragSessionState {
98 source: NodeRef,
99 current_effect: DragDropEffects,
100 active: bool,
101 completed_callback: Option<DragCompletedCallback>,
102}
103
104#[derive(Clone)]
105pub struct DragSession {
106 pub data: DragDataObject,
107 pub allowed_effects: DragDropEffects,
108 inner: Rc<RefCell<DragSessionState>>,
109}
110
111impl DragSession {
112 pub(crate) fn new(
113 source: NodeRef,
114 data: DragDataObject,
115 allowed_effects: DragDropEffects,
116 ) -> Self {
117 Self {
118 data,
119 allowed_effects,
120 inner: Rc::new(RefCell::new(DragSessionState {
121 source,
122 current_effect: DragDropEffects::None,
123 active: true,
124 completed_callback: None,
125 })),
126 }
127 }
128
129 pub fn current_effect(&self) -> DragDropEffects {
130 self.inner.borrow().current_effect
131 }
132
133 pub fn is_active(&self) -> bool {
134 self.inner.borrow().active
135 }
136
137 pub fn on_completed(&self, callback: impl Fn(DragCompletedEventArgs) + 'static) -> &Self {
138 self.inner.borrow_mut().completed_callback = Some(Rc::new(callback));
139 self
140 }
141
142 pub fn cancel(&self) {
143 if !self.is_active() {
144 return;
145 }
146 crate::event::cancel_drag_session(self.clone());
147 }
148
149 pub(crate) fn source(&self) -> NodeRef {
150 self.inner.borrow().source.clone()
151 }
152
153 pub(crate) fn set_current_effect(&self, effect: DragDropEffects) {
154 self.inner.borrow_mut().current_effect = effect;
155 }
156
157 pub(crate) fn complete(&self, effect: DragDropEffects) {
158 let callback = {
159 let mut state = self.inner.borrow_mut();
160 if !state.active {
161 return;
162 }
163 state.active = false;
164 state.current_effect = effect;
165 state.completed_callback.clone()
166 };
167 if let Some(callback) = callback {
168 callback(DragCompletedEventArgs { effect });
169 }
170 }
171}
172
173#[derive(Clone)]
174pub struct DragEventArgs {
175 pub session: DragSession,
176 pub x: f32,
177 pub y: f32,
178 pub modifiers: u32,
179}
180
181impl DragEventArgs {
182 pub fn new(session: DragSession, x: f32, y: f32, modifiers: u32) -> Self {
183 Self {
184 session,
185 x,
186 y,
187 modifiers,
188 }
189 }
190}
191
192#[derive(Default)]
193struct DragDropState {
194 active_session: Option<DragSession>,
195 active_target: Option<NodeRef>,
196}
197
198thread_local! {
199 static STATE: RefCell<DragDropState> = RefCell::new(DragDropState::default());
200}
201
202fn is_default_proposal(proposal: DropProposal) -> bool {
203 proposal.effect == DragDropEffects::None && !proposal.show_insertion_marker
204}
205
206fn with_state<T>(callback: impl FnOnce(&mut DragDropState) -> T) -> T {
207 STATE.with(|slot| callback(&mut slot.borrow_mut()))
208}
209
210pub(crate) fn cursor_override_style() -> CursorStyle {
211 STATE.with(|slot| {
212 let state = slot.borrow();
213 let Some(session) = state.active_session.as_ref() else {
214 return CursorStyle::Default;
215 };
216 if !session.is_active() {
217 return CursorStyle::Default;
218 }
219 if session.current_effect() == DragDropEffects::None {
220 CursorStyle::Grabbing
221 } else {
222 CursorStyle::Move
223 }
224 })
225}
226
227pub(crate) fn begin_session(source: NodeRef) -> bool {
228 let existing = STATE.with(|slot| slot.borrow().active_session.clone());
229 if let Some(existing) = existing {
230 finish_session(existing, DragDropEffects::None, 0.0, 0.0, 0, true);
231 }
232 if !source.has_drag_source() {
233 return false;
234 }
235 let Some(data) = source.create_drag_data_object() else {
236 return false;
237 };
238 let allowed = source.get_drag_allowed_effects();
239 if allowed == DragDropEffects::None {
240 return false;
241 }
242 with_state(|state| {
243 state.active_target = None;
244 state.active_session = Some(DragSession::new(source, data, allowed));
245 });
246 true
247}
248
249pub(crate) fn cancel_session(session: DragSession) {
250 let is_active = STATE.with(|slot| {
251 slot.borrow()
252 .active_session
253 .as_ref()
254 .map(|active| Rc::ptr_eq(&active.inner, &session.inner))
255 .unwrap_or(false)
256 });
257 if !is_active {
258 return;
259 }
260 finish_session(session, DragDropEffects::None, 0.0, 0.0, 0, true);
261}
262
263pub(crate) fn cancel_session_for_source(source: &NodeRef) {
264 let active = STATE.with(|slot| slot.borrow().active_session.clone());
265 let Some(session) = active else {
266 return;
267 };
268 if !session.source().ptr_eq(source) {
269 return;
270 }
271 finish_session(session, DragDropEffects::None, 0.0, 0.0, 0, true);
272}
273
274pub(crate) fn handle_node_destroyed(node: NodeRef) {
275 let active = STATE.with(|slot| {
276 let state = slot.borrow();
277 (state.active_session.clone(), state.active_target.clone())
278 });
279 if let Some(session) = active.0 {
280 if session.source().ptr_eq(&node) {
281 finish_session(session, DragDropEffects::None, 0.0, 0.0, 0, true);
282 return;
283 }
284 }
285 if let Some(target) = active.1 {
286 if target.ptr_eq(&node) {
287 with_state(|state| {
288 state.active_target = None;
289 if let Some(session) = state.active_session.as_ref() {
290 session.set_current_effect(DragDropEffects::None);
291 }
292 });
293 }
294 }
295}
296
297pub(crate) fn handle_pointer_event(
298 pointed_node: Option<NodeRef>,
299 event_type: PointerEventType,
300 x: f32,
301 y: f32,
302 modifiers: u32,
303) {
304 let active = STATE.with(|slot| slot.borrow().active_session.clone());
305 let Some(session) = active else {
306 return;
307 };
308 if !session.is_active() {
309 return;
310 }
311 match event_type {
312 PointerEventType::Down
313 | PointerEventType::Enter
314 | PointerEventType::Move
315 | PointerEventType::Leave => {
316 update_target(pointed_node, session, x, y, modifiers);
317 }
318 PointerEventType::Up => {
319 let effect = update_target(pointed_node, session.clone(), x, y, modifiers);
320 let target = STATE.with(|slot| slot.borrow().active_target.clone());
321 if let Some(target) = target {
322 if effect != DragDropEffects::None {
323 target.handle_drop_event(DragEventArgs::new(session.clone(), x, y, modifiers));
324 }
325 }
326 finish_session(session, effect, x, y, modifiers, true);
327 }
328 PointerEventType::Cancel => {}
329 }
330}
331
332pub(crate) fn reset() {
333 with_state(|state| {
334 state.active_session = None;
335 state.active_target = None;
336 });
337}
338
339fn update_target(
340 pointed_node: Option<NodeRef>,
341 session: DragSession,
342 x: f32,
343 y: f32,
344 modifiers: u32,
345) -> DragDropEffects {
346 let target = resolve_drop_target(pointed_node);
347 let args = DragEventArgs::new(session.clone(), x, y, modifiers);
348 let mut proposal = DropProposal::none();
349 let previous_target = STATE.with(|slot| slot.borrow().active_target.clone());
350 let target_changed = match (&target, &previous_target) {
351 (Some(left), Some(right)) => !left.ptr_eq(right),
352 (None, None) => false,
353 _ => true,
354 };
355 if target_changed {
356 if let Some(previous_target) = previous_target {
357 previous_target.handle_drag_leave(args.clone());
358 }
359 with_state(|state| {
360 state.active_target = target.clone();
361 });
362 if let Some(target) = target.as_ref() {
363 if target.has_drag_enter_handler() {
364 proposal = target.handle_drag_enter(args.clone());
365 }
366 }
367 }
368 let Some(target) = target else {
369 session.set_current_effect(DragDropEffects::None);
370 return DragDropEffects::None;
371 };
372 if target.has_drag_over_handler() {
373 proposal = target.handle_drag_over(args);
374 } else if !target_changed && is_default_proposal(proposal) {
375 return session.current_effect();
376 }
377 let effect = normalize_effect(proposal.effect, session.allowed_effects);
378 session.set_current_effect(effect);
379 effect
380}
381
382fn resolve_drop_target(pointed_node: Option<NodeRef>) -> Option<NodeRef> {
383 let mut current = pointed_node;
384 while let Some(node) = current {
385 if node.allows_drop() {
386 return Some(node);
387 }
388 current = node.parent();
389 }
390 None
391}
392
393fn finish_session(
394 session: DragSession,
395 effect: DragDropEffects,
396 x: f32,
397 y: f32,
398 modifiers: u32,
399 notify_target_leave: bool,
400) {
401 let target = with_state(|state| {
402 let active = state.active_session.as_ref()?;
403 if !Rc::ptr_eq(&active.inner, &session.inner) {
404 return None;
405 }
406 let target = state.active_target.clone();
407 state.active_session = None;
408 state.active_target = None;
409 Some(target)
410 });
411 let Some(target) = target else {
412 return;
413 };
414 session.set_current_effect(effect);
415 if notify_target_leave {
416 if let Some(target) = target {
417 target.handle_drag_leave(DragEventArgs::new(session.clone(), x, y, modifiers));
418 }
419 }
420 session.complete(effect);
421 session.source().notify_drag_completed(effect);
422}