diff --git a/crates/egui_extras/src/dock/behavior.rs b/crates/egui_extras/src/dock/behavior.rs index 47c661d81..dca8809f8 100644 --- a/crates/egui_extras/src/dock/behavior.rs +++ b/crates/egui_extras/src/dock/behavior.rs @@ -25,7 +25,7 @@ pub trait Behavior { if let Some(node) = nodes.nodes.get(&node_id) { match node { Node::Leaf(leaf) => self.tab_title_for_leaf(leaf), - Node::Branch(branch) => format!("{:?}", branch.get_layout()).into(), + Node::Branch(branch) => format!("{:?}", branch.layout()).into(), } } else { "MISSING NODE".into() diff --git a/crates/egui_extras/src/dock/branch/grid.rs b/crates/egui_extras/src/dock/branch/grid.rs index f4e9de13c..42bad12cd 100644 --- a/crates/egui_extras/src/dock/branch/grid.rs +++ b/crates/egui_extras/src/dock/branch/grid.rs @@ -198,8 +198,6 @@ impl Grid { ui: &mut egui::Ui, node_id: NodeId, ) { - // Grid drops are handled during layout. TODO: handle here instead. - for &child in &self.children { nodes.node_ui(behavior, drop_context, ui, child); } @@ -425,7 +423,10 @@ fn shrink_shares( } fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec { - assert!(!shares.is_empty()); + if shares.is_empty() { + return vec![]; + } + let available_size = available_size - gap_width * (shares.len() - 1) as f32; let available_size = available_size.at_least(0.0); diff --git a/crates/egui_extras/src/dock/branch/linear.rs b/crates/egui_extras/src/dock/branch/linear.rs index 42645dc02..197a9edb0 100644 --- a/crates/egui_extras/src/dock/branch/linear.rs +++ b/crates/egui_extras/src/dock/branch/linear.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use egui::{pos2, vec2, NumExt, Rect}; use itertools::Itertools as _; @@ -6,7 +8,60 @@ use crate::dock::{ ResizeState, }; -use super::Shares; +// ---------------------------------------------------------------------------- + +/// How large of a share of space each child has, on a 1D axis. +/// +/// Used for [`Linear`] layouts (horizontal and vertical). +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct Shares { + /// How large of a share each child has. + /// + /// For instance, the shares `[1, 2, 3]` means that the first child gets 1/6 of the space, + /// the second gets 2/6 and the third gets 3/6. + shares: HashMap, +} + +impl Shares { + pub fn replace_with(&mut self, remove: NodeId, new: NodeId) { + if let Some(share) = self.shares.remove(&remove) { + self.shares.insert(new, share); + } + } + + /// Split the given width based on the share of the children. + pub fn split(&self, children: &[NodeId], available_width: f32) -> Vec { + let mut num_shares = 0.0; + for &child in children { + num_shares += self[child]; + } + if num_shares == 0.0 { + num_shares = 1.0; + } + children + .iter() + .map(|&child| available_width * self[child] / num_shares) + .collect() + } +} + +impl std::ops::Index for Shares { + type Output = f32; + + #[inline] + fn index(&self, id: NodeId) -> &Self::Output { + self.shares.get(&id).unwrap_or(&1.0) + } +} + +impl std::ops::IndexMut for Shares { + #[inline] + fn index_mut(&mut self, id: NodeId) -> &mut Self::Output { + self.shares.entry(id).or_insert(1.0) + } +} + +// ---------------------------------------------------------------------------- #[derive(Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] pub enum LinearDir { @@ -350,7 +405,8 @@ fn linear_drop_zones( ); } -pub fn drop_zones( +/// Register drop-zones for a linear layout. +pub(super) fn drop_zones( preview_thickness: f32, children: &[NodeId], dragged_index: Option, @@ -407,7 +463,9 @@ pub fn drop_zones( } if let Some(last_rect) = prev_rect { - // Suggest dropping after the last child: - add_drop_drect(after_rect(last_rect), insertion_index + 1); + // Suggest dropping after the last child (unless that's the one being dragged): + if dragged_index != Some(children.len() - 1) { + add_drop_drect(after_rect(last_rect), insertion_index + 1); + } } } diff --git a/crates/egui_extras/src/dock/branch/mod.rs b/crates/egui_extras/src/dock/branch/mod.rs index d6585c0e4..4c4750a95 100644 --- a/crates/egui_extras/src/dock/branch/mod.rs +++ b/crates/egui_extras/src/dock/branch/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use egui::Rect; use super::{Behavior, DropContext, NodeId, Nodes, SimplifyAction}; @@ -9,7 +7,7 @@ mod linear; mod tabs; pub use grid::{Grid, GridLoc}; -pub use linear::{Linear, LinearDir}; +pub use linear::{Linear, LinearDir, Shares}; pub use tabs::Tabs; // ---------------------------------------------------------------------------- @@ -29,53 +27,6 @@ impl Layout { // ---------------------------------------------------------------------------- -/// 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 { - /// How large of a share each child has. - /// - /// For instance, the shares `[1, 2, 3]` means that the first child gets 1/6 of the space, - /// the second gets 2/6 and the third gets 3/6. - shares: HashMap, -} - -impl Shares { - pub fn replace_with(&mut self, a: NodeId, b: NodeId) { - if let Some(share) = self.shares.remove(&a) { - self.shares.insert(b, share); - } - } - - pub fn split(&self, children: &[NodeId], available_width: f32) -> Vec { - let mut num_shares = 0.0; - for &child in children { - num_shares += self[child]; - } - if num_shares == 0.0 { - num_shares = 1.0; - } - children - .iter() - .map(|&child| available_width * self[child] / num_shares) - .collect() - } -} - -impl std::ops::Index for Shares { - type Output = f32; - fn index(&self, id: NodeId) -> &Self::Output { - self.shares.get(&id).unwrap_or(&1.0) - } -} - -impl std::ops::IndexMut for Shares { - fn index_mut(&mut self, id: NodeId) -> &mut Self::Output { - self.shares.entry(id).or_insert(1.0) - } -} - -// ---------------------------------------------------------------------------- - #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum Branch { Tabs(Tabs), @@ -116,7 +67,7 @@ impl Branch { } } - pub fn get_layout(&self) -> Layout { + pub fn layout(&self) -> Layout { match self { Self::Tabs(_) => Layout::Tabs, Self::Linear(linear) => match linear.dir { @@ -128,7 +79,7 @@ impl Branch { } pub fn set_layout(&mut self, layout: Layout) { - if layout == self.get_layout() { + if layout == self.layout() { return; } @@ -191,7 +142,7 @@ impl Branch { } impl Branch { - pub(super) fn layout( + pub(super) fn layout_recursive( &mut self, nodes: &mut Nodes, style: &egui::Style, diff --git a/crates/egui_extras/src/dock/branch/tabs.rs b/crates/egui_extras/src/dock/branch/tabs.rs index ea8079832..f83fdde0b 100644 --- a/crates/egui_extras/src/dock/branch/tabs.rs +++ b/crates/egui_extras/src/dock/branch/tabs.rs @@ -8,7 +8,10 @@ use crate::dock::{ #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct Tabs { + /// The tabs, in order. pub children: Vec, + + /// The currenlty open tab. pub active: NodeId, } @@ -26,7 +29,7 @@ impl Tabs { self.active = child; } - pub fn layout( + pub(super) fn layout( &mut self, nodes: &mut Nodes, style: &egui::Style, @@ -62,10 +65,11 @@ impl Tabs { nodes.node_ui(behavior, drop_context, ui, self.active); } - // We have only laid out the active tab, so we need to switch active tab after the ui pass: + // We have only laid out the active tab, so we need to switch active tab _after_ the ui pass above: self.active = next_active; } + /// Returns the next active tab (e.g. the one clicked, or the current). fn tab_bar_ui( &self, behavior: &mut dyn Behavior, diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index fbebd6684..65f9f0f8d 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -16,9 +16,15 @@ //! The user needs to implement this in order to specify the `ui` of each `Leaf` and //! the tab name of leaves (if there are tab nodes). //! -//! ## Shortcomings -//! We use real recursion, so if your trees get too deep you will get a stack overflow. +//! ## Shares +//! The relative sizes of linear layout (horizontal or vertical) and grid columns and rows are spcified by _shares_. +//! If the shares are `1,2,3` it means the first element gets `1/6` of the space, the second `2/6`, and the third `3/6`. +//! The default share size is `1`, and when resizing the shares are restributed so that +//! the total shares are always aproximately the same as the number of rows/columns. +//! This makes it easy to add new rows/columns. //! +//! ## Shortcomings +//! The implementation is recursive, so if your trees get too deep you will get a stack overflow. //! //! ## Future improvements //! * Easy per-tab close-buttons @@ -93,7 +99,7 @@ impl Node { fn layout(&self) -> Option { match self { Node::Leaf(_) => None, - Node::Branch(branch) => Some(branch.get_layout()), + Node::Branch(branch) => Some(branch.layout()), } } } diff --git a/crates/egui_extras/src/dock/nodes.rs b/crates/egui_extras/src/dock/nodes.rs index ca16f9d74..087821823 100644 --- a/crates/egui_extras/src/dock/nodes.rs +++ b/crates/egui_extras/src/dock/nodes.rs @@ -227,7 +227,7 @@ impl Nodes { self.rects.insert(node_id, rect); if let Node::Branch(branch) = &mut node { - branch.layout(self, style, behavior, rect); + branch.layout_recursive(self, style, behavior, rect); } self.nodes.insert(node_id, node); @@ -296,7 +296,7 @@ impl Nodes { branch.simplify_children(|child| self.simplify(options, child)); - if branch.get_layout() == Layout::Tabs { + if branch.layout() == Layout::Tabs { if options.prune_empty_tabs && branch.is_empty() { log::debug!("Simplify: removing empty tabs node"); return SimplifyAction::Remove; @@ -346,7 +346,7 @@ impl Nodes { } } Node::Branch(branch) => { - let is_tabs = branch.get_layout() == Layout::Tabs; + let is_tabs = branch.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 e5d958db9..4f1dd4c22 100644 --- a/examples/dock/src/main.rs +++ b/examples/dock/src/main.rs @@ -263,7 +263,7 @@ fn tree_ui( .show(ui, |ui| match &mut node { dock::Node::Leaf(_) => {} dock::Node::Branch(branch) => { - let mut layout = branch.get_layout(); + let mut layout = branch.layout(); egui::ComboBox::from_label("Layout") .selected_text(format!("{:?}", layout)) .show_ui(ui, |ui| { @@ -272,7 +272,7 @@ fn tree_ui( .clicked(); } }); - if layout != branch.get_layout() { + if layout != branch.layout() { branch.set_layout(layout); }