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 141 142 143 144 145 146 147
use std::{any::Any, sync::Arc};
use crate::{Context, CursorIcon, Id};
/// Tracking of drag-and-drop payload.
///
/// 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`]
///
/// See [this example](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/drag_and_drop.rs).
#[doc(alias = "drag and drop")]
#[derive(Clone, Default)]
pub struct DragAndDrop {
/// If set, something is currently being dragged
payload: Option<Arc<dyn Any + Send + Sync>>,
}
impl DragAndDrop {
pub(crate) fn register(ctx: &Context) {
ctx.on_begin_pass("drag_and_drop_begin_pass", Arc::new(Self::begin_pass));
ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass));
}
/// 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 begin_pass(ctx: &Context) {
let has_any_payload = Self::has_any_payload(ctx);
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::clear_payload(ctx);
}
}
}
/// 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 end_pass(ctx: &Context) {
let has_any_payload = Self::has_any_payload(ctx);
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::clear_payload(ctx);
} 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;
}
});
}
}
}
/// 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.data_mut(|data| {
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
state.payload = Some(Arc::new(payload));
});
}
/// Clears the payload, setting it to `None`.
pub fn clear_payload(ctx: &Context) {
ctx.data_mut(|data| {
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
state.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.data(|data| {
let state = data.get_temp::<Self>(Id::NULL)?;
let payload = state.payload?;
payload.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.data_mut(|data| {
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
let payload = state.payload.take()?;
payload.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.data(|data| {
let state = data.get_temp::<Self>(Id::NULL);
state.map_or(false, |state| state.payload.is_some())
})
}
}