From ca0dc079b158563635a58dfa73d589572036ee25 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Jan 2024 13:03:48 +0100 Subject: [PATCH] Add API for storing drag-and-drop payload --- crates/egui/src/context.rs | 1 + crates/egui/src/drag_and_drop.rs | 53 +++++++++++++++++++ crates/egui/src/lib.rs | 2 + .../egui_demo_lib/src/demo/drag_and_drop.rs | 44 ++++++++------- 4 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 crates/egui/src/drag_and_drop.rs diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 6e73b22eebc2..b124280b9c64 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -600,6 +600,7 @@ impl Default for Context { // Register built-in plugins: crate::debug_text::register(&ctx); crate::text_selection::LabelSelectionState::register(&ctx); + crate::DragAndDrop::register(&ctx); ctx } diff --git a/crates/egui/src/drag_and_drop.rs b/crates/egui/src/drag_and_drop.rs new file mode 100644 index 000000000000..da709840b19e --- /dev/null +++ b/crates/egui/src/drag_and_drop.rs @@ -0,0 +1,53 @@ +use std::{any::Any, sync::Arc}; + +use crate::{Context, Id}; + +/// Helpers for drag-and-drop in egui. +#[derive(Clone, Default)] +pub struct DragAndDrop { + /// If set, something is currently being dragged + payload: Option>, +} + +impl DragAndDrop { + pub(crate) fn register(ctx: &Context) { + ctx.on_end_frame("debug_text", std::sync::Arc::new(Self::end_frame)); + } + + fn end_frame(ctx: &Context) { + let pointer_released = ctx.input(|i| i.pointer.any_released()); + ctx.data_mut(|data| { + let state = data.get_temp_mut_or_default::(Id::NULL); + if pointer_released { + state.payload = None; + } + }); + } + + /// Set a drag-and-drop payload. + /// + /// This can be read by [`Self::payload`] until the pointer is released. + pub fn set_payload(ctx: &Context, payload: T) + where + T: Any + Send + Sync, + { + ctx.data_mut(|data| { + let state = data.get_temp_mut_or_default::(Id::NULL); + state.payload = Some(Arc::new(payload)); + }); + } + + /// Retrieve the payload, if any. + /// + /// Returns `None` if there is no payload, or if it is not of the requested type. + pub fn payload(ctx: &Context) -> Option> + where + T: Any + Send + Sync, + { + ctx.data(|data| { + let state = data.get_temp::(Id::NULL)?; + let payload = state.payload?; + payload.downcast().ok() + }) + } +} diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index cd1882411d65..67cfacc2c31c 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -348,6 +348,7 @@ pub mod containers; mod context; mod data; pub mod debug_text; +mod drag_and_drop; mod frame_state; pub(crate) mod grid; pub mod gui_zoom; @@ -417,6 +418,7 @@ pub use { }, Key, }, + drag_and_drop::DragAndDrop, grid::Grid, id::{Id, IdMap}, input_state::{InputState, MultiTouchInfo, PointerState}, 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 37c21238047b..c1fb6c277e10 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -1,6 +1,6 @@ use egui::*; -pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) { +pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) -> egui::Response { let is_being_dragged = ui.memory(|mem| mem.is_being_dragged(id)); if !is_being_dragged { @@ -11,6 +11,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) { if response.hovered() { ui.ctx().set_cursor_icon(CursorIcon::Grab); } + response } else { ui.ctx().set_cursor_icon(CursorIcon::Grabbing); @@ -29,6 +30,8 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) { let delta = pointer_pos - response.rect.center(); ui.ctx().translate_layer(layer_id, delta); } + + response } } @@ -111,23 +114,29 @@ impl super::Demo for DragAndDropDemo { } } +struct DragInfo { + col_idx: usize, + row_idx: usize, +} + impl super::View for DragAndDropDemo { fn ui(&mut self, ui: &mut Ui) { ui.label("This is a proof-of-concept of drag-and-drop in egui."); ui.label("Drag items between columns."); let id_source = "my_drag_and_drop_demo"; - let mut source_col_row = None; - let mut drop_col = 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 can_accept_what_is_being_dragged = true; // We accept anything being dragged (for now) ¯\_(ツ)_/¯ + let can_accept_what_is_being_dragged = + DragAndDrop::payload::(ui.ctx()).is_some(); + let response = drop_target(ui, can_accept_what_is_being_dragged, |ui| { ui.set_min_size(vec2(64.0, 100.0)); for (row_idx, item) in column.iter().enumerate() { let item_id = Id::new(id_source).with(col_idx).with(row_idx); - drag_source(ui, item_id, |ui| { + let response = drag_source(ui, item_id, |ui| { let response = ui.add(Label::new(item).sense(Sense::click())); response.context_menu(|ui| { if ui.button("Remove").clicked() { @@ -137,8 +146,8 @@ impl super::View for DragAndDropDemo { }); }); - if ui.memory(|mem| mem.is_being_dragged(item_id)) { - source_col_row = Some((col_idx, row_idx)); + if response.drag_started() { + DragAndDrop::set_payload(ui.ctx(), DragInfo { col_idx, row_idx }); } } }) @@ -151,28 +160,17 @@ impl super::View for DragAndDropDemo { } }); - let is_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged()); // NOTE: we use `response.contains_pointer` here instead of `hovered`, because // `hovered` is always false when another widget is being dragged. - if is_being_dragged - && can_accept_what_is_being_dragged - && response.contains_pointer() - { - drop_col = Some(col_idx); + if response.contains_pointer() && ui.input(|i| i.pointer.any_released()) { + if let Some(source) = DragAndDrop::payload::(ui.ctx()) { + let item = self.columns[source.col_idx].remove(source.row_idx); + self.columns[col_idx].push(item); + } } } }); - if let Some((source_col, source_row)) = source_col_row { - if let Some(drop_col) = drop_col { - if ui.input(|i| i.pointer.any_released()) { - // do the drop: - let item = self.columns[source_col].remove(source_row); - self.columns[drop_col].push(item); - } - } - } - ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file!()); });