-
-
Save enomado/5e1de10edfa55bf005d95acd0930af2b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{borrow::BorrowMut, f64::consts::TAU}; | |
use eframe::{ | |
egui::{ | |
self, | |
plot::{CoordinatesFormatter, Corner, Legend, Line, LineStyle, Plot, Value, Values}, | |
Button, ComboBox, Context, CursorIcon, Id, InnerResponse, Label, LayerId, Order, Response, | |
Sense, Ui, Widget, | |
}, | |
emath::{remap, NumExt}, | |
epaint::{self, color, vec2, Color32, Pos2, Rect, Shape, Stroke, Vec2}, | |
epi, | |
}; | |
pub fn drag_source( | |
ui: &mut Ui, | |
id: Id, | |
drag_handle: impl FnOnce(&mut Ui), | |
drag_body: impl FnOnce(&mut Ui), | |
) -> Option<Rect> { | |
let is_being_dragged = ui.memory().is_being_dragged(id); | |
if !is_being_dragged { | |
let row_resp = ui.horizontal(|gg| { | |
let u = gg.scope(drag_handle); | |
// Check for drags: | |
// let response = ui.interact(response.rect, id, Sense::click()); | |
let response = gg.interact(u.response.rect, id, Sense::drag()); | |
if response.hovered() { | |
gg.output().cursor_icon = CursorIcon::Grab; | |
} | |
drag_body(gg) | |
}); | |
return Some(row_resp.response.rect); | |
// sponse.clicked() { | |
// println!("source clicked") | |
// } | |
} else { | |
ui.output().cursor_icon = CursorIcon::Grabbing; | |
// let response = ui.scope(body).response; | |
// Paint the body to a new layer: | |
let layer_id = LayerId::new(Order::Tooltip, id); | |
// let response = ui.with_layer_id(layer_id, body).response; | |
// Now we move the visuals of the body to where the mouse is. | |
// Normally you need to decide a location for a widget first, | |
// because otherwise that widget cannot interact with the mouse. | |
// However, a dragged component cannot be interacted with anyway | |
// (anything with `Order::Tooltip` always gets an empty [`Response`]) | |
// So this is fine! | |
// if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { | |
// let r = response.rect.center(); | |
// let delta = pointer_pos - r; | |
// ui.ctx().translate_layer(layer_id, delta); | |
// } | |
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { | |
let u = egui::Area::new("draggable_item") | |
.interactable(false) | |
.fixed_pos(pointer_pos) | |
.show(ui.ctx(), |x| { | |
x.horizontal(|gg| { | |
drag_handle(gg); | |
drag_body(gg) | |
}) | |
}); | |
// u.response.rect.area() | |
} | |
} | |
None | |
} | |
pub fn drop_target<R>( | |
ui: &mut Ui, | |
can_accept_what_is_being_dragged: bool, | |
is_being_dragged: bool, | |
body: impl FnOnce(&mut Ui) -> R, | |
) -> InnerResponse<R> { | |
// рендерит колонку | |
// let is_being_dragged = ui.memory().is_anything_being_dragged(); | |
// let is_being_dragged = false; | |
let margin = Vec2::splat(4.0); | |
let outer_rect_bounds = ui.available_rect_before_wrap(); | |
let inner_rect = outer_rect_bounds.shrink2(margin); | |
let where_to_put_background = ui.painter().add(Shape::Noop); | |
let mut content_ui = ui.child_ui(inner_rect, *ui.layout()); | |
let ret = body(&mut content_ui); | |
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin); | |
// let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover()); | |
let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover()); | |
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() { | |
ui.visuals().widgets.active | |
} else { | |
ui.visuals().widgets.inactive | |
}; | |
let mut fill = style.bg_fill; | |
let mut stroke = style.bg_stroke; | |
// let mut stroke: Stroke = (0.4, Color32::RED).into(); | |
if is_being_dragged && !can_accept_what_is_being_dragged { | |
// gray out: | |
fill = color::tint_color_towards(fill, ui.visuals().window_fill()); | |
stroke.color = color::tint_color_towards(stroke.color, ui.visuals().window_fill()); | |
} | |
ui.painter().set( | |
where_to_put_background, | |
epaint::RectShape { | |
rounding: style.rounding, | |
fill, | |
stroke, | |
rect, | |
}, | |
); | |
InnerResponse::new(ret, response) | |
} | |
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] | |
pub struct DragAndDropExampleState { | |
#[cfg_attr(feature = "persistence", serde(skip))] | |
columns: Vec<Vec<String>>, | |
} | |
impl Default for DragAndDropExampleState { | |
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"], | |
] | |
.into_iter() | |
.map(|v| v.into_iter().map(ToString::to_string).collect()) | |
.collect(), | |
} | |
} | |
} | |
pub struct DragAndDropExample<'a> { | |
pub state: &'a mut DragAndDropExampleState, | |
} | |
#[derive(Clone, Copy, Debug, Default)] | |
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] | |
#[cfg_attr(feature = "serde", serde(default))] | |
pub(crate) struct DndIntState { | |
// #[cfg_attr(feature = "persistence", serde(skip))] | |
dragging_column_id: Option<usize>, | |
// #[cfg_attr(feature = "persistence", serde(skip))] | |
drop_col: Option<usize>, | |
// #[cfg_attr(feature = "persistence", serde(skip))] | |
source_col_row: Option<(usize, usize)>, | |
} | |
impl DndIntState { | |
pub fn load(ctx: &Context, id: Id) -> Option<Self> { | |
ctx.data().get_temp(id) | |
} | |
pub fn store(self, ctx: &Context, id: Id) { | |
ctx.data().insert_temp(id, self); | |
} | |
} | |
impl<'a> Widget for DragAndDropExample<'a> { | |
fn ui(self, ui: &mut Ui) -> Response { | |
let dummy_resp = 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 container_rect = None; | |
let mut row_rect = None; | |
let columns = &mut self.state.columns; | |
let id = Id::new(id_source); | |
let mut state = DndIntState::load(ui.ctx(), id).unwrap_or_default(); | |
let drag_target_row_position = &mut state.dragging_column_id; | |
let source_col_row = &mut state.source_col_row; | |
let drop_col = &mut state.drop_col; | |
// | |
ui.columns(columns.len(), |uis| { | |
for (col_idx, column) in 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 this_col_is_dest = drop_col.map(|x| x == col_idx).unwrap_or(false); | |
let response = drop_target( | |
ui, | |
this_col_is_dest, | |
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); | |
if source_col_row.is_some() | |
&& *drag_target_row_position == Some(row_idx) | |
&& drop_col.map(|col| col == col_idx).unwrap_or(false) | |
{ | |
ui.add(Label::new("here")); | |
} | |
let c_row_size_rect = drag_source( | |
ui, | |
item_id, | |
|ui| { | |
let response = ui.add(Label::new(item).sense(Sense::click())); | |
// .sense(Sense::click()) | |
if response.clicked() { | |
println!("button clicked handle") | |
} | |
response.context_menu(|ui| { | |
if ui.button("Remove").clicked() { | |
columns[col_idx].remove(row_idx); | |
ui.close_menu(); | |
} | |
}); | |
}, | |
|ui| { | |
let butt = Button::new(item).sense(Sense::click()); | |
let response = ui.add(butt); | |
if response.clicked() { | |
println!("button clicked sss") | |
} | |
}, | |
); | |
c_row_size_rect.map(|x| row_rect = Some(x)); | |
if ui.memory().is_being_dragged(item_id) { | |
*source_col_row = Some((col_idx, row_idx)); | |
} | |
} | |
if source_col_row.is_some() | |
&& drag_target_row_position | |
.map(|x| x >= column.len()) | |
.unwrap_or(false) | |
&& drop_col.map(|col| col == col_idx).unwrap_or(false) | |
{ | |
ui.add(Label::new("here1")); | |
} | |
}, | |
) | |
.response; | |
let response = response.context_menu(|ui| { | |
if ui.button("New Item").clicked() { | |
columns[col_idx].push("New Item".to_string()); | |
ui.close_menu(); | |
} | |
}); | |
// let is_being_dragged = ui.memory().is_anything_being_dragged(); | |
let is_being_dragged = source_col_row.is_some(); | |
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() { | |
*drop_col = Some(col_idx); | |
container_rect = Some(response.rect); | |
} | |
} | |
}); | |
if let (Some(drop_col), Some(row_rect), Some(container_rect)) = | |
(*drop_col, row_rect, container_rect) | |
{ | |
if ui.memory().is_anything_being_dragged() { | |
let pos = ui.input().pointer.hover_pos(); | |
let row_rectr = row_rect.size(); | |
let offset = pos.unwrap() - container_rect.min; | |
let drag_position = | |
(((offset.y - row_rectr.y / 2.) / row_rectr.y).round() as usize); | |
// .at_most(self.columns[drop_col].len().saturating_sub(1)); | |
*drag_target_row_position = Some(drag_position); | |
} else { | |
*drag_target_row_position = None; | |
} | |
} else { | |
*drag_target_row_position = None; | |
} | |
if let Some((source_col, source_row)) = *source_col_row { | |
if let Some(drop_col) = *drop_col { | |
// | |
if ui.input().pointer.any_released() { | |
// dbg!(); | |
// do the drop: | |
if let Some(drag_target_row_position) = drag_target_row_position { | |
let item = columns[source_col].remove(source_row); | |
let insert_index = | |
drag_target_row_position.at_most(columns[drop_col].len()); | |
columns[drop_col].insert(insert_index, item); | |
} | |
} | |
} | |
} | |
if ui.input().pointer.any_released() { | |
*source_col_row = None; | |
*drop_col = None | |
} | |
ui.vertical_centered(|ui| { | |
ui.label("text sdfsdf") | |
// ui.add(crate::__egui_github_link_file!()); | |
}); | |
state.store(ui.ctx(), id); | |
dummy_resp | |
} | |
} | |
impl<'a> DragAndDropExample<'a> { | |
pub fn new(dnd_example: &'a mut DragAndDropExampleState) -> DragAndDropExample<'a> { | |
DragAndDropExample { state: dnd_example } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment