From 375fc395d2eb8e1e3d65133f95d17dcf4a0bb64e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Jan 2024 20:16:53 +0100 Subject: [PATCH] Add ui.dnd_drop_zone --- crates/egui/src/response.rs | 1 + crates/egui/src/ui.rs | 59 ++++++++++++++++++- .../egui_demo_lib/src/demo/drag_and_drop.rs | 58 +++--------------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 1646158e035..41965d72f9e 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -355,6 +355,7 @@ impl Response { None } } + /// Drag-and-Drop: Return what the is being dropped onto this widget, if any. /// /// Only returns something if [`Self::contains_pointer`] is true, diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 7b1c3c4df7c..658b867657f 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2128,14 +2128,14 @@ impl Ui { /// In contrast to [`Response::dnd_set_drag_payload`], /// this function will paint the widget at the mouse cursor while the user is dragging. #[doc(alias = "drag and drop")] - pub fn dnd_drag_source( + pub fn dnd_drag_source( &mut self, id: Id, - payload: T, + payload: Payload, add_contents: impl FnOnce(&mut Self) -> R, ) -> InnerResponse where - T: Any + Send + Sync, + Payload: Any + Send + Sync, { let is_being_dragged = self.memory(|mem| mem.is_being_dragged(id)); @@ -2169,6 +2169,59 @@ impl Ui { } } + /// Surround the given ui with a frame which + /// changes colors when you can drop something onto it. + /// + /// Returns the dropped item, if it was released this frame. + /// + /// The given frame is used for its margins, but it color is ignored. + #[doc(alias = "drag and drop")] + pub fn dnd_drop_zone( + &mut self, + frame: Frame, + add_contents: impl FnOnce(&mut Ui), + ) -> (Response, Option>) + where + Payload: Any + Send + Sync, + { + let is_anything_being_dragged = DragAndDrop::has_any_payload(self.ctx()); + let can_accept_what_is_being_dragged = + DragAndDrop::has_payload_of_type::(self.ctx()); + + let mut frame = frame.begin(self); + add_contents(&mut frame.content_ui); + let response = frame.allocate_space(self); + + // NOTE: we use `response.contains_pointer` here instead of `hovered`, because + // `hovered` is always false when another widget is being dragged. + let style = if is_anything_being_dragged + && can_accept_what_is_being_dragged + && response.contains_pointer() + { + self.visuals().widgets.active + } else { + self.visuals().widgets.inactive + }; + + let mut fill = style.bg_fill; + let mut stroke = style.bg_stroke; + + if is_anything_being_dragged && !can_accept_what_is_being_dragged { + // When dragging something else, show that it can't be dropped here: + fill = self.visuals().gray_out(fill); + stroke.color = self.visuals().gray_out(stroke.color); + } + + frame.frame.fill = fill; + frame.frame.stroke = stroke; + + frame.paint(self); + + let payload = response.dnd_release_payload::(); + + (response, payload) + } + /// Close the menu we are in (including submenus), if any. /// /// See also: [`Self::menu_button`] and [`Response::context_menu`]. 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 51d4e4e1b70..af697a130c2 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -38,6 +38,7 @@ impl super::Demo for DragAndDropDemo { } } +/// What is being dragged. struct DragInfo { col_idx: usize, row_idx: usize, @@ -48,27 +49,23 @@ impl super::View for DragAndDropDemo { ui.label("This is a simple example of drag-and-drop in egui."); ui.label("Drag items between columns."); - let id_source = "my_drag_and_drop_demo"; - 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 = - DragAndDrop::has_payload_of_type::(ui.ctx()); - let response = drop_zone(ui, can_accept_what_is_being_dragged, |ui| { + let frame = Frame::default().inner_margin(4.0); + 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(id_source).with(col_idx).with(row_idx); + 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.add(Label::new(item).sense(Sense::click())); + ui.label(item); }); } - }) - .response; + }); - if let Some(source) = response.dnd_release_payload::() { + if let Some(source) = dropped_payload { let item = self.columns[source.col_idx].remove(source.row_idx); self.columns[col_idx].push(item); } @@ -80,44 +77,3 @@ impl super::View for DragAndDropDemo { }); } } - -/// Paint a drop-zone which changes colors when you can drop something onto it. -fn drop_zone( - ui: &mut Ui, - can_accept_what_is_being_dragged: bool, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> InnerResponse { - let is_anything_being_dragged = DragAndDrop::has_any_payload(ui.ctx()); - - let frame = Frame::default().inner_margin(4.0); - let mut frame = frame.begin(ui); - let ret = add_contents(&mut frame.content_ui); - let response = frame.allocate_space(ui); - - // NOTE: we use `response.contains_pointer` here instead of `hovered`, because - // `hovered` is always false when another widget is being dragged. - let style = if is_anything_being_dragged - && can_accept_what_is_being_dragged - && response.contains_pointer() - { - ui.visuals().widgets.active - } else { - ui.visuals().widgets.inactive - }; - - let mut fill = style.bg_fill; - let mut stroke = style.bg_stroke; - - if is_anything_being_dragged && !can_accept_what_is_being_dragged { - // When dragging something else, show that it can't be dropped here. - fill = ui.visuals().gray_out(fill); - stroke.color = ui.visuals().gray_out(stroke.color); - } - - frame.frame.fill = fill; - frame.frame.stroke = stroke; - - frame.paint(ui); - - InnerResponse::new(ret, response) -}