Browse Source

Last cleanup pass

emilk/dock
Emil Ernerfeldt 2 years ago
parent
commit
1799713fb0
  1. 2
      crates/egui_extras/src/dock/behavior.rs
  2. 7
      crates/egui_extras/src/dock/branch/grid.rs
  3. 66
      crates/egui_extras/src/dock/branch/linear.rs
  4. 57
      crates/egui_extras/src/dock/branch/mod.rs
  5. 8
      crates/egui_extras/src/dock/branch/tabs.rs
  6. 12
      crates/egui_extras/src/dock/mod.rs
  7. 6
      crates/egui_extras/src/dock/nodes.rs
  8. 4
      examples/dock/src/main.rs

2
crates/egui_extras/src/dock/behavior.rs

@ -25,7 +25,7 @@ pub trait Behavior<Leaf> {
if let Some(node) = nodes.nodes.get(&node_id) { if let Some(node) = nodes.nodes.get(&node_id) {
match node { match node {
Node::Leaf(leaf) => self.tab_title_for_leaf(leaf), 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 { } else {
"MISSING NODE".into() "MISSING NODE".into()

7
crates/egui_extras/src/dock/branch/grid.rs

@ -198,8 +198,6 @@ impl Grid {
ui: &mut egui::Ui, ui: &mut egui::Ui,
node_id: NodeId, node_id: NodeId,
) { ) {
// Grid drops are handled during layout. TODO: handle here instead.
for &child in &self.children { for &child in &self.children {
nodes.node_ui(behavior, drop_context, ui, child); nodes.node_ui(behavior, drop_context, ui, child);
} }
@ -425,7 +423,10 @@ fn shrink_shares<Leaf>(
} }
fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec<f32> { fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec<f32> {
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 - gap_width * (shares.len() - 1) as f32;
let available_size = available_size.at_least(0.0); let available_size = available_size.at_least(0.0);

66
crates/egui_extras/src/dock/branch/linear.rs

@ -1,3 +1,5 @@
use std::collections::HashMap;
use egui::{pos2, vec2, NumExt, Rect}; use egui::{pos2, vec2, NumExt, Rect};
use itertools::Itertools as _; use itertools::Itertools as _;
@ -6,7 +8,60 @@ use crate::dock::{
ResizeState, 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<NodeId, f32>,
}
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<f32> {
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<NodeId> 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<NodeId> 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)] #[derive(Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]
pub enum LinearDir { pub enum LinearDir {
@ -350,7 +405,8 @@ fn linear_drop_zones<Leaf>(
); );
} }
pub fn drop_zones( /// Register drop-zones for a linear layout.
pub(super) fn drop_zones(
preview_thickness: f32, preview_thickness: f32,
children: &[NodeId], children: &[NodeId],
dragged_index: Option<usize>, dragged_index: Option<usize>,
@ -407,7 +463,9 @@ pub fn drop_zones(
} }
if let Some(last_rect) = prev_rect { if let Some(last_rect) = prev_rect {
// Suggest dropping after the last child: // Suggest dropping after the last child (unless that's the one being dragged):
add_drop_drect(after_rect(last_rect), insertion_index + 1); if dragged_index != Some(children.len() - 1) {
add_drop_drect(after_rect(last_rect), insertion_index + 1);
}
} }
} }

57
crates/egui_extras/src/dock/branch/mod.rs

@ -1,5 +1,3 @@
use std::collections::HashMap;
use egui::Rect; use egui::Rect;
use super::{Behavior, DropContext, NodeId, Nodes, SimplifyAction}; use super::{Behavior, DropContext, NodeId, Nodes, SimplifyAction};
@ -9,7 +7,7 @@ mod linear;
mod tabs; mod tabs;
pub use grid::{Grid, GridLoc}; pub use grid::{Grid, GridLoc};
pub use linear::{Linear, LinearDir}; pub use linear::{Linear, LinearDir, Shares};
pub use tabs::Tabs; 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<NodeId, f32>,
}
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<f32> {
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<NodeId> for Shares {
type Output = f32;
fn index(&self, id: NodeId) -> &Self::Output {
self.shares.get(&id).unwrap_or(&1.0)
}
}
impl std::ops::IndexMut<NodeId> 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)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum Branch { pub enum Branch {
Tabs(Tabs), Tabs(Tabs),
@ -116,7 +67,7 @@ impl Branch {
} }
} }
pub fn get_layout(&self) -> Layout { pub fn layout(&self) -> Layout {
match self { match self {
Self::Tabs(_) => Layout::Tabs, Self::Tabs(_) => Layout::Tabs,
Self::Linear(linear) => match linear.dir { Self::Linear(linear) => match linear.dir {
@ -128,7 +79,7 @@ impl Branch {
} }
pub fn set_layout(&mut self, layout: Layout) { pub fn set_layout(&mut self, layout: Layout) {
if layout == self.get_layout() { if layout == self.layout() {
return; return;
} }
@ -191,7 +142,7 @@ impl Branch {
} }
impl Branch { impl Branch {
pub(super) fn layout<Leaf>( pub(super) fn layout_recursive<Leaf>(
&mut self, &mut self,
nodes: &mut Nodes<Leaf>, nodes: &mut Nodes<Leaf>,
style: &egui::Style, style: &egui::Style,

8
crates/egui_extras/src/dock/branch/tabs.rs

@ -8,7 +8,10 @@ use crate::dock::{
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Tabs { pub struct Tabs {
/// The tabs, in order.
pub children: Vec<NodeId>, pub children: Vec<NodeId>,
/// The currenlty open tab.
pub active: NodeId, pub active: NodeId,
} }
@ -26,7 +29,7 @@ impl Tabs {
self.active = child; self.active = child;
} }
pub fn layout<Leaf>( pub(super) fn layout<Leaf>(
&mut self, &mut self,
nodes: &mut Nodes<Leaf>, nodes: &mut Nodes<Leaf>,
style: &egui::Style, style: &egui::Style,
@ -62,10 +65,11 @@ impl Tabs {
nodes.node_ui(behavior, drop_context, ui, self.active); 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; self.active = next_active;
} }
/// Returns the next active tab (e.g. the one clicked, or the current).
fn tab_bar_ui<Leaf>( fn tab_bar_ui<Leaf>(
&self, &self,
behavior: &mut dyn Behavior<Leaf>, behavior: &mut dyn Behavior<Leaf>,

12
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 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). //! the tab name of leaves (if there are tab nodes).
//! //!
//! ## Shortcomings //! ## Shares
//! We use real recursion, so if your trees get too deep you will get a stack overflow. //! 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 //! ## Future improvements
//! * Easy per-tab close-buttons //! * Easy per-tab close-buttons
@ -93,7 +99,7 @@ impl<Leaf> Node<Leaf> {
fn layout(&self) -> Option<Layout> { fn layout(&self) -> Option<Layout> {
match self { match self {
Node::Leaf(_) => None, Node::Leaf(_) => None,
Node::Branch(branch) => Some(branch.get_layout()), Node::Branch(branch) => Some(branch.layout()),
} }
} }
} }

6
crates/egui_extras/src/dock/nodes.rs

@ -227,7 +227,7 @@ impl<Leaf> Nodes<Leaf> {
self.rects.insert(node_id, rect); self.rects.insert(node_id, rect);
if let Node::Branch(branch) = &mut node { 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); self.nodes.insert(node_id, node);
@ -296,7 +296,7 @@ impl<Leaf> Nodes<Leaf> {
branch.simplify_children(|child| self.simplify(options, child)); 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() { if options.prune_empty_tabs && branch.is_empty() {
log::debug!("Simplify: removing empty tabs node"); log::debug!("Simplify: removing empty tabs node");
return SimplifyAction::Remove; return SimplifyAction::Remove;
@ -346,7 +346,7 @@ impl<Leaf> Nodes<Leaf> {
} }
} }
Node::Branch(branch) => { Node::Branch(branch) => {
let is_tabs = branch.get_layout() == Layout::Tabs; let is_tabs = branch.layout() == Layout::Tabs;
for &child in branch.children() { for &child in branch.children() {
self.make_all_leaves_children_of_tabs(is_tabs, child); self.make_all_leaves_children_of_tabs(is_tabs, child);
} }

4
examples/dock/src/main.rs

@ -263,7 +263,7 @@ fn tree_ui(
.show(ui, |ui| match &mut node { .show(ui, |ui| match &mut node {
dock::Node::Leaf(_) => {} dock::Node::Leaf(_) => {}
dock::Node::Branch(branch) => { dock::Node::Branch(branch) => {
let mut layout = branch.get_layout(); let mut layout = branch.layout();
egui::ComboBox::from_label("Layout") egui::ComboBox::from_label("Layout")
.selected_text(format!("{:?}", layout)) .selected_text(format!("{:?}", layout))
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
@ -272,7 +272,7 @@ fn tree_ui(
.clicked(); .clicked();
} }
}); });
if layout != branch.get_layout() { if layout != branch.layout() {
branch.set_layout(layout); branch.set_layout(layout);
} }

Loading…
Cancel
Save