diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index 4b0fc7082..3b617fdab 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -173,11 +173,13 @@ pub enum UiResponse { DragStarted, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SimplificationOptions { pub prune_empty_tabs: bool, pub prune_single_child_tabs: bool, pub prune_empty_layouts: bool, pub prune_single_child_layouts: bool, + pub all_leaves_must_have_tabs: bool, } impl Default for SimplificationOptions { @@ -187,6 +189,7 @@ impl Default for SimplificationOptions { prune_single_child_tabs: true, prune_empty_layouts: true, prune_single_child_layouts: true, + all_leaves_must_have_tabs: false, } } } @@ -410,7 +413,12 @@ impl Dock { } pub fn ui(&mut self, behavior: &mut dyn Behavior, ui: &mut Ui) { - self.simplify(&behavior.simplification_options()); + let options = behavior.simplification_options(); + self.simplify(&options); + if options.all_leaves_must_have_tabs { + self.nodes + .make_all_leaves_children_of_tabs(false, self.root); + } self.nodes.gc_root(behavior, self.root); self.nodes.layout_node( @@ -1020,12 +1028,18 @@ impl Nodes { }); if options.prune_empty_tabs && children.is_empty() { - log::debug!("Simplify: removing empty tabs"); + log::debug!("Simplify: removing empty tabs node"); return SimplifyAction::Remove; } if options.prune_single_child_tabs && children.len() == 1 { - log::debug!("Simplify: removing single-child tabs"); - return SimplifyAction::Replace(children[0]); + if options.all_leaves_must_have_tabs + && matches!(self.get(children[0]), Some(NodeLayout::Leaf(_))) + { + // Keep it + } else { + log::debug!("Simplify: collapsing single-child tabs node"); + return SimplifyAction::Replace(children[0]); + } } } @@ -1042,11 +1056,11 @@ impl Nodes { }); if options.prune_empty_layouts && children.is_empty() { - log::debug!("Simplify: removing empty layout"); + log::debug!("Simplify: removing empty layout node"); return SimplifyAction::Remove; } if options.prune_single_child_layouts && children.len() == 1 { - log::debug!("Simplify: removing single-child layout"); + log::debug!("Simplify: collapsing single-child layout node"); return SimplifyAction::Replace(children[0]); } } @@ -1056,3 +1070,39 @@ impl Nodes { SimplifyAction::Keep } } + +impl Nodes { + fn make_all_leaves_children_of_tabs(&mut self, parent_is_tabs: bool, it: NodeId) { + let Some(mut node) = self.nodes.remove(&it) else { return; }; + + match &mut node.layout { + NodeLayout::Leaf(_) => { + if !parent_is_tabs { + // Add tabs to this leaf: + let new_id = NodeId::random(); + self.nodes.insert(new_id, node); + let tabs = NodeState::from(NodeLayout::Tabs(Tabs { + children: vec![new_id], + active: new_id, + })); + self.nodes.insert(it, tabs); + return; + } + } + NodeLayout::Tabs(Tabs { children, .. }) => { + for child in children { + self.make_all_leaves_children_of_tabs(true, *child); + } + } + + NodeLayout::Horizontal(Horizontal { children, .. }) + | NodeLayout::Vertical(Vertical { children, .. }) => { + for child in children { + self.make_all_leaves_children_of_tabs(false, *child); + } + } + } + + self.nodes.insert(it, node); + } +} diff --git a/examples/dock/src/main.rs b/examples/dock/src/main.rs index 1168c20e2..0180ed621 100644 --- a/examples/dock/src/main.rs +++ b/examples/dock/src/main.rs @@ -29,9 +29,11 @@ impl View { pub fn ui(&mut self, ui: &mut egui::Ui) -> dock::UiResponse { ui.painter().rect_filled(ui.max_rect(), 0.0, self.color); - ui.label(&self.title); let dragged = ui - .add(egui::Button::new("Drag me to drag view").sense(egui::Sense::drag())) + .add( + egui::Button::new(format!("Contents of {}. Drag me!", self.title)) + .sense(egui::Sense::drag()), + ) .on_hover_cursor(egui::CursorIcon::Grab) .dragged(); if dragged { @@ -42,8 +44,33 @@ impl View { } } +#[derive(Default)] +struct DockBehavior { + simplification_options: dock::SimplificationOptions, +} + +impl dock::Behavior for DockBehavior { + fn leaf_ui( + &mut self, + ui: &mut egui::Ui, + _node_id: dock::NodeId, + view: &mut View, + ) -> dock::UiResponse { + view.ui(ui) + } + + fn tab_text_for_leaf(&mut self, view: &View) -> egui::WidgetText { + view.title.clone().into() + } + + fn simplification_options(&self) -> dock::SimplificationOptions { + self.simplification_options + } +} + struct MyApp { dock: dock::Dock, + behavior: DockBehavior, } impl Default for MyApp { @@ -66,32 +93,43 @@ impl Default for MyApp { let tab2 = { let a = nodes.insert_leaf(gen_view()); let b = nodes.insert_leaf(gen_view()); - nodes.insert_horizontal_node(vec![a, b]) + let c = nodes.insert_leaf(gen_view()); + let d = nodes.insert_leaf(gen_view()); + let e = nodes.insert_leaf(gen_view()); + nodes.insert_horizontal_node(vec![a, b, c, d, e]) }; let root = nodes.insert_tab_node(vec![tab0, tab1, tab2]); let dock = dock::Dock::new(root, nodes); - Self { dock } + Self { + dock, + behavior: Default::default(), + } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - let mut behavior = DockBehavior {}; - egui::SidePanel::left("tree").show(ctx, |ui| { if ui.button("Reset").clicked() { *self = Default::default(); } + ui.checkbox( + &mut self + .behavior + .simplification_options + .all_leaves_must_have_tabs, + "All views have tabs", + ); ui.separator(); - tree_ui(ui, &mut behavior, &self.dock.nodes, self.dock.root); + tree_ui(ui, &mut self.behavior, &self.dock.nodes, self.dock.root); }); egui::CentralPanel::default().show(ctx, |ui| { - self.dock.ui(&mut behavior, ui); + self.dock.ui(&mut self.behavior, ui); }); } } @@ -136,20 +174,3 @@ fn tree_ui( } }); } - -struct DockBehavior {} - -impl dock::Behavior for DockBehavior { - fn leaf_ui( - &mut self, - ui: &mut egui::Ui, - _node_id: dock::NodeId, - view: &mut View, - ) -> dock::UiResponse { - view.ui(ui) - } - - fn tab_text_for_leaf(&mut self, view: &View) -> egui::WidgetText { - view.title.clone().into() - } -}