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,
}