diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index 7a583dfd6..1860d7401 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -75,38 +75,44 @@ impl Default for Nodes { #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum Node { Leaf(Leaf), - Tabs(Tabs), - Horizontal(Horizontal), - Vertical(Vertical), + Branch(Branch), } impl Node { - pub fn name(&self) -> &'static str { + fn branch_type(&self) -> Option { match self { - Node::Leaf(_) => "Leaf", - Node::Tabs(_) => "Tabs", - Node::Horizontal(_) => "Horizontal", - Node::Vertical(_) => "Vertical", + Node::Leaf(_) => None, + Node::Branch(branch) => Some(branch.typ), } } } #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Tabs { +pub struct Branch { + pub typ: BranchType, pub children: Vec, - pub active: NodeId, + + /// Only if [`Self.typ`] == [`BranchType::Tab`] + pub active_tab: NodeId, + + /// Only for linear layouts (horizontal or vertical). + pub linear_shares: Shares, } -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Horizontal { - pub children: Vec, - pub shares: Shares, -} +impl Branch { + pub fn new(typ: BranchType, children: Vec) -> Self { + let active_tab = children.first().copied().unwrap_or_default(); + Self { + typ, + children, + active_tab, + ..Default::default() + } + } -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Vertical { - pub children: Vec, - pub shares: Shares, + pub fn new_tabs(children: Vec) -> Self { + Self::new(BranchType::Tabs, children) + } } /// How large of a share of space each child has, on a 1D axis. @@ -137,20 +143,9 @@ impl Shares { } } -impl From for Node { - fn from(tabs: Tabs) -> Self { - Node::Tabs(tabs) - } -} - -impl From for Node { - fn from(horizontal: Horizontal) -> Self { - Node::Horizontal(horizontal) - } -} - -#[derive(Clone, Copy, Debug)] -enum BranchType { +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum BranchType { + #[default] Tabs, Horizontal, Vertical, @@ -220,7 +215,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), - layout => layout.name().into(), + Node::Branch(branch) => format!("{:?}", branch.typ).into(), } } @@ -299,6 +294,11 @@ impl Nodes { id } + #[must_use] + pub fn insert_branch(&mut self, branch: Branch) -> NodeId { + self.insert_node(Node::Branch(branch)) + } + #[must_use] pub fn insert_leaf(&mut self, leaf: Leaf) -> NodeId { self.insert_node(Node::Leaf(leaf)) @@ -306,28 +306,23 @@ impl Nodes { #[must_use] pub fn insert_tab_node(&mut self, children: Vec) -> NodeId { - let tabs = Tabs { - active: children.first().copied().unwrap_or_default(), - children, - }; - self.insert_node(Node::Tabs(tabs)) + self.insert_node(Node::Branch(Branch::new_tabs(children))) } #[must_use] pub fn insert_horizontal_node(&mut self, children: Vec) -> NodeId { - let horizontal = Horizontal { - children, - shares: Default::default(), - }; - self.insert_node(Node::Horizontal(horizontal)) + self.insert_node(Node::Branch(Branch::new(BranchType::Horizontal, children))) + } + + #[must_use] + pub fn insert_vertical_node(&mut self, children: Vec) -> NodeId { + self.insert_node(Node::Branch(Branch::new(BranchType::Vertical, children))) } fn parent(&self, it: NodeId, needle_child: NodeId) -> Option { match &self.nodes.get(&it)? { Node::Leaf(_) => None, - Node::Tabs(Tabs { children, .. }) - | Node::Horizontal(Horizontal { children, .. }) - | Node::Vertical(Vertical { children, .. }) => { + Node::Branch(Branch { children, .. }) => { for &child in children { if child == needle_child { return Some(it); @@ -347,19 +342,10 @@ impl Nodes { return GcAction::Remove; } let Some(mut node) = self.nodes.remove(&it) else { return GcAction::Remove; }; - match &mut node { - Node::Leaf(_) => {} - Node::Tabs(Tabs { children, .. }) => { - children.retain(|&child| { - self.remove_node_id_from_parent(child, remove) == GcAction::Keep - }); - } - Node::Horizontal(Horizontal { children, .. }) - | Node::Vertical(Vertical { children, .. }) => { - children.retain(|&child| { - self.remove_node_id_from_parent(child, remove) == GcAction::Keep - }); - } + if let Node::Branch(branch) = &mut node { + branch + .children + .retain(|&child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep); } self.nodes.insert(it, node); GcAction::Keep @@ -377,48 +363,54 @@ impl Nodes { }; match branch_type { BranchType::Tabs => { - if let Node::Tabs(layout) = &mut node { - let index = index.min(layout.children.len()); - layout.children.insert(index, child_id); + if let Node::Branch(Branch { + typ: BranchType::Tabs, + children, + .. + }) = &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 layout = Tabs { - children: vec![new_node_id], - active: new_node_id, - }; - layout.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Tabs(layout)); + 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)); } } BranchType::Horizontal => { - if let Node::Horizontal(layout) = &mut node { - let index = index.min(layout.children.len()); - layout.children.insert(index, child_id); + if let Node::Branch(Branch { + typ: BranchType::Horizontal, + children, + .. + }) = &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 layout = Horizontal { - children: vec![new_node_id], - shares: Default::default(), - }; - layout.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Horizontal(layout)); + let mut branch = Branch::new(BranchType::Horizontal, vec![new_node_id]); + branch.children.insert(index.min(1), child_id); + self.nodes.insert(parent_id, Node::Branch(branch)); } } BranchType::Vertical => { - if let Node::Vertical(layout) = &mut node { - let index = index.min(layout.children.len()); - layout.children.insert(index, child_id); + if let Node::Branch(Branch { + typ: BranchType::Vertical, + children, + .. + }) = &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 layout = Vertical { - children: vec![new_node_id], - shares: Default::default(), - }; - layout.children.insert(index.min(1), child_id); - self.nodes.insert(parent_id, Node::Vertical(layout)); + let mut branch = Branch::new(BranchType::Vertical, vec![new_node_id]); + branch.children.insert(index.min(1), child_id); + self.nodes.insert(parent_id, Node::Branch(branch)); } } } @@ -466,7 +458,7 @@ impl Dock { // Check if anything is being dragged: let mut drop_context = DropContext { - active: true, + enabled: true, dragged_node_id: self.dragged_id(ui.ctx()), mouse_pos: ui.input(|i| i.pointer.hover_pos()), best_dist_sq: f32::INFINITY, @@ -656,9 +648,7 @@ impl Nodes { return GcAction::Remove; } } - Node::Tabs(Tabs { children, .. }) - | Node::Horizontal(Horizontal { children, .. }) - | Node::Vertical(Vertical { children, .. }) => { + Node::Branch(Branch { children, .. }) => { children .retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep); } @@ -684,35 +674,43 @@ impl Nodes { match &node { Node::Leaf(_) => {} - Node::Tabs(tabs) => { - self.layout_tabs(style, behavior, rect, tabs); - } - Node::Horizontal(horizontal) => { - self.layout_horizontal(style, behavior, rect, horizontal); - } - Node::Vertical(vertical) => { - self.layout_vertical(style, behavior, rect, vertical); + Node::Branch(branch) => { + self.layout_branch(style, behavior, rect, branch); } } self.nodes.insert(node_id, node); } + fn layout_branch( + &mut self, + style: &Style, + behavior: &mut dyn Behavior, + rect: Rect, + branch: &Branch, + ) { + match branch.typ { + BranchType::Tabs => self.layout_tabs(style, behavior, rect, branch), + BranchType::Horizontal => self.layout_horizontal(style, behavior, rect, branch), + BranchType::Vertical => self.layout_vertical(style, behavior, rect, branch), + } + } + fn layout_tabs( &mut self, style: &Style, behavior: &mut dyn Behavior, rect: Rect, - tabs: &Tabs, + branch: &Branch, ) { let mut active_rect = rect; active_rect.min.y += behavior.tab_bar_height(style); if false { - self.layout_node(style, behavior, active_rect, tabs.active); + self.layout_node(style, behavior, active_rect, branch.active_tab); } else { // Layout all nodes in case the user switches active tab - for &child_id in &tabs.children { + for &child_id in &branch.children { self.layout_node(style, behavior, active_rect, child_id); } } @@ -723,20 +721,22 @@ impl Nodes { style: &Style, behavior: &mut dyn Behavior, rect: Rect, - layout: &Horizontal, + branch: &Branch, ) { - if layout.children.is_empty() { + if branch.children.is_empty() { return; } - let num_gaps = layout.children.len() - 1; + let num_gaps = branch.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 = layout.shares.split(&layout.children, available_width); + let widths = branch + .linear_shares + .split(&branch.children, available_width); let mut x = rect.min.x; - for (child, width) in layout.children.iter().zip(widths) { + 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); x += width + gap_width; @@ -748,20 +748,22 @@ impl Nodes { style: &Style, behavior: &mut dyn Behavior, rect: Rect, - layout: &Vertical, + branch: &Branch, ) { - if layout.children.is_empty() { + if branch.children.is_empty() { return; } - let num_gaps = layout.children.len() - 1; + let num_gaps = branch.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 heights = branch + .linear_shares + .split(&branch.children, available_height); let mut y = rect.min.y; - for (child, height) in layout.children.iter().zip(heights) { + 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); y += height + gap_height; @@ -773,7 +775,7 @@ impl Nodes { // ui struct DropContext { - active: bool, + enabled: bool, dragged_node_id: Option, mouse_pos: Option, @@ -783,12 +785,12 @@ struct DropContext { } impl DropContext { - fn on_node(&mut self, parent_id: NodeId, rect: Rect, layout: &Node) { - if !self.active { + fn on_node(&mut self, parent_id: NodeId, rect: Rect, node: &Node) { + if !self.enabled { return; } - if !matches!(layout, Node::Horizontal(_)) { + if node.branch_type() != Some(BranchType::Horizontal) { self.suggest_rect( InsertionPoint::new(parent_id, BranchType::Horizontal, 0), rect.split_left_right_at_fraction(0.5).0, @@ -799,7 +801,7 @@ impl DropContext { ); } - if !matches!(layout, Node::Vertical(_)) { + if node.branch_type() != Some(BranchType::Vertical) { self.suggest_rect( InsertionPoint::new(parent_id, BranchType::Vertical, 0), rect.split_top_bottom_at_fraction(0.5).0, @@ -818,7 +820,7 @@ impl DropContext { } fn suggest_point(&mut self, insertion: InsertionPoint, target_point: Pos2, preview_rect: Rect) { - if !self.active { + if !self.enabled { return; } if let Some(mouse_pos) = self.mouse_pos { @@ -842,10 +844,10 @@ impl Nodes { ) { let (Some(rect), Some(mut node)) = (self.rect(node_id), self.nodes.remove(&node_id)) else { return }; - let drop_context_was_active = drop_context.active; + let drop_context_was_enabled = drop_context.enabled; if Some(node_id) == drop_context.dragged_node_id { // Can't drag a node onto self or any children - drop_context.active = false; + drop_context.enabled = false; } drop_context.on_node(node_id, rect, &node); @@ -862,19 +864,35 @@ impl Nodes { ui.memory_mut(|mem| mem.set_dragged_id(node_id.id())); } } - Node::Tabs(tabs) => { - self.tabs_ui(behavior, drop_context, ui, rect, node_id, tabs); - } - Node::Horizontal(horizontal) => { - self.horizontal_ui(behavior, drop_context, ui, node_id, horizontal); - } - Node::Vertical(vertical) => { - self.vertical_ui(behavior, drop_context, ui, node_id, vertical); + Node::Branch(branch) => { + self.branch_ui(behavior, drop_context, ui, rect, node_id, branch); } }; self.nodes.insert(node_id, node); - drop_context.active = drop_context_was_active; + drop_context.enabled = drop_context_was_enabled; + } + + fn branch_ui( + &mut self, + behavior: &mut dyn Behavior, + drop_context: &mut DropContext, + ui: &mut Ui, + rect: Rect, + node_id: NodeId, + branch: &mut Branch, + ) { + match branch.typ { + BranchType::Tabs => { + self.tabs_ui(behavior, drop_context, ui, rect, node_id, branch); + } + BranchType::Horizontal => { + self.horizontal_ui(behavior, drop_context, ui, node_id, branch); + } + BranchType::Vertical => { + self.vertical_ui(behavior, drop_context, ui, node_id, branch); + } + } } fn tabs_ui( @@ -884,11 +902,15 @@ impl Nodes { ui: &mut Ui, rect: Rect, node_id: NodeId, - tabs: &mut Tabs, + branch: &mut Branch, ) { - if !tabs.children.iter().any(|&child| child == tabs.active) { + if !branch + .children + .iter() + .any(|&child| child == branch.active_tab) + { // Make sure something is active: - tabs.active = tabs.children.first().copied().unwrap_or_default(); + branch.active_tab = branch.children.first().copied().unwrap_or_default(); } let tab_bar_height = behavior.tab_bar_height(ui.style()); @@ -900,24 +922,24 @@ impl Nodes { let mut prev_tab_rect: Option = None; let mut insertion_index = 0; // skips over drag-source, if any, beacuse it will be removed then re-inserted - for (i, &child_id) in tabs.children.iter().enumerate() { + for (i, &child_id) in branch.children.iter().enumerate() { if is_being_dragged(ui.ctx(), child_id) { continue; // leave a gap! } - let selected = child_id == tabs.active; + let selected = child_id == branch.active_tab; let id = child_id.id(); let response = behavior.tab_ui(self, ui, id, child_id, selected); let response = response.on_hover_cursor(CursorIcon::Grab); if response.clicked() { - tabs.active = child_id; + branch.active_tab = 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; + branch.active_tab = child_id; } } @@ -939,7 +961,7 @@ impl Nodes { ); } - if i + 1 == tabs.children.len() { + if i + 1 == branch.children.len() { // suggest dropping after last tab: drop_context.suggest_rect( InsertionPoint::new(node_id, BranchType::Tabs, insertion_index + 1), @@ -953,10 +975,10 @@ impl Nodes { }); // When dragged, don't show it (it is "being held") - let is_active_being_dragged = - ui.memory(|mem| mem.is_being_dragged(tabs.active.id())) && is_possible_drag(ui.ctx()); + let is_active_being_dragged = ui.memory(|mem| mem.is_being_dragged(branch.active_tab.id())) + && is_possible_drag(ui.ctx()); if !is_active_being_dragged { - self.node_ui(behavior, drop_context, ui, tabs.active); + self.node_ui(behavior, drop_context, ui, branch.active_tab); } } @@ -966,12 +988,12 @@ impl Nodes { drop_context: &mut DropContext, ui: &mut Ui, parent_id: NodeId, - horizontal: &mut Horizontal, + branch: &mut Branch, ) { let mut prev_rect: Option = None; let mut insertion_index = 0; // skips over drag-source, if any, beacuse it will be removed then re-inserted - for (i, &child) in horizontal.children.iter().enumerate() { + for (i, &child) in branch.children.iter().enumerate() { let Some(rect) = self.rect(child) else { continue; }; if is_being_dragged(ui.ctx(), child) { @@ -997,7 +1019,7 @@ impl Nodes { ); } - if i + 1 == horizontal.children.len() { + if i + 1 == branch.children.len() { // Suggest dropping after the last child: drop_context.suggest_rect( InsertionPoint::new(parent_id, BranchType::Horizontal, insertion_index + 1), @@ -1017,9 +1039,9 @@ impl Nodes { drop_context: &mut DropContext, ui: &mut Ui, parent_id: NodeId, - vertical: &mut Vertical, + branch: &mut Branch, ) { - for child in &vertical.children { + for child in &branch.children { self.node_ui(behavior, drop_context, ui, *child); } } @@ -1039,55 +1061,46 @@ impl Nodes { fn simplify(&mut self, options: &SimplificationOptions, it: NodeId) -> SimplifyAction { let Some(mut node) = self.nodes.remove(&it) else { return SimplifyAction::Remove; }; - match &mut node { - Node::Leaf(_) => {} - Node::Tabs(Tabs { children, .. }) => { - children.retain_mut(|child| match self.simplify(options, *child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - *child = new; - true - } - }); + if let Node::Branch(Branch { typ, children, .. }) = &mut node { + // TODO: join nested versions of the same horizontal/vertical layouts - if options.prune_empty_tabs && children.is_empty() { - log::debug!("Simplify: removing empty tabs node"); - return SimplifyAction::Remove; + children.retain_mut(|child| match self.simplify(options, *child) { + SimplifyAction::Remove => false, + SimplifyAction::Keep => true, + SimplifyAction::Replace(new) => { + *child = new; + true } - if options.prune_single_child_tabs && children.len() == 1 { - if options.all_leaves_must_have_tabs - && matches!(self.get(children[0]), Some(Node::Leaf(_))) - { - // Keep it - } else { - log::debug!("Simplify: collapsing single-child tabs node"); + }); + + match typ { + BranchType::Tabs => { + if options.prune_empty_tabs && children.is_empty() { + log::debug!("Simplify: removing empty tabs node"); + return SimplifyAction::Remove; + } + if options.prune_single_child_tabs && children.len() == 1 { + if options.all_leaves_must_have_tabs + && matches!(self.get(children[0]), Some(Node::Leaf(_))) + { + // Keep it + } else { + log::debug!("Simplify: collapsing single-child tabs node"); + return SimplifyAction::Replace(children[0]); + } + } + } + BranchType::Horizontal | BranchType::Vertical => { + if options.prune_empty_layouts && children.is_empty() { + log::debug!("Simplify: removing empty layout node"); + return SimplifyAction::Remove; + } + if options.prune_single_child_layouts && children.len() == 1 { + log::debug!("Simplify: collapsing single-child layout node"); return SimplifyAction::Replace(children[0]); } } } - - Node::Horizontal(Horizontal { children, .. }) - | Node::Vertical(Vertical { children, .. }) => { - // TODO: join nested versions of the same thing - children.retain_mut(|child| match self.simplify(options, *child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - *child = new; - true - } - }); - - if options.prune_empty_layouts && children.is_empty() { - log::debug!("Simplify: removing empty layout node"); - return SimplifyAction::Remove; - } - if options.prune_single_child_layouts && children.len() == 1 { - log::debug!("Simplify: collapsing single-child layout node"); - return SimplifyAction::Replace(children[0]); - } - } } self.nodes.insert(it, node); @@ -1105,24 +1118,15 @@ impl Nodes { // Add tabs to this leaf: let new_id = NodeId::random(); self.nodes.insert(new_id, node); - let tabs = Node::Tabs(Tabs { - children: vec![new_id], - active: new_id, - }); - self.nodes.insert(it, tabs); + self.nodes + .insert(it, Node::Branch(Branch::new_tabs(vec![new_id]))); return; } } - Node::Tabs(Tabs { children, .. }) => { + Node::Branch(Branch { typ, children, .. }) => { + let is_tabs = *typ == BranchType::Tabs; for child in children { - self.make_all_leaves_children_of_tabs(true, *child); - } - } - - Node::Horizontal(Horizontal { children, .. }) - | Node::Vertical(Vertical { children, .. }) => { - for child in children { - self.make_all_leaves_children_of_tabs(false, *child); + 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 780fc5a18..9981b0867 100644 --- a/examples/dock/src/main.rs +++ b/examples/dock/src/main.rs @@ -177,18 +177,8 @@ fn tree_ui( .default_open(true) .show(ui, |ui| match node { dock::Node::Leaf(_) => {} - dock::Node::Tabs(tabs) => { - for &child in &tabs.children { - tree_ui(ui, behavior, nodes, child); - } - } - dock::Node::Horizontal(layout) => { - for &child in &layout.children { - tree_ui(ui, behavior, nodes, child); - } - } - dock::Node::Vertical(layout) => { - for &child in &layout.children { + dock::Node::Branch(branch) => { + for &child in &branch.children { tree_ui(ui, behavior, nodes, child); } }