egui/drag_and_drop.rs
1use core::any::Any;
2use std::sync::Arc;
3
4use crate::{Context, CursorIcon, Plugin, Ui};
5
6/// Plugin for tracking drag-and-drop payload.
7///
8/// This plugin stores the current drag-and-drop payload internally and handles
9/// automatic cleanup when the drag operation ends (via Escape key or mouse release).
10///
11/// This is a low-level API. For a higher-level API, see:
12/// - [`crate::Ui::dnd_drag_source`]
13/// - [`crate::Ui::dnd_drop_zone`]
14/// - [`crate::Response::dnd_set_drag_payload`]
15/// - [`crate::Response::dnd_hover_payload`]
16/// - [`crate::Response::dnd_release_payload`]
17///
18/// This is a built-in plugin in egui, automatically registered during [`Context`] creation.
19///
20/// See [this example](https://github.com/emilk/egui/blob/main/crates/egui_demo_lib/src/demo/drag_and_drop.rs).
21#[doc(alias = "drag and drop")]
22#[derive(Clone, Default)]
23pub struct DragAndDrop {
24 /// The current drag-and-drop payload, if any. Automatically cleared when drag ends.
25 payload: Option<Arc<dyn Any + Send + Sync>>,
26}
27
28impl Plugin for DragAndDrop {
29 fn debug_name(&self) -> &'static str {
30 "DragAndDrop"
31 }
32
33 /// Interrupt drag-and-drop if the user presses the escape key.
34 ///
35 /// This needs to happen at frame start so we can properly capture the escape key.
36 fn on_begin_pass(&mut self, ui: &mut Ui) {
37 let has_any_payload = self.payload.is_some();
38
39 if has_any_payload {
40 let abort_dnd_due_to_escape_key =
41 ui.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
42
43 if abort_dnd_due_to_escape_key {
44 self.payload = None;
45 }
46 }
47 }
48
49 /// Interrupt drag-and-drop if the user releases the mouse button.
50 ///
51 /// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
52 /// This must happen at end-of-frame such that we don't shadow the mouse release event from user
53 /// code.
54 fn on_end_pass(&mut self, ui: &mut Ui) {
55 let has_any_payload = self.payload.is_some();
56
57 if has_any_payload {
58 let abort_dnd_due_to_mouse_release = ui.input_mut(|i| i.pointer.any_released());
59
60 if abort_dnd_due_to_mouse_release {
61 self.payload = None;
62 } else {
63 // We set the cursor icon only if its default, as the user code might have
64 // explicitly set it already.
65 ui.output_mut(|o| {
66 if o.cursor_icon == CursorIcon::Default {
67 o.cursor_icon = CursorIcon::Grabbing;
68 }
69 });
70 }
71 }
72 }
73}
74
75impl DragAndDrop {
76 /// Set a drag-and-drop payload.
77 ///
78 /// This can be read by [`Self::payload`] until the pointer is released.
79 pub fn set_payload<Payload>(ctx: &Context, payload: Payload)
80 where
81 Payload: Any + Send + Sync,
82 {
83 ctx.plugin::<Self>().lock().payload = Some(Arc::new(payload));
84 }
85
86 /// Clears the payload, setting it to `None`.
87 pub fn clear_payload(ctx: &Context) {
88 ctx.plugin::<Self>().lock().payload = None;
89 }
90
91 /// Retrieve the payload, if any.
92 ///
93 /// Returns `None` if there is no payload, or if it is not of the requested type.
94 ///
95 /// Returns `Some` both during a drag and on the frame the pointer is released
96 /// (if there is a payload).
97 pub fn payload<Payload>(ctx: &Context) -> Option<Arc<Payload>>
98 where
99 Payload: Any + Send + Sync,
100 {
101 Arc::clone(ctx.plugin::<Self>().lock().payload.as_ref()?)
102 .downcast()
103 .ok()
104 }
105
106 /// Retrieve and clear the payload, if any.
107 ///
108 /// Returns `None` if there is no payload, or if it is not of the requested type.
109 ///
110 /// Returns `Some` both during a drag and on the frame the pointer is released
111 /// (if there is a payload).
112 pub fn take_payload<Payload>(ctx: &Context) -> Option<Arc<Payload>>
113 where
114 Payload: Any + Send + Sync,
115 {
116 ctx.plugin::<Self>().lock().payload.take()?.downcast().ok()
117 }
118
119 /// Are we carrying a payload of the given type?
120 ///
121 /// Returns `true` both during a drag and on the frame the pointer is released
122 /// (if there is a payload).
123 pub fn has_payload_of_type<Payload>(ctx: &Context) -> bool
124 where
125 Payload: Any + Send + Sync,
126 {
127 Self::payload::<Payload>(ctx).is_some()
128 }
129
130 /// Are we carrying a payload?
131 ///
132 /// Returns `true` both during a drag and on the frame the pointer is released
133 /// (if there is a payload).
134 pub fn has_any_payload(ctx: &Context) -> bool {
135 ctx.plugin::<Self>().lock().payload.is_some()
136 }
137}