1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -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),
Tabs(Tabs),
Horizontal(Horizontal),
// Vertical(Vertical)
// Grid(Grid)
Vertical(Vertical),
}
impl<Leaf> NodeLayout<Leaf> {
@@ -96,6 +95,7 @@ impl<Leaf> NodeLayout<Leaf> {
NodeLayout::Leaf(_) => "Leaf",
NodeLayout::Tabs(_) => "Tabs",
NodeLayout::Horizontal(_) => "Horizontal",
NodeLayout::Vertical(_) => "Vertical",
}
}
}
@@ -112,6 +112,12 @@ pub struct Horizontal {
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.
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Shares {
@@ -224,6 +230,10 @@ impl<Leaf> Dock<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]
pub fn insert_node(&mut self, node: NodeState<Leaf>) -> NodeId {
let id = NodeId::random();
@@ -254,8 +264,83 @@ impl<Leaf> Nodes<Leaf> {
self.insert_node(NodeLayout::Horizontal(horizontal).into())
}
pub fn get(&self, node_id: NodeId) -> Option<&NodeLayout<Leaf>> {
self.nodes.get(&node_id).map(|node| &node.layout)
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) {
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.nodes.node_ui(behavior, ui, self.root);
// Check if anything is being dragged:
let mouse_pos = ui.input(|i| i.pointer.hover_pos());
let dragged_id = self.dragged_id(ui.ctx());
if let (Some(mouse_pos), Some(dragged_node_id)) = (mouse_pos, dragged_id) {
let mut drop_context = DropContext {
active: true,
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:
egui::Area::new(Id::new((dragged_node_id, "preview")))
.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 {
ui.painter().rect_filled(
ui.painter().rect(
preview_rect,
1.0,
Color32::LIGHT_BLUE.gamma_multiply(0.5),
(1.0, Color32::LIGHT_BLUE),
);
}
// if ui.input(|i| i.pointer.any_released()) {
// ui.memory_mut(|mem| mem.stop_dragging());
// if let Some(target_node_id) = drop_context.target_node_id {
// self.remove_node_id_from_parent(dragged_node_id);
// // self.drop_node(behavior, target_node_id, dragged_node_id); // TODO
// }
// }
if ui.input(|i| i.pointer.any_released()) {
ui.memory_mut(|mem| mem.stop_dragging());
if let Some(insertion_point) = drop_context.best_insertion {
self.remove_node_id_from_parent(dragged_node_id);
self.nodes.insert(insertion_point, dragged_node_id);
}
}
}
}
/// Find the currently dragged node, if any.
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.
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
@@ -367,6 +458,12 @@ impl<Leaf> Nodes<Leaf> {
fn gc_root(&mut self, behavior: &mut dyn Behavior<Leaf>, root_id: NodeId) {
let mut visited = HashSet::default();
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));
}
@@ -379,7 +476,7 @@ impl<Leaf> Nodes<Leaf> {
let Some(mut node) = self.nodes.remove(&node_id) else { return GcAction::Remove; };
if !visited.insert(node_id) {
#[cfg(feature = "log")]
log::warn!("Cycle detected in egui_extras::dock");
log::warn!("Cycle or duplication detected");
return GcAction::Remove;
}
@@ -390,7 +487,8 @@ impl<Leaf> Nodes<Leaf> {
}
}
NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. }) => {
| NodeLayout::Horizontal(Horizontal { children, .. })
| NodeLayout::Vertical(Vertical { children, .. }) => {
children
.retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
}
@@ -422,6 +520,9 @@ impl<Leaf> Nodes<Leaf> {
NodeLayout::Horizontal(horizontal) => {
self.layout_horizontal(style, behavior, rect, horizontal);
}
NodeLayout::Vertical(vertical) => {
self.layout_vertical(style, behavior, rect, vertical);
}
}
self.nodes.insert(node_id, node);
@@ -452,62 +553,189 @@ impl<Leaf> Nodes<Leaf> {
style: &Style,
behavior: &mut dyn Behavior<Leaf>,
rect: Rect,
horizontal: &Horizontal,
layout: &Horizontal,
) {
if horizontal.children.is_empty() {
if layout.children.is_empty() {
return;
}
let num_gaps = horizontal.children.len() - 1;
let num_gaps = layout.children.len() - 1;
let gap_width = behavior.gap_width(style);
let total_gap_width = gap_width * num_gaps as f32;
let available_width = (rect.width() - total_gap_width).at_least(0.0);
let widths = horizontal
.shares
.split(&horizontal.children, available_width);
let widths = layout.shares.split(&layout.children, available_width);
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()));
self.layout_node(style, behavior, child_rect, *child);
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
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> {
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 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 {
NodeLayout::Leaf(leaf) => {
let mut leaf_ui = ui.child_ui(node.rect, *ui.layout());
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) => {
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);
drop_context.active = drop_context_was_active;
}
fn tabs_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
rect: Rect,
parent_id: NodeId,
tabs: &mut Tabs,
) {
let tab_bar_height = behavior.tab_bar_height(ui.style());
let tab_bar_rect = {
let mut r = rect;
r.max.y = r.min.y + tab_bar_height;
r
};
let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0;
let mut tab_bar_ui = ui.child_ui(tab_bar_rect, *ui.layout());
// Show tab bar:
@@ -516,134 +744,53 @@ impl<Leaf> Nodes<Leaf> {
let selected = child_id == tabs.active;
let id = child_id.id();
let is_node_being_dragged = ui.memory(|mem| mem.is_being_dragged(id))
&& !ui.input(|i| i.pointer.could_any_button_be_click());
if is_node_being_dragged {
continue; // leave a gap!
}
// let is_node_being_dragged = ui.memory(|mem| mem.is_being_dragged(id))
// && is_possible_drag(ui.ctx());
// if is_node_being_dragged {
// continue; // leave a gap!
// }
let response = behavior.tab_ui(self, ui, id, child_id, selected);
let response = response.on_hover_cursor(CursorIcon::Grab);
if response.clicked() || response.drag_started() {
if response.clicked() {
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(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
_rect: Rect,
parent_id: NodeId,
horizontal: &mut Horizontal,
) {
for child in &horizontal.children {
self.node_ui(behavior, 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);
}
}
self.node_ui(behavior, drop_context, ui, *child);
}
}
// fn drop_node(
// behavior: &mut dyn Behavior<Leaf>,
// dropped_node_id: NodeId,
// target_node_id: NodeId,
// mouse_pos: Pos2,
// ) {
// // TODO
// }
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) {
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
});
}
fn vertical_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
parent_id: NodeId,
vertical: &mut Vertical,
) {
for child in &vertical.children {
self.node_ui(behavior, drop_context, ui, *child);
}
self.nodes.insert(it, node);
}
}

View File

@@ -12,5 +12,5 @@ publish = false
eframe = { path = "../../crates/eframe", features = [
"__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"

View File

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