Skip to content

Commit

Permalink
Add ui.dnd_drop_zone
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Jan 25, 2024
1 parent e1a7b32 commit 375fc39
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 54 deletions.
1 change: 1 addition & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
59 changes: 56 additions & 3 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, R>(
pub fn dnd_drag_source<Payload, R>(
&mut self,
id: Id,
payload: T,
payload: Payload,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R>
where
T: Any + Send + Sync,
Payload: Any + Send + Sync,
{
let is_being_dragged = self.memory(|mem| mem.is_being_dragged(id));

Expand Down Expand Up @@ -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<Payload>(
&mut self,
frame: Frame,
add_contents: impl FnOnce(&mut Ui),
) -> (Response, Option<Arc<Payload>>)
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::<Payload>(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::<Payload>();

(response, payload)
}

/// Close the menu we are in (including submenus), if any.
///
/// See also: [`Self::menu_button`] and [`Response::context_menu`].
Expand Down
58 changes: 7 additions & 51 deletions crates/egui_demo_lib/src/demo/drag_and_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl super::Demo for DragAndDropDemo {
}
}

/// What is being dragged.
struct DragInfo {
col_idx: usize,
row_idx: usize,
Expand All @@ -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::<DragInfo>(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::<DragInfo>(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::<DragInfo>() {
if let Some(source) = dropped_payload {
let item = self.columns[source.col_idx].remove(source.row_idx);
self.columns[col_idx].push(item);
}
Expand All @@ -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<R>(
ui: &mut Ui,
can_accept_what_is_being_dragged: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
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)
}

0 comments on commit 375fc39

Please sign in to comment.