Skip to content

Commit

Permalink
Add API for storing drag-and-drop payload
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Jan 25, 2024
1 parent d190df7 commit 264ec2d
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 23 deletions.
1 change: 1 addition & 0 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
53 changes: 53 additions & 0 deletions crates/egui/src/drag_and_drop.rs
Original file line number Diff line number Diff line change
@@ -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<Arc<dyn Any + Send + Sync>>,
}

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());
if pointer_released {
ctx.data_mut(|data| {
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
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<T>(ctx: &Context, payload: T)
where
T: Any + Send + Sync,
{
ctx.data_mut(|data| {
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
state.payload = Some(Arc::new(payload));
});
}

/// Retreive the payload, if any.

Check warning on line 40 in crates/egui/src/drag_and_drop.rs

View workflow job for this annotation

GitHub Actions / typos

"Retreive" should be "Retrieve".
///
/// Returns `None` if there is no payload, or if it is not of the requested type.
pub fn payload<T>(ctx: &Context) -> Option<Arc<T>>
where
T: Any + Send + Sync,
{
ctx.data(|data| {
let state = data.get_temp::<Self>(Id::NULL)?;
let payload = state.payload?;
payload.downcast().ok()
})
}
}
2 changes: 2 additions & 0 deletions crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -417,6 +418,7 @@ pub use {
},
Key,
},
drag_and_drop::DragAndDrop,
grid::Grid,
id::{Id, IdMap},
input_state::{InputState, MultiTouchInfo, PointerState},
Expand Down
44 changes: 21 additions & 23 deletions crates/egui_demo_lib/src/demo/drag_and_drop.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);

Expand All @@ -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
}
}

Expand Down Expand Up @@ -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::<DragInfo>(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() {
Expand All @@ -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 });
}
}
})
Expand All @@ -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::<DragInfo>(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!());
});
Expand Down

0 comments on commit 264ec2d

Please sign in to comment.