From e9928c8766ecae59cac59edaa05576dbcf7b9fb3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 26 Apr 2023 20:31:37 +0200 Subject: [PATCH] Turn Branch into an enum --- crates/egui_extras/src/dock/branch.rs | 304 ++++++++++++++++++++------ crates/egui_extras/src/dock/mod.rs | 132 +++++------ examples/dock/src/main.rs | 10 +- 3 files changed, 296 insertions(+), 150 deletions(-) diff --git a/crates/egui_extras/src/dock/branch.rs b/crates/egui_extras/src/dock/branch.rs index 10d41304a..6129f3554 100644 --- a/crates/egui_extras/src/dock/branch.rs +++ b/crates/egui_extras/src/dock/branch.rs @@ -4,7 +4,7 @@ use egui::{pos2, vec2, NumExt as _, Rect}; use super::{ is_being_dragged, is_possible_drag, sizes_from_shares, Behavior, DropContext, GridLoc, - InsertionPoint, LayoutInsertion, NodeId, Nodes, + InsertionPoint, LayoutInsertion, NodeId, Nodes, SimplifyAction, }; // ---------------------------------------------------------------------------- @@ -60,40 +60,168 @@ impl Shares { // ---------------------------------------------------------------------------- -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Branch { - pub layout: Layout, - pub children: Vec, - - /// Only if [`Self.typ`] == [`Layout::Tab`] - pub active_tab: NodeId, - - /// Only for linear layouts (horizontal or vertical). - pub linear_shares: Shares, - - /// Only if [`Self.typ`] == [`Layout::Grid`]. - /// col,row - pub grid_locations: HashMap, - - /// Share of the avilable width assigned to each column. - pub grid_col_shares: Vec, - /// Share of the avilable height assigned to each row. - pub grid_row_shares: Vec, +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum Branch { + Tabs(Tabs), + Linear(Linear), + Grid(Grid), } impl Branch { - pub fn new(layout: Layout, children: Vec) -> Self { - let active_tab = children.first().copied().unwrap_or_default(); - Self { - layout, - children, - active_tab, - ..Default::default() - } + pub fn new_linear(dir: LinearDir, children: Vec) -> Self { + Self::Linear(Linear::new(dir, children)) } pub fn new_tabs(children: Vec) -> Self { - Self::new(Layout::Tabs, children) + Self::Tabs(Tabs::new(children)) + } + + pub fn new_grid(children: Vec) -> Self { + Self::Grid(Grid::new(children)) + } + + pub fn is_empty(&self) -> bool { + self.children().is_empty() + } + + pub fn children(&self) -> &[NodeId] { + match self { + Self::Tabs(tabs) => &tabs.children, + Self::Linear(linear) => &linear.children, + Self::Grid(grid) => &grid.children, + } + } + + pub fn get_layout(&self) -> Layout { + match self { + Self::Tabs(_) => Layout::Tabs, + Self::Linear(linear) => match linear.dir { + LinearDir::Horizontal => Layout::Horizontal, + LinearDir::Vertical => Layout::Vertical, + }, + Self::Grid(_) => Layout::Grid, + } + } + + pub fn set_layout(&mut self, layout: Layout) { + if layout == self.get_layout() { + return; + } + + *self = match layout { + Layout::Tabs => Self::Tabs(Tabs::new(self.children().to_vec())), + Layout::Horizontal => { + Self::Linear(Linear::new(LinearDir::Horizontal, self.children().to_vec())) + } + Layout::Vertical => { + Self::Linear(Linear::new(LinearDir::Vertical, self.children().to_vec())) + } + Layout::Grid => Self::Grid(Grid::new(self.children().to_vec())), + }; + } + + pub(super) fn retain(&mut self, mut retain: impl FnMut(NodeId) -> bool) { + let retain = |node_id: &NodeId| retain(*node_id); + match self { + Self::Tabs(tabs) => tabs.children.retain(retain), + Self::Linear(linear) => linear.children.retain(retain), + Self::Grid(grid) => grid.children.retain(retain), + } + } + + pub(super) fn simplify_children(&mut self, mut simplify: impl FnMut(NodeId) -> SimplifyAction) { + match self { + Self::Tabs(tabs) => tabs.children.retain_mut(|child| match simplify(*child) { + SimplifyAction::Remove => false, + SimplifyAction::Keep => true, + SimplifyAction::Replace(new) => { + if tabs.active == *child { + tabs.active = new; + } + *child = new; + true + } + }), + Self::Linear(linear) => linear.children.retain_mut(|child| match simplify(*child) { + SimplifyAction::Remove => false, + SimplifyAction::Keep => true, + SimplifyAction::Replace(new) => { + linear.shares.replace_with(*child, new); + *child = new; + true + } + }), + Self::Grid(grid) => grid.children.retain_mut(|child| match simplify(*child) { + SimplifyAction::Remove => false, + SimplifyAction::Keep => true, + SimplifyAction::Replace(new) => { + if let Some(loc) = grid.locations.remove(child) { + grid.locations.insert(new, loc); + } + *child = new; + true + } + }), + } + } +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct Tabs { + pub children: Vec, + pub active: NodeId, +} + +impl Tabs { + pub fn new(children: Vec) -> Self { + let active = children.first().copied().unwrap_or_default(); + Self { children, active } + } +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum LinearDir { + #[default] + Horizontal, + Vertical, +} + +/// Horizontal or vertical layout. +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct Linear { + pub children: Vec, + pub dir: LinearDir, + pub shares: Shares, +} + +impl Linear { + pub fn new(dir: LinearDir, children: Vec) -> Self { + Self { + children, + dir, + ..Default::default() + } + } +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct Grid { + pub children: Vec, + + pub locations: HashMap, + + /// Share of the avilable width assigned to each column. + pub col_shares: Vec, + /// Share of the avilable height assigned to each row. + pub row_shares: Vec, +} + +impl Grid { + pub fn new(children: Vec) -> Self { + Self { + children, + ..Default::default() + } } } @@ -110,21 +238,22 @@ impl Branch { rect: Rect, node_id: NodeId, ) { - if self.children.is_empty() { + if self.is_empty() { return; } - match self.layout { - Layout::Tabs => self.layout_tabs(nodes, style, behavior, drop_context, rect), - Layout::Horizontal => { - self.layout_horizontal(nodes, style, behavior, drop_context, rect); + match self { + Branch::Tabs(tabs) => tabs.layout(nodes, style, behavior, drop_context, rect), + Branch::Linear(linear) => { + linear.layout(nodes, style, behavior, drop_context, rect); } - Layout::Vertical => self.layout_vertical(nodes, style, behavior, drop_context, rect), - Layout::Grid => self.layout_grid(nodes, style, behavior, drop_context, rect, node_id), + Branch::Grid(grid) => grid.layout(nodes, style, behavior, drop_context, rect, node_id), } } +} - fn layout_tabs( +impl Tabs { + fn layout( &self, nodes: &mut Nodes, style: &egui::Style, @@ -136,7 +265,7 @@ impl Branch { active_rect.min.y += behavior.tab_bar_height(style); if false { - nodes.layout_node(style, behavior, drop_context, active_rect, self.active_tab); + nodes.layout_node(style, behavior, drop_context, active_rect, self.active); } else { // Layout all nodes in case the user switches active tab // TODO: only layout active tab, or don't register drop-zones during layout. @@ -145,6 +274,24 @@ impl Branch { } } } +} + +impl Linear { + fn layout( + &mut self, + nodes: &mut Nodes, + style: &egui::Style, + behavior: &mut dyn Behavior, + drop_context: &mut DropContext, + rect: Rect, + ) { + match self.dir { + LinearDir::Horizontal => { + self.layout_horizontal(nodes, style, behavior, drop_context, rect); + } + LinearDir::Vertical => self.layout_vertical(nodes, style, behavior, drop_context, rect), + } + } fn layout_horizontal( &mut self, @@ -159,7 +306,7 @@ impl Branch { let total_gap_width = gap_width * num_gaps as f32; let available_width = (rect.width() - total_gap_width).at_least(0.0); - let widths = self.linear_shares.split(&self.children, available_width); + let widths = self.shares.split(&self.children, available_width); let mut x = rect.min.x; for (child, width) in self.children.iter().zip(widths) { @@ -182,7 +329,7 @@ impl Branch { let total_gap_height = gap_height * num_gaps as f32; let available_height = (rect.height() - total_gap_height).at_least(0.0); - let heights = self.linear_shares.split(&self.children, available_height); + let heights = self.shares.split(&self.children, available_height); let mut y = rect.min.y; for (child, height) in self.children.iter().zip(heights) { @@ -191,8 +338,10 @@ impl Branch { y += height + gap_height; } } +} - fn layout_grid( +impl Grid { + fn layout( &mut self, nodes: &mut Nodes, style: &egui::Style, @@ -207,7 +356,7 @@ impl Branch { // Where to place each node? let mut node_id_from_location: BTreeMap = Default::default(); - self.grid_locations.retain(|&child_id, &mut loc| { + self.locations.retain(|&child_id, &mut loc| { if child_ids.contains(&child_id) { match node_id_from_location.entry(loc) { std::collections::btree_map::Entry::Occupied(_) => { @@ -227,8 +376,7 @@ impl Branch { // Find location for nodes that don't have one yet let mut next_pos = 0; for &child_id in &self.children { - if let std::collections::hash_map::Entry::Vacant(entry) = - self.grid_locations.entry(child_id) + if let std::collections::hash_map::Entry::Vacant(entry) = self.locations.entry(child_id) { // find a position: loop { @@ -248,12 +396,12 @@ impl Branch { let num_rows = node_id_from_location.keys().last().unwrap().row + 1; // Figure out where each column and row goes: - self.grid_col_shares.resize(num_cols, 1.0); - self.grid_row_shares.resize(num_rows, 1.0); + self.col_shares.resize(num_cols, 1.0); + self.row_shares.resize(num_rows, 1.0); let gap = behavior.gap_width(style); - let col_widths = sizes_from_shares(&self.grid_col_shares, rect.width(), gap); - let row_heights = sizes_from_shares(&self.grid_row_shares, rect.height(), gap); + let col_widths = sizes_from_shares(&self.col_shares, rect.width(), gap); + let row_heights = sizes_from_shares(&self.row_shares, rect.height(), gap); let mut col_x = vec![rect.left()]; for &width in &col_widths { @@ -266,12 +414,11 @@ impl Branch { } // Each child now has a location. Use this to order them, in case we will ater do auto-layouts: - self.children - .sort_by_key(|&child| self.grid_locations[&child]); + self.children.sort_by_key(|&child| self.locations[&child]); // Place each child: for &child in &self.children { - let loc = self.grid_locations[&child]; + let loc = self.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]), @@ -310,23 +457,22 @@ impl Branch { rect: Rect, node_id: NodeId, ) { - match self.layout { - Layout::Tabs => { - self.tabs_ui(nodes, behavior, drop_context, ui, rect, node_id); + match self { + Branch::Tabs(tabs) => { + tabs.ui(nodes, behavior, drop_context, ui, rect, node_id); } - Layout::Horizontal => { - self.horizontal_ui(nodes, behavior, drop_context, ui, node_id); + Branch::Linear(linear) => { + linear.ui(nodes, behavior, drop_context, ui, node_id); } - Layout::Vertical => { - self.vertical_ui(nodes, behavior, drop_context, ui, node_id); - } - Layout::Grid => { - self.grid_ui(nodes, behavior, drop_context, ui); + Branch::Grid(grid) => { + grid.grid_ui(nodes, behavior, drop_context, ui); } } } +} - fn tabs_ui( +impl Tabs { + fn ui( &mut self, nodes: &mut Nodes, behavior: &mut dyn Behavior, @@ -335,9 +481,9 @@ impl Branch { rect: Rect, node_id: NodeId, ) { - if !self.children.iter().any(|&child| child == self.active_tab) { + if !self.children.iter().any(|&child| child == self.active) { // Make sure something is active: - self.active_tab = self.children.first().copied().unwrap_or_default(); + self.active = self.children.first().copied().unwrap_or_default(); } let tab_bar_height = behavior.tab_bar_height(ui.style()); @@ -354,19 +500,19 @@ impl Branch { continue; // leave a gap! } - let selected = child_id == self.active_tab; + let selected = child_id == self.active; let id = child_id.id(); let response = behavior.tab_ui(nodes, ui, id, child_id, selected); let response = response.on_hover_cursor(egui::CursorIcon::Grab); if response.clicked() { - self.active_tab = child_id; + self.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! - self.active_tab = child_id; + self.active = child_id; } } @@ -402,10 +548,26 @@ impl Branch { }); // When dragged, don't show it (it is "being held") - let is_active_being_dragged = ui.memory(|mem| mem.is_being_dragged(self.active_tab.id())) - && is_possible_drag(ui.ctx()); + let is_active_being_dragged = + ui.memory(|mem| mem.is_being_dragged(self.active.id())) && is_possible_drag(ui.ctx()); if !is_active_being_dragged { - nodes.node_ui(behavior, drop_context, ui, self.active_tab); + nodes.node_ui(behavior, drop_context, ui, self.active); + } + } +} + +impl Linear { + fn ui( + &mut self, + nodes: &mut Nodes, + behavior: &mut dyn Behavior, + drop_context: &mut DropContext, + ui: &mut egui::Ui, + node_id: NodeId, + ) { + match self.dir { + LinearDir::Horizontal => self.horizontal_ui(nodes, behavior, drop_context, ui, node_id), + LinearDir::Vertical => self.vertical_ui(nodes, behavior, drop_context, ui, node_id), } } @@ -479,7 +641,9 @@ impl Branch { nodes.node_ui(behavior, drop_context, ui, *child); } } +} +impl Grid { fn grid_ui( &mut self, nodes: &mut Nodes, diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index a7917d7a2..e8bf57e1a 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -4,7 +4,7 @@ use egui::{Id, Key, NumExt, Pos2, Rect, Response, Sense, TextStyle, Ui, WidgetTe mod branch; -pub use branch::{Branch, Layout}; +pub use branch::{Branch, Grid, Layout, Linear, LinearDir, Tabs}; // ---------------------------------------------------------------------------- // Types required for state @@ -83,7 +83,7 @@ impl Node { fn layout(&self) -> Option { match self { Node::Leaf(_) => None, - Node::Branch(branch) => Some(branch.layout), + Node::Branch(branch) => Some(branch.get_layout()), } } } @@ -183,7 +183,7 @@ pub trait Behavior { fn tab_text_for_node(&mut self, nodes: &Nodes, node_id: NodeId) -> WidgetText { match &nodes.nodes[&node_id] { Node::Leaf(leaf) => self.tab_text_for_leaf(leaf), - Node::Branch(branch) => format!("{:?}", branch.layout).into(), + Node::Branch(branch) => format!("{:?}", branch.get_layout()).into(), } } @@ -279,24 +279,30 @@ impl Nodes { #[must_use] pub fn insert_horizontal_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new(Layout::Horizontal, children))) + self.insert_node(Node::Branch(Branch::new_linear( + LinearDir::Horizontal, + children, + ))) } #[must_use] pub fn insert_vertical_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new(Layout::Vertical, children))) + self.insert_node(Node::Branch(Branch::new_linear( + LinearDir::Vertical, + children, + ))) } #[must_use] pub fn insert_grid_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new(Layout::Grid, children))) + self.insert_node(Node::Branch(Branch::new_grid(children))) } fn parent(&self, it: NodeId, needle_child: NodeId) -> Option { match &self.nodes.get(&it)? { Node::Leaf(_) => None, Node::Branch(branch) => { - for &child in &branch.children { + for &child in branch.children() { if child == needle_child { return Some(it); } @@ -316,9 +322,7 @@ impl Nodes { } let Some(mut node) = self.nodes.remove(&it) else { return GcAction::Remove; }; if let Node::Branch(branch) = &mut node { - branch - .children - .retain(|&child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep); + branch.retain(|child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep); } self.nodes.insert(it, node); GcAction::Keep @@ -337,73 +341,66 @@ impl Nodes { match insertion { LayoutInsertion::Tabs(index) => { - if let Node::Branch(Branch { - layout: Layout::Tabs, - children, - .. - }) = &mut node - { - let index = index.min(children.len()); - children.insert(index, child_id); + if let Node::Branch(Branch::Tabs(tabs)) = &mut node { + let index = index.min(tabs.children.len()); + tabs.children.insert(index, child_id); self.nodes.insert(parent_id, node); } else { let new_node_id = self.insert_node(node); - let mut branch = Branch::new_tabs(vec![new_node_id]); - branch.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Branch(branch)); + let mut tabs = Tabs::new(vec![new_node_id]); + tabs.children.insert(index.min(1), child_id); + self.nodes + .insert(parent_id, Node::Branch(Branch::Tabs(tabs))); } } LayoutInsertion::Horizontal(index) => { - if let Node::Branch(Branch { - layout: Layout::Horizontal, + if let Node::Branch(Branch::Linear(Linear { + dir: LinearDir::Horizontal, children, .. - }) = &mut node + })) = &mut node { let index = index.min(children.len()); children.insert(index, child_id); self.nodes.insert(parent_id, node); } else { let new_node_id = self.insert_node(node); - let mut branch = Branch::new(Layout::Horizontal, vec![new_node_id]); - branch.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Branch(branch)); + let mut linear = Linear::new(LinearDir::Horizontal, vec![new_node_id]); + linear.children.insert(index.min(1), child_id); + self.nodes + .insert(parent_id, Node::Branch(Branch::Linear(linear))); } } LayoutInsertion::Vertical(index) => { - if let Node::Branch(Branch { - layout: Layout::Vertical, + if let Node::Branch(Branch::Linear(Linear { + dir: LinearDir::Vertical, children, .. - }) = &mut node + })) = &mut node { let index = index.min(children.len()); children.insert(index, child_id); self.nodes.insert(parent_id, node); } else { let new_node_id = self.insert_node(node); - let mut branch = Branch::new(Layout::Vertical, vec![new_node_id]); - branch.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Branch(branch)); + let mut linear = Linear::new(LinearDir::Vertical, vec![new_node_id]); + linear.children.insert(index.min(1), child_id); + self.nodes + .insert(parent_id, Node::Branch(Branch::Linear(linear))); } } LayoutInsertion::Grid(insert_location) => { - if let Node::Branch(Branch { - layout: Layout::Grid, - grid_locations, - children, - .. - }) = &mut node - { - grid_locations.retain(|_, pos| *pos != insert_location); - grid_locations.insert(child_id, insert_location); - children.push(child_id); + if let Node::Branch(Branch::Grid(grid)) = &mut node { + grid.locations.retain(|_, pos| *pos != insert_location); + grid.locations.insert(child_id, insert_location); + grid.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, child_id]); - branch.grid_locations.insert(child_id, insert_location); - self.nodes.insert(parent_id, Node::Branch(branch)); + let mut grid = Grid::new(vec![new_node_id, child_id]); + grid.locations.insert(child_id, insert_location); + self.nodes + .insert(parent_id, Node::Branch(Branch::Grid(grid))); } } } @@ -645,9 +642,7 @@ impl Nodes { } } Node::Branch(branch) => { - branch - .children - .retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep); + branch.retain(|child| self.gc_node_id(behavior, visited, child) == GcAction::Keep); } } self.nodes.insert(node_id, node); @@ -820,48 +815,31 @@ impl Nodes { if let Node::Branch(branch) = &mut node { // TODO: join nested versions of the same horizontal/vertical layouts - branch - .children - .retain_mut(|child| match self.simplify(options, *child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - if branch.active_tab == *child { - branch.active_tab = new; - } - branch.linear_shares.replace_with(*child, new); - if let Some(loc) = branch.grid_locations.remove(child) { - branch.grid_locations.insert(new, loc); - } + branch.simplify_children(|child| self.simplify(options, child)); - *child = new; - true - } - }); - - if branch.layout == Layout::Tabs { - if options.prune_empty_tabs && branch.children.is_empty() { + if branch.get_layout() == Layout::Tabs { + if options.prune_empty_tabs && branch.is_empty() { log::debug!("Simplify: removing empty tabs node"); return SimplifyAction::Remove; } - if options.prune_single_child_tabs && branch.children.len() == 1 { + if options.prune_single_child_tabs && branch.children().len() == 1 { if options.all_leaves_must_have_tabs - && matches!(self.get(branch.children[0]), Some(Node::Leaf(_))) + && matches!(self.get(branch.children()[0]), Some(Node::Leaf(_))) { // Keep it } else { log::debug!("Simplify: collapsing single-child tabs node"); - return SimplifyAction::Replace(branch.children[0]); + return SimplifyAction::Replace(branch.children()[0]); } } } else { - if options.prune_empty_layouts && branch.children.is_empty() { + if options.prune_empty_layouts && branch.is_empty() { log::debug!("Simplify: removing empty layout node"); return SimplifyAction::Remove; } - if options.prune_single_child_layouts && branch.children.len() == 1 { + if options.prune_single_child_layouts && branch.children().len() == 1 { log::debug!("Simplify: collapsing single-child layout node"); - return SimplifyAction::Replace(branch.children[0]); + return SimplifyAction::Replace(branch.children()[0]); } } } @@ -888,8 +866,8 @@ impl Nodes { } } Node::Branch(branch) => { - let is_tabs = branch.layout == Layout::Tabs; - for &child in &branch.children { + let is_tabs = branch.get_layout() == Layout::Tabs; + for &child in branch.children() { self.make_all_leaves_children_of_tabs(is_tabs, child); } } diff --git a/examples/dock/src/main.rs b/examples/dock/src/main.rs index 645687ad0..b11254689 100644 --- a/examples/dock/src/main.rs +++ b/examples/dock/src/main.rs @@ -232,16 +232,20 @@ fn tree_ui( .show(ui, |ui| match &mut node { dock::Node::Leaf(_) => {} dock::Node::Branch(branch) => { + let mut layout = branch.get_layout(); egui::ComboBox::from_label("Layout") - .selected_text(format!("{:?}", branch.layout)) + .selected_text(format!("{:?}", layout)) .show_ui(ui, |ui| { for typ in dock::Layout::ALL { - ui.selectable_value(&mut branch.layout, typ, format!("{:?}", typ)) + ui.selectable_value(&mut layout, typ, format!("{:?}", typ)) .clicked(); } }); + if layout != branch.get_layout() { + branch.set_layout(layout); + } - for &child in &branch.children { + for &child in branch.children() { tree_ui(ui, behavior, nodes, child); } }