From 65635229391fc43d5706d1939c974e69ecc6bb60 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 25 Apr 2023 21:51:49 +0200 Subject: [PATCH] Better re-ordering of grid cells --- crates/egui_extras/src/dock/mod.rs | 211 +++++++++++++++++------------ examples/dock/src/main.rs | 5 +- 2 files changed, 126 insertions(+), 90 deletions(-) diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index 22b80b689..411b21622 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -191,22 +191,27 @@ impl Layout { pub const ALL: [Self; 4] = [Self::Tabs, Self::Horizontal, Self::Vertical, Self::Grid]; } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LayoutInsertion { + Tabs(usize), + Horizontal(usize), + Vertical(usize), + Grid(GridLoc), +} + #[derive(Clone, Copy, Debug)] struct InsertionPoint { parent_id: NodeId, - layout: Layout, - /// Where in the parent? - index: usize, + insertion: LayoutInsertion, } impl InsertionPoint { - fn new(parent_id: NodeId, layout: Layout, index: usize) -> Self { + fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self { Self { parent_id, - layout, - index, + insertion, } } } @@ -394,15 +399,14 @@ impl Nodes { fn insert(&mut self, insertion_point: InsertionPoint, child_id: NodeId) { let InsertionPoint { parent_id, - layout: branch_type, - index, + insertion, } = insertion_point; let Some(mut node) = self.nodes.remove(&parent_id) else { log::warn!("Failed to insert: could not find parent {parent_id:?}"); return; }; - match branch_type { - Layout::Tabs => { + match insertion { + LayoutInsertion::Tabs(index) => { if let Node::Branch(Branch { layout: Layout::Tabs, children, @@ -419,7 +423,7 @@ impl Nodes { self.nodes.insert(parent_id, Node::Branch(branch)); } } - Layout::Horizontal => { + LayoutInsertion::Horizontal(index) => { if let Node::Branch(Branch { layout: Layout::Horizontal, children, @@ -436,7 +440,7 @@ impl Nodes { self.nodes.insert(parent_id, Node::Branch(branch)); } } - Layout::Vertical => { + LayoutInsertion::Vertical(index) => { if let Node::Branch(Branch { layout: Layout::Vertical, children, @@ -453,27 +457,22 @@ impl Nodes { self.nodes.insert(parent_id, Node::Branch(branch)); } } - Layout::Grid => { + LayoutInsertion::Grid(insert_location) => { if let Node::Branch(Branch { layout: Layout::Grid, + grid_locations, children, - grid_locations: grid_positions, .. }) = &mut node { - // The order of the children only matters with auto-layout - // Swap the positions of the children - let index = index.min(children.len()); - let replaced_node_id = children.get(index).copied().unwrap_or_default(); - children.insert(index, child_id); - if let Some(pos) = grid_positions.remove(&replaced_node_id) { - grid_positions.insert(child_id, pos); - } + grid_locations.retain(|_, pos| *pos != insert_location); + grid_locations.insert(child_id, insert_location); + children.push(child_id); self.nodes.insert(parent_id, node); } else { let new_node_id = self.insert_node(node); - let mut branch = Branch::new(Layout::Grid, vec![new_node_id]); - branch.children.insert(index.min(1), child_id); + let mut branch = Branch::new(Layout::Grid, vec![new_node_id, child_id]); + branch.grid_locations.insert(child_id, insert_location); self.nodes.insert(parent_id, Node::Branch(branch)); } } @@ -487,22 +486,7 @@ impl Dock { self.root } - fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect { - let dt = ctx.input(|input| input.stable_dt).at_most(0.1); - let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt); - - let smoothed = self.smoothed_preview_rect.get_or_insert(new_rect); - *smoothed = smoothed.lerp_towards(&new_rect, t); - - let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max); - if diff < 0.5 { - *smoothed = new_rect; - } else { - ctx.request_repaint(); - } - *smoothed - } - + /// Show all the leaves in the dock. pub fn ui(&mut self, behavior: &mut dyn Behavior, ui: &mut Ui) { let options = behavior.simplification_options(); self.simplify(&options); @@ -513,12 +497,6 @@ impl Dock { self.nodes.gc_root(behavior, self.root); self.nodes.rects.clear(); - self.nodes.layout_node( - ui.style(), - behavior, - ui.available_rect_before_wrap(), - self.root, - ); // Check if anything is being dragged: let mut drop_context = DropContext { @@ -530,6 +508,14 @@ impl Dock { preview_rect: None, }; + self.nodes.layout_node( + ui.style(), + behavior, + &mut drop_context, + ui.available_rect_before_wrap(), + self.root, + ); + self.nodes .node_ui(behavior, &mut drop_context, ui, self.root); @@ -600,6 +586,22 @@ impl Dock { } } + fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect { + let dt = ctx.input(|input| input.stable_dt).at_most(0.1); + let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt); + + let smoothed = self.smoothed_preview_rect.get_or_insert(new_rect); + *smoothed = smoothed.lerp_towards(&new_rect, t); + + let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max); + if diff < 0.5 { + *smoothed = new_rect; + } else { + ctx.request_repaint(); + } + *smoothed + } + fn simplify(&mut self, options: &SimplificationOptions) { match self.nodes.simplify(options, self.root) { SimplifyAction::Remove => { @@ -614,9 +616,8 @@ impl Dock { fn move_node(&mut self, moved_node_id: NodeId, insertion_point: InsertionPoint) { log::debug!( - "Moving {moved_node_id:?} into {:?} {:?}", - insertion_point.layout, - insertion_point.parent_id + "Moving {moved_node_id:?} into {:?}", + insertion_point.insertion ); self.remove_node_id_from_parent(moved_node_id); self.nodes.insert(insertion_point, moved_node_id); @@ -731,17 +732,15 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, node_id: NodeId, ) { let Some(mut node) = self.nodes.remove(&node_id) else { return; }; self.rects.insert(node_id, rect); - match &mut node { - Node::Leaf(_) => {} - Node::Branch(branch) => { - self.layout_branch(style, behavior, rect, branch); - } + if let Node::Branch(branch) = &mut node { + self.layout_branch(style, behavior, drop_context, rect, node_id, branch); } self.nodes.insert(node_id, node); @@ -751,14 +750,18 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, + node_id: NodeId, branch: &mut Branch, ) { match branch.layout { - Layout::Tabs => self.layout_tabs(style, behavior, rect, branch), - Layout::Horizontal => self.layout_horizontal(style, behavior, rect, branch), - Layout::Vertical => self.layout_vertical(style, behavior, rect, branch), - Layout::Grid => self.layout_grid(style, behavior, rect, branch), + Layout::Tabs => self.layout_tabs(style, behavior, drop_context, rect, branch), + Layout::Horizontal => { + self.layout_horizontal(style, behavior, drop_context, rect, branch); + } + Layout::Vertical => self.layout_vertical(style, behavior, drop_context, rect, branch), + Layout::Grid => self.layout_grid(style, behavior, drop_context, rect, node_id, branch), } } @@ -766,6 +769,7 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, branch: &Branch, ) { @@ -773,11 +777,17 @@ impl Nodes { active_rect.min.y += behavior.tab_bar_height(style); if false { - self.layout_node(style, behavior, active_rect, branch.active_tab); + self.layout_node( + style, + behavior, + drop_context, + active_rect, + branch.active_tab, + ); } else { // Layout all nodes in case the user switches active tab for &child_id in &branch.children { - self.layout_node(style, behavior, active_rect, child_id); + self.layout_node(style, behavior, drop_context, active_rect, child_id); } } } @@ -786,6 +796,7 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, branch: &Branch, ) { @@ -804,7 +815,7 @@ impl Nodes { let mut x = rect.min.x; for (child, width) in branch.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); + self.layout_node(style, behavior, drop_context, child_rect, *child); x += width + gap_width; } } @@ -813,6 +824,7 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, branch: &Branch, ) { @@ -831,7 +843,7 @@ impl Nodes { let mut y = rect.min.y; for (child, height) in branch.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); + self.layout_node(style, behavior, drop_context, child_rect, *child); y += height + gap_height; } } @@ -840,7 +852,9 @@ impl Nodes { &mut self, style: &Style, behavior: &mut dyn Behavior, + drop_context: &mut DropContext, rect: Rect, + node_id: NodeId, branch: &mut Branch, ) { if branch.children.is_empty() { @@ -911,14 +925,36 @@ impl Nodes { row_y.push(row_y.last().unwrap() + height + gap); } - // Place each node: - for child in &branch.children { - let loc = branch.grid_locations[child]; + // Each child now has a location. Use this to order them, in case we will ater do auto-layouts: + branch + .children + .sort_by_key(|&child| branch.grid_locations[&child]); + + // Place each child: + for &child in &branch.children { + let loc = branch.grid_locations[&child]; let child_rect = Rect::from_min_size( pos2(col_x[loc.col], row_y[loc.row]), vec2(col_widths[loc.col], row_heights[loc.row]), ); - self.layout_node(style, behavior, child_rect, *child); + self.layout_node(style, behavior, drop_context, child_rect, child); + } + + // Register drop-zones: + for col in 0..num_cols { + for row in 0..num_rows { + let cell_rect = Rect::from_min_size( + pos2(col_x[col], row_y[row]), + vec2(col_widths[col], row_heights[row]), + ); + drop_context.suggest_rect( + InsertionPoint::new( + node_id, + LayoutInsertion::Grid(GridLoc::from_col_row(col, row)), + ), + cell_rect, + ); + } } } } @@ -960,22 +996,22 @@ impl DropContext { if node.layout() != Some(Layout::Horizontal) { self.suggest_rect( - InsertionPoint::new(parent_id, Layout::Horizontal, 0), + InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(0)), rect.split_left_right_at_fraction(0.5).0, ); self.suggest_rect( - InsertionPoint::new(parent_id, Layout::Horizontal, usize::MAX), + InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(usize::MAX)), rect.split_left_right_at_fraction(0.5).1, ); } if node.layout() != Some(Layout::Vertical) { self.suggest_rect( - InsertionPoint::new(parent_id, Layout::Vertical, 0), + InsertionPoint::new(parent_id, LayoutInsertion::Vertical(0)), rect.split_top_bottom_at_fraction(0.5).0, ); self.suggest_rect( - InsertionPoint::new(parent_id, Layout::Vertical, usize::MAX), + InsertionPoint::new(parent_id, LayoutInsertion::Vertical(usize::MAX)), rect.split_top_bottom_at_fraction(0.5).1, ); } @@ -1020,7 +1056,7 @@ impl Nodes { drop_context.on_node(node_id, rect, &node); drop_context.suggest_rect( - InsertionPoint::new(node_id, Layout::Tabs, usize::MAX), + InsertionPoint::new(node_id, LayoutInsertion::Tabs(usize::MAX)), rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(ui.style())) .1, ); @@ -1061,7 +1097,7 @@ impl Nodes { self.vertical_ui(behavior, drop_context, ui, node_id, branch); } Layout::Grid => { - self.grid_ui(behavior, drop_context, ui, node_id, branch); + self.grid_ui(behavior, drop_context, ui, branch); } } } @@ -1127,7 +1163,7 @@ impl Nodes { }; drop_context.suggest_rect( - InsertionPoint::new(node_id, Layout::Tabs, insertion_index), + InsertionPoint::new(node_id, LayoutInsertion::Tabs(insertion_index)), Rect::from_center_size(before_point, vec2(4.0, rect.height())), ); } @@ -1135,7 +1171,7 @@ impl Nodes { if i + 1 == branch.children.len() { // suggest dropping after last tab: drop_context.suggest_rect( - InsertionPoint::new(node_id, Layout::Tabs, insertion_index + 1), + InsertionPoint::new(node_id, LayoutInsertion::Tabs(insertion_index + 1)), Rect::from_center_size(rect.right_center(), vec2(4.0, rect.height())), ); } @@ -1169,21 +1205,26 @@ impl Nodes { if is_being_dragged(ui.ctx(), child) { // Leave a hole, and suggest that hole as drop-target: - drop_context - .suggest_rect(InsertionPoint::new(parent_id, Layout::Horizontal, i), rect); + drop_context.suggest_rect( + InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(i)), + rect, + ); } else { self.node_ui(behavior, drop_context, ui, child); if let Some(prev_rect) = prev_rect { // Suggest dropping between the rects: drop_context.suggest_rect( - InsertionPoint::new(parent_id, Layout::Horizontal, insertion_index), + InsertionPoint::new( + parent_id, + LayoutInsertion::Horizontal(insertion_index), + ), Rect::from_min_max(prev_rect.center_top(), rect.center_bottom()), ); } else { // Suggest dropping before the first child: drop_context.suggest_rect( - InsertionPoint::new(parent_id, Layout::Horizontal, 0), + InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(0)), rect.split_left_right_at_fraction(0.66).0, ); } @@ -1191,7 +1232,10 @@ impl Nodes { if i + 1 == branch.children.len() { // Suggest dropping after the last child: drop_context.suggest_rect( - InsertionPoint::new(parent_id, Layout::Horizontal, insertion_index + 1), + InsertionPoint::new( + parent_id, + LayoutInsertion::Horizontal(insertion_index + 1), + ), rect.split_left_right_at_fraction(0.33).1, ); } @@ -1221,16 +1265,11 @@ impl Nodes { behavior: &mut dyn Behavior, drop_context: &mut DropContext, ui: &mut Ui, - parent_id: NodeId, branch: &mut Branch, ) { - // TODO: drag-and-drop - for (i, &child) in branch.children.iter().enumerate() { - drop_context.suggest_rect( - InsertionPoint::new(parent_id, Layout::Grid, i), - self.rect(child).unwrap(), - ); + // Grid drops are handled during layout + for &child in &branch.children { self.node_ui(behavior, drop_context, ui, child); } } diff --git a/examples/dock/src/main.rs b/examples/dock/src/main.rs index 87de704b2..45dfa1df2 100644 --- a/examples/dock/src/main.rs +++ b/examples/dock/src/main.rs @@ -43,10 +43,7 @@ impl View { pub fn ui(&mut self, ui: &mut egui::Ui) -> dock::UiResponse { ui.painter().rect_filled(ui.max_rect(), 0.0, self.color); let dragged = ui - .add( - egui::Button::new(format!("Contents of {}. Drag me!", self.title)) - .sense(egui::Sense::drag()), - ) + .allocate_rect(ui.max_rect(), egui::Sense::drag()) .on_hover_cursor(egui::CursorIcon::Grab) .dragged(); if dragged {