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