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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]

use egui::{Id, Ui};
pub use state::{DragDropConfig, DragDropItem, DragDropResponse, DragUpdate, Handle};

use crate::item_iterator::ItemIterator;
use crate::state::DragDropUi;
use std::hash::Hash;

mod item;
mod item_iterator;
mod state;
/// Helper functions to support the drag and drop functionality
pub mod utils;

/// Helper struct for ease of use.
pub struct Dnd<'a> {
    id: Id,
    ui: &'a mut Ui,
    drag_drop_ui: DragDropUi,
}

/// Main entry point for the drag and drop functionality.
/// Loads and saves it's state from egui memory.
/// Use either [Dnd::show] or [Dnd::show_vec] to display the drag and drop UI.
/// You can use [Dnd::with_mouse_config] or [Dnd::with_touch_config] to configure the drag detection.
/// Example usage:
/// ```rust no_run
/// use std::hash::Hash;
/// use eframe::egui;
/// use egui::CentralPanel;
/// use egui_dnd::dnd;
///
/// pub fn main() -> eframe::Result<()> {
///     let mut items = vec!["alfred", "bernhard", "christian"];
///
///     eframe::run_simple_native("DnD Simple Example", Default::default(), move |ctx, _frame| {
///         CentralPanel::default().show(ctx, |ui| {
///
///             dnd(ui, "dnd_example")
///                 .show_vec(&mut items, |ui, item, handle, state| {
///                     handle.ui(ui, |ui| {
///                         ui.label("drag");
///                     });
///                     ui.label(*item);
///                 });
///
///         });
///     })
/// }
/// ```
pub fn dnd(ui: &mut Ui, id_source: impl Hash) -> Dnd {
    let id = Id::new(id_source).with("dnd");
    let mut dnd_ui: DragDropUi =
        ui.data_mut(|data| (*data.get_temp_mut_or_default::<DragDropUi>(id)).clone());

    dnd_ui.return_animation_time = ui.style().animation_time;
    dnd_ui.swap_animation_time = ui.style().animation_time;

    Dnd {
        id,
        ui,
        drag_drop_ui: dnd_ui,
    }
}

impl<'a> Dnd<'a> {
    /// Initialize the drag and drop UI. Same as [dnd].
    pub fn new(ui: &'a mut Ui, id_source: impl Hash) -> Self {
        dnd(ui, id_source)
    }

    /// Sets the config used when dragging with the mouse or when no touch config is set
    pub fn with_mouse_config(mut self, config: DragDropConfig) -> Self {
        self.drag_drop_ui = self.drag_drop_ui.with_mouse_config(config);
        self
    }

    /// Sets the config used when dragging with touch
    /// If None, the mouse config is used instead
    /// Use [DragDropConfig::touch] or [DragDropConfig::touch_scroll] to get a config optimized for touch
    /// The default is [DragDropConfig::touch]
    /// For dragging in a ScrollArea, use [DragDropConfig::touch_scroll]
    pub fn with_touch_config(mut self, config: Option<DragDropConfig>) -> Self {
        self.drag_drop_ui = self.drag_drop_ui.with_touch_config(config);
        self
    }

    /// Sets the animation time for the return animation (after dropping an item)
    /// The default is the same as the egui animation time
    /// If you want to disable the animation, set it to 0
    pub fn with_return_animation_time(mut self, animation_time: f32) -> Self {
        self.drag_drop_ui.return_animation_time = animation_time;
        self
    }

    /// Sets the animation time for the swap animation (when dragging an item over another item)
    /// The default is the same as the egui animation time
    /// If you want to disable the animation, set it to 0
    pub fn with_swap_animation_time(mut self, animation_time: f32) -> Self {
        self.drag_drop_ui.swap_animation_time = animation_time;
        self
    }

    /// Sets the animation time for all animations
    /// The default is the same as the egui animation time
    /// If you want to disable all animations, set it to 0
    pub fn with_animation_time(mut self, animation_time: f32) -> Self {
        self.drag_drop_ui.return_animation_time = animation_time;
        self.drag_drop_ui.swap_animation_time = animation_time;
        self
    }

    /// Display the drag and drop UI.
    /// `items` should be an iterator over items that should be sorted.
    ///
    /// The items won't be sorted automatically, but you can use [Dnd::show_vec] or [DragDropResponse::update_vec] to do so.
    /// If your items aren't in a vec, you have to sort them yourself.
    ///
    /// `item_ui` is called for each item. Display your item there.
    /// `item_ui` gets a [Handle] that can be used to display the drag handle.
    /// Only the handle can be used to drag the item. If you want the whole item to be draggable, put everything in the handle.
    pub fn show<T: DragDropItem>(
        self,
        items: impl Iterator<Item = T>,
        mut item_ui: impl FnMut(&mut Ui, T, Handle, ItemState),
    ) -> DragDropResponse {
        self._show_with_inner(|_id, ui, drag_drop_ui| {
            drag_drop_ui.ui(ui, |ui, iter| {
                items.enumerate().for_each(|(i, item)| {
                    iter.next(ui, item.id(), i, true, |ui, item_handle| {
                        item_handle.ui(ui, |ui, handle, state| item_ui(ui, item, handle, state))
                    });
                });
            })
        })
    }

    /// Same as [Dnd::show], but with a fixed size for each item.
    /// This allows items to be placed in a horizontal_wrapped ui.
    /// For more info, look at the [horizontal example](https://github.com/lucasmerlin/hello_egui/blob/main/crates/egui_dnd/examples/horizontal.rs).
    /// If you need even more control over the size, use [Dnd::show_custom] instead, where you
    /// can individually size each item. See the sort_words example for an example.
    pub fn show_sized<T: DragDropItem>(
        self,
        items: impl Iterator<Item = T>,
        size: egui::Vec2,
        mut item_ui: impl FnMut(&mut Ui, T, Handle, ItemState),
    ) -> DragDropResponse {
        self._show_with_inner(|_id, ui, drag_drop_ui| {
            drag_drop_ui.ui(ui, |ui, iter| {
                items.enumerate().for_each(|(i, item)| {
                    iter.next(ui, item.id(), i, true, |ui, item_handle| {
                        item_handle.ui_sized(ui, size, |ui, handle, state| {
                            item_ui(ui, item, handle, state)
                        })
                    });
                });
            })
        })
    }

    /// Same as [Dnd::show], but automatically sorts the items.
    pub fn show_vec<T: Hash>(
        self,
        items: &mut [T],
        item_ui: impl FnMut(&mut Ui, &mut T, Handle, ItemState),
    ) -> DragDropResponse {
        let response = self.show(items.iter_mut(), item_ui);
        response.update_vec(items);
        response
    }

    /// Same as [Dnd::show_sized], but automatically sorts the items.
    pub fn show_vec_sized<T: Hash>(
        self,
        items: &mut [T],
        size: egui::Vec2,
        item_ui: impl FnMut(&mut Ui, &mut T, Handle, ItemState),
    ) -> DragDropResponse {
        let response = self.show_sized(items.iter_mut(), size, item_ui);
        response.update_vec(items);
        response
    }

    /// This will allow for very flexible UI. You can use it to e.g. render outlines around items
    /// or render items in complex layouts. This is **experimental**.
    pub fn show_custom(self, f: impl FnOnce(&mut Ui, &mut ItemIterator)) -> DragDropResponse {
        self._show_with_inner(|_id, ui, drag_drop_ui| drag_drop_ui.ui(ui, f))
    }

    /// Same as [Dnd::show_custom], but automatically sorts the items.
    pub fn show_custom_vec<T: Hash>(
        self,
        items: &mut [T],
        f: impl FnOnce(&mut Ui, &mut [T], &mut ItemIterator),
    ) -> DragDropResponse {
        let response = self.show_custom(|ui, iter| f(ui, items, iter));
        response.update_vec(items);
        response
    }

    fn _show_with_inner(
        self,
        inner_fn: impl FnOnce(Id, &mut Ui, &mut DragDropUi) -> DragDropResponse,
    ) -> DragDropResponse {
        let Dnd {
            id,
            ui,
            mut drag_drop_ui,
        } = self;

        let response = inner_fn(id, ui, &mut drag_drop_ui);

        ui.ctx().data_mut(|data| data.insert_temp(id, drag_drop_ui));

        response
    }
}

/// State of the current item.
pub struct ItemState {
    /// True if the item is currently being dragged.
    pub dragged: bool,
    /// Index of the item in the list.
    /// Note that when you sort the source list while the drag is still ongoing (default behaviour
    /// of [Dnd::show_vec]), this index will updated while the item is being dragged.
    /// If you sort once after the item is dropped, the index will be stable during the drag.
    pub index: usize,
}