1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

Buggy drop-detection

This commit is contained in:
Emil Ernerfeldt
2023-04-23 22:45:25 +02:00
parent 9b154c6d1d
commit b5eddc5d5c
3 changed files with 307 additions and 155 deletions

View File

@@ -86,8 +86,7 @@ pub enum NodeLayout<Leaf> {
Leaf(Leaf), Leaf(Leaf),
Tabs(Tabs), Tabs(Tabs),
Horizontal(Horizontal), Horizontal(Horizontal),
// Vertical(Vertical) Vertical(Vertical),
// Grid(Grid)
} }
impl<Leaf> NodeLayout<Leaf> { impl<Leaf> NodeLayout<Leaf> {
@@ -96,6 +95,7 @@ impl<Leaf> NodeLayout<Leaf> {
NodeLayout::Leaf(_) => "Leaf", NodeLayout::Leaf(_) => "Leaf",
NodeLayout::Tabs(_) => "Tabs", NodeLayout::Tabs(_) => "Tabs",
NodeLayout::Horizontal(_) => "Horizontal", NodeLayout::Horizontal(_) => "Horizontal",
NodeLayout::Vertical(_) => "Vertical",
} }
} }
} }
@@ -112,6 +112,12 @@ pub struct Horizontal {
pub shares: Shares, pub shares: Shares,
} }
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Vertical {
pub children: Vec<NodeId>,
pub shares: Shares,
}
/// How large of a share of space each child has, on a 1D axis. /// How large of a share of space each child has, on a 1D axis.
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Shares { pub struct Shares {
@@ -224,6 +230,10 @@ impl<Leaf> Dock<Leaf> {
} }
impl<Leaf> Nodes<Leaf> { impl<Leaf> Nodes<Leaf> {
pub fn get(&self, node_id: NodeId) -> Option<&NodeLayout<Leaf>> {
self.nodes.get(&node_id).map(|node| &node.layout)
}
#[must_use] #[must_use]
pub fn insert_node(&mut self, node: NodeState<Leaf>) -> NodeId { pub fn insert_node(&mut self, node: NodeState<Leaf>) -> NodeId {
let id = NodeId::random(); let id = NodeId::random();
@@ -254,8 +264,83 @@ impl<Leaf> Nodes<Leaf> {
self.insert_node(NodeLayout::Horizontal(horizontal).into()) self.insert_node(NodeLayout::Horizontal(horizontal).into())
} }
pub fn get(&self, node_id: NodeId) -> Option<&NodeLayout<Leaf>> { fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) {
self.nodes.get(&node_id).map(|node| &node.layout) let Some(mut node) = self.nodes.remove(&it) else { return; };
match &mut node.layout {
NodeLayout::Leaf(_) => {}
NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. })
| NodeLayout::Vertical(Vertical { children, .. }) => {
children.retain(|&child| {
self.remove_node_id_from_parent(child, remove);
child != remove
});
}
}
self.nodes.insert(it, node);
}
fn insert(&mut self, insertion_point: InsertionPoint, child: NodeId) {
let InsertionPoint {
parent_id,
layout_type,
index,
} = insertion_point;
let Some(mut node) = self.nodes.remove(&parent_id) else {
#[cfg(feature = "log")]
log::warn!("failed to insert");
return;
};
match layout_type {
LayoutType::Tabs => {
if let NodeLayout::Tabs(layout) = &mut node.layout {
let index = index.min(layout.children.len());
layout.children.insert(index, child);
self.nodes.insert(parent_id, node);
} else {
let new_node_id = self.insert_node(node);
let mut layout = Tabs {
children: vec![new_node_id],
active: new_node_id,
};
layout.children.insert(index.min(1), child);
self.nodes
.insert(parent_id, NodeLayout::Tabs(layout).into());
}
}
LayoutType::Horizontal => {
if let NodeLayout::Horizontal(layout) = &mut node.layout {
let index = index.min(layout.children.len());
layout.children.insert(index, child);
self.nodes.insert(parent_id, node);
} else {
let new_node_id = self.insert_node(node);
let mut layout = Horizontal {
children: vec![new_node_id],
shares: Default::default(),
};
layout.children.insert(index.min(1), child);
self.nodes
.insert(parent_id, NodeLayout::Horizontal(layout).into());
}
}
LayoutType::Vertical => {
if let NodeLayout::Vertical(layout) = &mut node.layout {
let index = index.min(layout.children.len());
layout.children.insert(index, child);
self.nodes.insert(parent_id, node);
} else {
let new_node_id = self.insert_node(node);
let mut layout = Vertical {
children: vec![new_node_id],
shares: Default::default(),
};
layout.children.insert(index.min(1), child);
self.nodes
.insert(parent_id, NodeLayout::Vertical(layout).into());
}
}
}
} }
} }
@@ -275,12 +360,22 @@ impl<Leaf> Dock<Leaf> {
self.root, self.root,
); );
self.nodes.node_ui(behavior, ui, self.root);
// Check if anything is being dragged: // Check if anything is being dragged:
let mouse_pos = ui.input(|i| i.pointer.hover_pos()); let mut drop_context = DropContext {
let dragged_id = self.dragged_id(ui.ctx()); active: true,
if let (Some(mouse_pos), Some(dragged_node_id)) = (mouse_pos, dragged_id) { dragged_node_id: self.dragged_id(ui.ctx()),
mouse_pos: ui.input(|i| i.pointer.hover_pos()),
best_dist_sq: f32::INFINITY,
best_insertion: None,
preview_rect: None,
};
self.nodes
.node_ui(behavior, &mut drop_context, ui, self.root);
if let (Some(mouse_pos), Some(dragged_node_id)) =
(drop_context.mouse_pos, drop_context.dragged_node_id)
{
// Preview what is being dragged: // Preview what is being dragged:
egui::Area::new(Id::new((dragged_node_id, "preview"))) egui::Area::new(Id::new((dragged_node_id, "preview")))
.pivot(egui::Align2::CENTER_CENTER) .pivot(egui::Align2::CENTER_CENTER)
@@ -294,36 +389,28 @@ impl<Leaf> Dock<Leaf> {
}); });
}); });
let mut drop_context = DropContext {
dragged_node_id,
mouse_pos,
best_dist_sq: f32::INFINITY,
target_node_id: None,
preview_rect: None,
};
self.nodes.find_drop_target(&mut drop_context, self.root);
if let Some(preview_rect) = drop_context.preview_rect { if let Some(preview_rect) = drop_context.preview_rect {
ui.painter().rect_filled( ui.painter().rect(
preview_rect, preview_rect,
1.0, 1.0,
Color32::LIGHT_BLUE.gamma_multiply(0.5), Color32::LIGHT_BLUE.gamma_multiply(0.5),
(1.0, Color32::LIGHT_BLUE),
); );
} }
// if ui.input(|i| i.pointer.any_released()) { if ui.input(|i| i.pointer.any_released()) {
// ui.memory_mut(|mem| mem.stop_dragging()); ui.memory_mut(|mem| mem.stop_dragging());
// if let Some(target_node_id) = drop_context.target_node_id { if let Some(insertion_point) = drop_context.best_insertion {
// self.remove_node_id_from_parent(dragged_node_id); self.remove_node_id_from_parent(dragged_node_id);
// // self.drop_node(behavior, target_node_id, dragged_node_id); // TODO self.nodes.insert(insertion_point, dragged_node_id);
// } }
// } }
} }
} }
/// Find the currently dragged node, if any. /// Find the currently dragged node, if any.
fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> { fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
if ctx.input(|i| i.pointer.could_any_button_be_click()) { if !is_possible_drag(ctx) {
// We're not sure we're dragging _at all_ yet. // We're not sure we're dragging _at all_ yet.
return None; return None;
} }
@@ -354,6 +441,10 @@ impl<Leaf> Dock<Leaf> {
} }
} }
fn is_possible_drag(ctx: &egui::Context) -> bool {
ctx.input(|i| !i.pointer.could_any_button_be_click() && !i.pointer.any_pressed())
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// gc // gc
@@ -367,6 +458,12 @@ impl<Leaf> Nodes<Leaf> {
fn gc_root(&mut self, behavior: &mut dyn Behavior<Leaf>, root_id: NodeId) { fn gc_root(&mut self, behavior: &mut dyn Behavior<Leaf>, root_id: NodeId) {
let mut visited = HashSet::default(); let mut visited = HashSet::default();
self.gc_node_id(behavior, &mut visited, root_id); self.gc_node_id(behavior, &mut visited, root_id);
#[cfg(feature = "log")]
if visited.len() < self.nodes.len() {
log::warn!("GC collection {} nodes", self.nodes.len() - visited.len());
}
self.nodes.retain(|node_id, _| visited.contains(node_id)); self.nodes.retain(|node_id, _| visited.contains(node_id));
} }
@@ -379,7 +476,7 @@ impl<Leaf> Nodes<Leaf> {
let Some(mut node) = self.nodes.remove(&node_id) else { return GcAction::Remove; }; let Some(mut node) = self.nodes.remove(&node_id) else { return GcAction::Remove; };
if !visited.insert(node_id) { if !visited.insert(node_id) {
#[cfg(feature = "log")] #[cfg(feature = "log")]
log::warn!("Cycle detected in egui_extras::dock"); log::warn!("Cycle or duplication detected");
return GcAction::Remove; return GcAction::Remove;
} }
@@ -390,7 +487,8 @@ impl<Leaf> Nodes<Leaf> {
} }
} }
NodeLayout::Tabs(Tabs { children, .. }) NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. }) => { | NodeLayout::Horizontal(Horizontal { children, .. })
| NodeLayout::Vertical(Vertical { children, .. }) => {
children children
.retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep); .retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
} }
@@ -422,6 +520,9 @@ impl<Leaf> Nodes<Leaf> {
NodeLayout::Horizontal(horizontal) => { NodeLayout::Horizontal(horizontal) => {
self.layout_horizontal(style, behavior, rect, horizontal); self.layout_horizontal(style, behavior, rect, horizontal);
} }
NodeLayout::Vertical(vertical) => {
self.layout_vertical(style, behavior, rect, vertical);
}
} }
self.nodes.insert(node_id, node); self.nodes.insert(node_id, node);
@@ -452,62 +553,189 @@ impl<Leaf> Nodes<Leaf> {
style: &Style, style: &Style,
behavior: &mut dyn Behavior<Leaf>, behavior: &mut dyn Behavior<Leaf>,
rect: Rect, rect: Rect,
horizontal: &Horizontal, layout: &Horizontal,
) { ) {
if horizontal.children.is_empty() { if layout.children.is_empty() {
return; return;
} }
let num_gaps = horizontal.children.len() - 1; let num_gaps = layout.children.len() - 1;
let gap_width = behavior.gap_width(style); let gap_width = behavior.gap_width(style);
let total_gap_width = gap_width * num_gaps as f32; let total_gap_width = gap_width * num_gaps as f32;
let available_width = (rect.width() - total_gap_width).at_least(0.0); let available_width = (rect.width() - total_gap_width).at_least(0.0);
let widths = horizontal let widths = layout.shares.split(&layout.children, available_width);
.shares
.split(&horizontal.children, available_width);
let mut x = rect.min.x; let mut x = rect.min.x;
for (child, width) in horizontal.children.iter().zip(widths) { for (child, width) in layout.children.iter().zip(widths) {
let child_rect = Rect::from_min_size(pos2(x, rect.min.y), vec2(width, rect.height())); let child_rect = Rect::from_min_size(pos2(x, rect.min.y), vec2(width, rect.height()));
self.layout_node(style, behavior, child_rect, *child); self.layout_node(style, behavior, child_rect, *child);
x += width + gap_width; x += width + gap_width;
} }
} }
fn layout_vertical(
&mut self,
style: &Style,
behavior: &mut dyn Behavior<Leaf>,
rect: Rect,
layout: &Vertical,
) {
if layout.children.is_empty() {
return;
}
let num_gaps = layout.children.len() - 1;
let gap_height = behavior.gap_width(style);
let total_gap_height = gap_height * num_gaps as f32;
let available_height = (rect.height() - total_gap_height).at_least(0.0);
let heights = layout.shares.split(&layout.children, available_height);
let mut y = rect.min.y;
for (child, height) in layout.children.iter().zip(heights) {
let child_rect = Rect::from_min_size(pos2(rect.min.x, y), vec2(rect.width(), height));
self.layout_node(style, behavior, child_rect, *child);
y += height + gap_height;
}
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// ui // ui
enum LayoutType {
Tabs,
Horizontal,
Vertical,
}
struct InsertionPoint {
parent_id: NodeId,
layout_type: LayoutType,
/// Where in the parent?
index: usize,
}
struct DropContext {
active: bool,
dragged_node_id: Option<NodeId>,
mouse_pos: Option<Pos2>,
best_insertion: Option<InsertionPoint>,
best_dist_sq: f32,
preview_rect: Option<Rect>,
}
impl DropContext {
fn on_node(&mut self, parent_id: NodeId, rect: Rect) {
self.suggest_point(
InsertionPoint {
parent_id,
layout_type: LayoutType::Horizontal,
index: 0,
},
rect.left_center(),
rect.split_left_right_at_fraction(0.5).0,
);
self.suggest_point(
InsertionPoint {
parent_id,
layout_type: LayoutType::Horizontal,
index: usize::MAX,
},
rect.right_center(),
rect.split_left_right_at_fraction(0.5).1,
);
self.suggest_point(
InsertionPoint {
parent_id,
layout_type: LayoutType::Vertical,
index: 0,
},
rect.center_top(),
rect.split_top_bottom_at_fraction(0.5).0,
);
self.suggest_point(
InsertionPoint {
parent_id,
layout_type: LayoutType::Vertical,
index: usize::MAX,
},
rect.center_bottom(),
rect.split_top_bottom_at_fraction(0.5).1,
);
self.suggest_point(
InsertionPoint {
parent_id,
layout_type: LayoutType::Tabs,
index: 1,
},
rect.center(),
rect,
);
}
fn suggest_point(&mut self, insertion: InsertionPoint, target_point: Pos2, preview_rect: Rect) {
if let Some(mouse_pos) = self.mouse_pos {
let dist_sq = mouse_pos.distance_sq(target_point);
if dist_sq < self.best_dist_sq {
self.best_dist_sq = dist_sq;
self.best_insertion = Some(insertion);
self.preview_rect = Some(preview_rect);
}
}
}
}
impl<Leaf> Nodes<Leaf> { impl<Leaf> Nodes<Leaf> {
fn node_ui(&mut self, behavior: &mut dyn Behavior<Leaf>, ui: &mut Ui, node_id: NodeId) { fn node_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
node_id: NodeId,
) {
let Some(mut node) = self.nodes.remove(&node_id) else { return }; let Some(mut node) = self.nodes.remove(&node_id) else { return };
let drop_context_was_active = drop_context.active;
if Some(node_id) == drop_context.dragged_node_id {
// Can't drag a node onto self or any children
drop_context.active = true;
}
drop_context.on_node(node_id, node.rect);
match &mut node.layout { match &mut node.layout {
NodeLayout::Leaf(leaf) => { NodeLayout::Leaf(leaf) => {
let mut leaf_ui = ui.child_ui(node.rect, *ui.layout()); let mut leaf_ui = ui.child_ui(node.rect, *ui.layout());
behavior.leaf_ui(&mut leaf_ui, node_id, leaf); behavior.leaf_ui(&mut leaf_ui, node_id, leaf);
} }
NodeLayout::Tabs(tabs) => self.tabs_ui(behavior, ui, node.rect, tabs), NodeLayout::Tabs(tabs) => {
self.tabs_ui(behavior, drop_context, ui, node.rect, node_id, tabs);
}
NodeLayout::Horizontal(horizontal) => { NodeLayout::Horizontal(horizontal) => {
self.horizontal_ui(behavior, ui, node.rect, horizontal); self.horizontal_ui(behavior, drop_context, ui, node_id, horizontal);
}
NodeLayout::Vertical(vertical) => {
self.vertical_ui(behavior, drop_context, ui, node_id, vertical);
} }
}; };
self.nodes.insert(node_id, node); self.nodes.insert(node_id, node);
drop_context.active = drop_context_was_active;
} }
fn tabs_ui( fn tabs_ui(
&mut self, &mut self,
behavior: &mut dyn Behavior<Leaf>, behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui, ui: &mut Ui,
rect: Rect, rect: Rect,
parent_id: NodeId,
tabs: &mut Tabs, tabs: &mut Tabs,
) { ) {
let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_height = behavior.tab_bar_height(ui.style());
let tab_bar_rect = { let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0;
let mut r = rect;
r.max.y = r.min.y + tab_bar_height;
r
};
let mut tab_bar_ui = ui.child_ui(tab_bar_rect, *ui.layout()); let mut tab_bar_ui = ui.child_ui(tab_bar_rect, *ui.layout());
// Show tab bar: // Show tab bar:
@@ -516,134 +744,53 @@ impl<Leaf> Nodes<Leaf> {
let selected = child_id == tabs.active; let selected = child_id == tabs.active;
let id = child_id.id(); let id = child_id.id();
let is_node_being_dragged = ui.memory(|mem| mem.is_being_dragged(id)) // let is_node_being_dragged = ui.memory(|mem| mem.is_being_dragged(id))
&& !ui.input(|i| i.pointer.could_any_button_be_click()); // && is_possible_drag(ui.ctx());
if is_node_being_dragged { // if is_node_being_dragged {
continue; // leave a gap! // continue; // leave a gap!
} // }
let response = behavior.tab_ui(self, ui, id, child_id, selected); let response = behavior.tab_ui(self, ui, id, child_id, selected);
let response = response.on_hover_cursor(CursorIcon::Grab); let response = response.on_hover_cursor(CursorIcon::Grab);
if response.clicked() || response.drag_started() { if response.clicked() {
tabs.active = child_id; tabs.active = child_id;
} }
if let Some(mouse_pos) = drop_context.mouse_pos {
if drop_context.dragged_node_id.is_some() && response.rect.contains(mouse_pos) {
// Expand this tab - maybe the user wants to drop something into it!
tabs.active = child_id;
}
}
} }
}); });
self.node_ui(behavior, ui, tabs.active); self.node_ui(behavior, drop_context, ui, tabs.active);
} }
fn horizontal_ui( fn horizontal_ui(
&mut self, &mut self,
behavior: &mut dyn Behavior<Leaf>, behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui, ui: &mut Ui,
_rect: Rect, parent_id: NodeId,
horizontal: &mut Horizontal, horizontal: &mut Horizontal,
) { ) {
for child in &horizontal.children { for child in &horizontal.children {
self.node_ui(behavior, ui, *child); self.node_ui(behavior, drop_context, ui, *child);
}
}
}
// ----------------------------------------------------------------------------
// Dropping
struct DropContext {
dragged_node_id: NodeId,
mouse_pos: Pos2,
target_node_id: Option<NodeId>,
best_dist_sq: f32,
preview_rect: Option<Rect>,
}
impl DropContext {
fn suggest_point(&mut self, node_id: NodeId, target_point: Pos2, preview_rect: Rect) {
let dist_sq = self.mouse_pos.distance_sq(target_point);
if dist_sq < self.best_dist_sq {
self.best_dist_sq = dist_sq;
self.target_node_id = Some(node_id);
self.preview_rect = Some(preview_rect);
}
}
}
impl<Leaf> Nodes<Leaf> {
fn find_drop_target(&self, drop_context: &mut DropContext, node_id: NodeId) {
if drop_context.dragged_node_id == node_id {
// Can't drag a node onto self or any children
return;
}
let Some(node) = self.nodes.get(&node_id) else { return; };
drop_context.suggest_point(
node_id,
node.rect.left_center(),
node.rect.split_left_right_at_fraction(0.5).0,
);
drop_context.suggest_point(
node_id,
node.rect.right_center(),
node.rect.split_left_right_at_fraction(0.5).1,
);
drop_context.suggest_point(
node_id,
node.rect.center_top(),
node.rect.split_top_bottom_at_fraction(0.5).0,
);
drop_context.suggest_point(
node_id,
node.rect.center_bottom(),
node.rect.split_top_bottom_at_fraction(0.5).1,
);
drop_context.suggest_point(node_id, node.rect.center(), node.rect);
match &node.layout {
NodeLayout::Leaf(_) => {}
NodeLayout::Tabs(Tabs { active, .. }) => {
if let Some(active_node) = self.nodes.get(active) {
// Suggest dropping into tab bar
let tabs_rect = active_node
.rect
.split_top_bottom_at_y(active_node.rect.top())
.0;
if tabs_rect.contains(drop_context.mouse_pos) {
drop_context.suggest_point(node_id, drop_context.mouse_pos, tabs_rect)
}
}
self.find_drop_target(drop_context, *active);
}
NodeLayout::Horizontal(Horizontal { children, .. }) => {
for &child in children {
self.find_drop_target(drop_context, child);
}
}
} }
} }
// fn drop_node( fn vertical_ui(
// behavior: &mut dyn Behavior<Leaf>, &mut self,
// dropped_node_id: NodeId, behavior: &mut dyn Behavior<Leaf>,
// target_node_id: NodeId, drop_context: &mut DropContext,
// mouse_pos: Pos2, ui: &mut Ui,
// ) { parent_id: NodeId,
// // TODO vertical: &mut Vertical,
// } ) {
for child in &vertical.children {
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) { self.node_ui(behavior, drop_context, ui, *child);
let Some(mut node) = self.nodes.remove(&it) else { return; };
match &mut node.layout {
NodeLayout::Leaf(_) => {}
NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. }) => {
children.retain(|&child| {
self.remove_node_id_from_parent(child, remove);
child != remove
});
}
} }
self.nodes.insert(it, node);
} }
} }

View File

@@ -12,5 +12,5 @@ publish = false
eframe = { path = "../../crates/eframe", features = [ eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] } ] }
egui_extras = { path = "../../crates/egui_extras" } egui_extras = { path = "../../crates/egui_extras", features = ["log"] }
env_logger = "0.10" env_logger = "0.10"

View File

@@ -104,8 +104,13 @@ fn tree_ui(
tree_ui(ui, behavior, nodes, child); tree_ui(ui, behavior, nodes, child);
} }
} }
dock::NodeLayout::Horizontal(horizontal) => { dock::NodeLayout::Horizontal(layout) => {
for &child in &horizontal.children { for &child in &layout.children {
tree_ui(ui, behavior, nodes, child);
}
}
dock::NodeLayout::Vertical(layout) => {
for &child in &layout.children {
tree_ui(ui, behavior, nodes, child); tree_ui(ui, behavior, nodes, child);
} }
} }