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())
        })
    }
}