diff --git a/crates/egui_demo_lib/src/demo/drag_and_drop.rs b/crates/egui_demo_lib/src/demo/drag_and_drop.rs index af697a130c2..29bda3350ac 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -11,9 +11,9 @@ impl Default for DragAndDropDemo { fn default() -> Self { Self { columns: vec![ - vec!["Item A", "Item B", "Item C"], - vec!["Item D", "Item E"], - vec!["Item F", "Item G", "Item H"], + vec!["Item A", "Item B", "Item C", "Item D"], + vec!["Item E", "Item F", "Item G"], + vec!["Item H", "Item I", "Item J", "Item K"], ] .into_iter() .map(|v| v.into_iter().map(ToString::to_string).collect()) @@ -39,9 +39,10 @@ impl super::Demo for DragAndDropDemo { } /// What is being dragged. -struct DragInfo { - col_idx: usize, - row_idx: usize, +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Location { + col: usize, + row: usize, } impl super::View for DragAndDropDemo { @@ -49,29 +50,90 @@ impl super::View for DragAndDropDemo { ui.label("This is a simple example of drag-and-drop in egui."); ui.label("Drag items between columns."); + // If there is a drop, store the location of the item being dragged, and the destination for the drop. + let mut from = None; + let mut to = None; + ui.columns(self.columns.len(), |uis| { for (col_idx, column) in self.columns.clone().into_iter().enumerate() { let ui = &mut uis[col_idx]; let frame = Frame::default().inner_margin(4.0); - let (_, dropped_payload) = ui.dnd_drop_zone::(frame, |ui| { + + let (_, dropped_payload) = ui.dnd_drop_zone::(frame, |ui| { ui.set_min_size(vec2(64.0, 100.0)); for (row_idx, item) in column.iter().enumerate() { let item_id = Id::new(("my_drag_and_drop_demo", col_idx, row_idx)); - let payload = DragInfo { col_idx, row_idx }; - ui.dnd_drag_source(item_id, payload, |ui| { - ui.label(item); - }); + let item_location = Location { + col: col_idx, + row: row_idx, + }; + let response = ui + .dnd_drag_source(item_id, item_location, |ui| { + ui.label(item); + }) + .response; + + // Detect drops onto this item: + if let (Some(pointer), Some(hovered_payload)) = ( + ui.input(|i| i.pointer.interact_pos()), + response.dnd_hover_payload::(), + ) { + let rect = response.rect; + + // Preview insertion: + let stroke = egui::Stroke::new(1.0, Color32::WHITE); + let insert_row_idx = if *hovered_payload == item_location { + // We are dragged onto ourselves + ui.painter().hline(rect.x_range(), rect.center().y, stroke); + row_idx + } else if pointer.y < rect.center().y { + // Above us + ui.painter().hline(rect.x_range(), rect.top(), stroke); + row_idx + } else { + // Below us + ui.painter().hline(rect.x_range(), rect.bottom(), stroke); + row_idx + 1 + }; + + if let Some(dragged_payload) = response.dnd_release_payload() { + // The user dropped onto this item. + from = Some(dragged_payload); + to = Some(Location { + col: col_idx, + row: insert_row_idx, + }); + } + } } }); - if let Some(source) = dropped_payload { - let item = self.columns[source.col_idx].remove(source.row_idx); - self.columns[col_idx].push(item); + if let Some(dragged_payload) = dropped_payload { + // The user dropped onto the column, but not on any one item. + from = Some(dragged_payload); + to = Some(Location { + col: col_idx, + row: usize::MAX, // Inset last + }); } } }); + if let (Some(from), Some(mut to)) = (from, to) { + if from.col == to.col { + // Dragging within the same column. + // Adjust row index if we are re-ordering: + to.row -= (from.row < to.row) as usize; + } + + let item = self.columns[from.col].remove(from.row); + + let column = &mut self.columns[to.col]; + to.row = to.row.min(column.len()); + column.insert(to.row, item); + } + ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file!()); });