egui/
drag_and_drop.rs

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