egui/
drag_and_drop.rs

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