1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 07:03:14 -04:00

Move code around a bit

This commit is contained in:
Emil Ernerfeldt
2023-05-04 20:56:58 +02:00
parent 84356885d1
commit e757fdc152
6 changed files with 743 additions and 710 deletions

View File

@@ -1,11 +1,10 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use egui::{emath::Rangef, pos2, vec2, Rect};
use egui::{emath::Rangef, pos2, vec2, NumExt as _, Rect};
use itertools::Itertools as _;
use crate::dock::{
sizes_from_shares, Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes,
ResizeState,
Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes, ResizeState,
};
/// Where in a grid?
@@ -155,7 +154,7 @@ impl Grid {
}
}
pub fn ui<Leaf>(
pub(super) fn ui<Leaf>(
&mut self,
nodes: &mut Nodes<Leaf>,
behavior: &mut dyn Behavior<Leaf>,
@@ -360,3 +359,19 @@ fn shrink_shares<Leaf>(
total_shares_lost
}
fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec<f32> {
assert!(!shares.is_empty());
let available_size = available_size - gap_width * (shares.len() - 1) as f32;
let available_size = available_size.at_least(0.0);
let total_share: f32 = shares.iter().sum();
if total_share <= 0.0 {
vec![available_size / shares.len() as f32; shares.len()]
} else {
shares
.iter()
.map(|&share| share / total_share * available_size)
.collect()
}
}

View File

@@ -95,7 +95,7 @@ impl Linear {
}
}
pub fn ui<Leaf>(
pub(super) fn ui<Leaf>(
&mut self,
nodes: &mut Nodes<Leaf>,
behavior: &mut dyn Behavior<Leaf>,

View File

@@ -46,7 +46,7 @@ impl Tabs {
nodes.layout_node(style, behavior, active_rect, self.active);
}
pub fn ui<Leaf>(
pub(super) fn ui<Leaf>(
&mut self,
nodes: &mut Nodes<Leaf>,
behavior: &mut dyn Behavior<Leaf>,

View File

@@ -0,0 +1,432 @@
use egui::{Id, NumExt as _, Pos2, Rect, Ui};
use super::{
is_possible_drag, Behavior, Branch, DropContext, GcAction, Grid, InsertionPoint,
LayoutInsertion, Linear, LinearDir, Node, NodeId, Nodes, SimplificationOptions, SimplifyAction,
Tabs,
};
/// The top level type. Contains all persistent state, including layouts and sizes.
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Dock<Leaf> {
pub root: NodeId,
pub nodes: Nodes<Leaf>,
/// Smoothed avaerage of preview
#[serde(skip)]
pub smoothed_preview_rect: Option<Rect>,
}
impl<Leaf> Default for Dock<Leaf> {
fn default() -> Self {
Self {
root: Default::default(),
nodes: Default::default(),
smoothed_preview_rect: None,
}
}
}
impl<Leaf: std::fmt::Debug> std::fmt::Debug for Dock<Leaf> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Print a hiearchical view of the tree:
fn format_node<Leaf: std::fmt::Debug>(
f: &mut std::fmt::Formatter<'_>,
nodes: &Nodes<Leaf>,
indent: usize,
node_id: NodeId,
) -> std::fmt::Result {
write!(f, "{} {node_id:?} ", " ".repeat(indent))?;
if let Some(node) = nodes.get(node_id) {
match node {
Node::Leaf(leaf) => writeln!(f, "Leaf {leaf:?}"),
Node::Branch(branch) => {
writeln!(
f,
"{}",
match branch {
Branch::Tabs(_) => "Tabs",
Branch::Linear(_) => "Linear",
Branch::Grid(_) => "Grid",
}
)?;
for &child in branch.children() {
format_node(f, nodes, indent + 1, child)?;
}
Ok(())
}
}
} else {
write!(f, "DANGLING {node_id:?}")
}
}
writeln!(f, "Dock {{")?;
format_node(f, &self.nodes, 1, self.root)?;
write!(f, "\n}}")
}
}
// ----------------------------------------------------------------------------
// Construction
impl<Leaf> Dock<Leaf> {
pub fn new(root: NodeId, nodes: Nodes<Leaf>) -> Self {
Self {
root,
nodes,
smoothed_preview_rect: None,
}
}
pub fn parent_of(&self, node_id: NodeId) -> Option<NodeId> {
self.nodes
.nodes
.iter()
.find(|(_, node)| {
if let Node::Branch(branch) = node {
branch.children().contains(&node_id)
} else {
false
}
})
.map(|(id, _)| *id)
}
}
impl<Leaf> Nodes<Leaf> {
pub fn try_rect(&self, node_id: NodeId) -> Option<Rect> {
self.rects.get(&node_id).copied()
}
pub fn rect(&self, node_id: NodeId) -> Rect {
let rect = self.try_rect(node_id);
debug_assert!(rect.is_some(), "Failed to find rect for {node_id:?}");
rect.unwrap_or(egui::Rect::from_min_max(Pos2::ZERO, Pos2::ZERO))
}
pub fn get(&self, node_id: NodeId) -> Option<&Node<Leaf>> {
self.nodes.get(&node_id)
}
pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut Node<Leaf>> {
self.nodes.get_mut(&node_id)
}
#[must_use]
pub fn insert_node(&mut self, node: Node<Leaf>) -> NodeId {
let id = NodeId::random();
self.nodes.insert(id, node);
id
}
#[must_use]
pub fn insert_leaf(&mut self, leaf: Leaf) -> NodeId {
self.insert_node(Node::Leaf(leaf))
}
#[must_use]
pub fn insert_branch(&mut self, branch: Branch) -> NodeId {
self.insert_node(Node::Branch(branch))
}
#[must_use]
pub fn insert_tab_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_tabs(children)))
}
#[must_use]
pub fn insert_horizontal_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_linear(
LinearDir::Horizontal,
children,
)))
}
#[must_use]
pub fn insert_vertical_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_linear(
LinearDir::Vertical,
children,
)))
}
#[must_use]
pub fn insert_grid_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_grid(children)))
}
/// Performs no simplifcations!
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) -> GcAction {
if it == remove {
return GcAction::Remove;
}
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Unexpected missing node during removal");
return GcAction::Remove;
};
if let Node::Branch(branch) = &mut node {
branch.retain(|child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep);
}
self.nodes.insert(it, node);
GcAction::Keep
}
fn insert(&mut self, insertion_point: InsertionPoint, child_id: NodeId) {
let InsertionPoint {
parent_id,
insertion,
} = insertion_point;
let Some(mut node) = self.nodes.remove(&parent_id) else {
log::warn!("Failed to insert: could not find parent {parent_id:?}");
return;
};
match insertion {
LayoutInsertion::Tabs(index) => {
if let Node::Branch(Branch::Tabs(tabs)) = &mut node {
let index = index.min(tabs.children.len());
tabs.children.insert(index, child_id);
tabs.active = child_id;
self.nodes.insert(parent_id, node);
} else {
let new_node_id = self.insert_node(node);
let mut tabs = Tabs::new(vec![new_node_id]);
tabs.children.insert(index.min(1), child_id);
tabs.active = child_id;
self.nodes
.insert(parent_id, Node::Branch(Branch::Tabs(tabs)));
}
}
LayoutInsertion::Horizontal(index) => {
if let Node::Branch(Branch::Linear(Linear {
dir: LinearDir::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 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::Linear(Linear {
dir: LinearDir::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 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::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 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)));
}
}
}
}
}
// Usage
impl<Leaf> Dock<Leaf> {
pub fn root(&self) -> NodeId {
self.root
}
/// Show all the leaves in the dock.
pub fn ui(&mut self, behavior: &mut dyn Behavior<Leaf>, ui: &mut Ui) {
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.rects.clear();
// Check if anything is being dragged:
let mut drop_context = DropContext {
enabled: true,
dragged_node_id: self.dragged_id(ui.ctx()),
mouse_pos: ui.input(|i| i.pointer.hover_pos()),
best_dist_sq: f32::INFINITY,
best_insertion: None,
preview_rect: None,
};
self.nodes.layout_node(
ui.style(),
behavior,
ui.available_rect_before_wrap(),
self.root,
);
self.nodes
.node_ui(behavior, &mut drop_context, ui, self.root);
if let (Some(mouse_pos), Some(dragged_node_id)) =
(drop_context.mouse_pos, drop_context.dragged_node_id)
{
ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Grabbing);
// Preview what is being dragged:
egui::Area::new(Id::new((dragged_node_id, "preview")))
.pivot(egui::Align2::CENTER_CENTER)
.current_pos(mouse_pos)
.interactable(false)
.show(ui.ctx(), |ui| {
let mut frame = egui::Frame::popup(ui.style());
frame.fill = frame.fill.gamma_multiply(0.5); // Make see-through
frame.show(ui, |ui| {
// TODO: preview contents
let text = behavior.tab_text_for_node(&self.nodes, dragged_node_id);
ui.label(text);
});
});
if let Some(preview_rect) = drop_context.preview_rect {
let preview_rect = self.smooth_preview_rect(ui.ctx(), preview_rect);
let preview_stroke = ui.visuals().selection.stroke;
let preview_color = preview_stroke.color;
if let Some(insertion_point) = &drop_context.best_insertion {
if let Some(parent_rect) = self.nodes.try_rect(insertion_point.parent_id) {
// Show which parent we will be dropped into
ui.painter().rect_stroke(parent_rect, 1.0, preview_stroke);
}
}
ui.painter().rect(
preview_rect,
1.0,
preview_color.gamma_multiply(0.5),
preview_stroke,
);
let preview_child = false;
if preview_child {
// Preview actual child?
if preview_rect.width() > 32.0 && preview_rect.height() > 32.0 {
if let Some(Node::Leaf(leaf)) = self.nodes.get_mut(dragged_node_id) {
let _ = behavior.leaf_ui(
&mut ui.child_ui(preview_rect, *ui.layout()),
dragged_node_id,
leaf,
);
}
}
}
}
if ui.input(|i| i.pointer.any_released()) {
ui.memory_mut(|mem| mem.stop_dragging());
if let Some(insertion_point) = drop_context.best_insertion {
self.move_node(dragged_node_id, insertion_point);
}
self.smoothed_preview_rect = None;
}
} else {
self.smoothed_preview_rect = None;
}
}
fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect {
let dt = ctx.input(|input| input.stable_dt).at_most(0.1);
let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt);
let smoothed = self.smoothed_preview_rect.get_or_insert(new_rect);
*smoothed = smoothed.lerp_towards(&new_rect, t);
let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max);
if diff < 0.5 {
*smoothed = new_rect;
} else {
ctx.request_repaint();
}
*smoothed
}
fn simplify(&mut self, options: &SimplificationOptions) {
match self.nodes.simplify(options, self.root) {
SimplifyAction::Remove => {
log::warn!("Tried to simplify root node!"); // TODO: handle this
}
SimplifyAction::Keep => {}
SimplifyAction::Replace(new_root) => {
self.root = new_root;
}
}
}
fn move_node(&mut self, moved_node_id: NodeId, insertion_point: InsertionPoint) {
log::debug!(
"Moving {moved_node_id:?} into {:?}",
insertion_point.insertion
);
self.remove_node_id_from_parent(moved_node_id);
self.nodes.insert(insertion_point, moved_node_id);
}
/// Find the currently dragged node, if any.
fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
if !is_possible_drag(ctx) {
// We're not sure we're dragging _at all_ yet.
return None;
}
for &node_id in self.nodes.nodes.keys() {
if node_id == self.root {
continue; // now allowed to drag root
}
let id = node_id.id();
let is_node_being_dragged = ctx.memory(|mem| mem.is_being_dragged(id));
if is_node_being_dragged {
// Abort drags on escape:
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
ctx.memory_mut(|mem| mem.stop_dragging());
return None;
}
return Some(node_id);
}
}
None
}
/// Performs no simplifcations!
fn remove_node_id_from_parent(&mut self, dragged_node_id: NodeId) {
self.nodes
.remove_node_id_from_parent(self.root, dragged_node_id);
}
}

View File

@@ -1,31 +1,57 @@
// # TODO
// * A new ui for each node, nested
// * Styling
// * Per-tab close-buttons
// * Scrolling of tab-bar
// * Adding extra stuff at the end of the tab-bar (e.g. an "Add new tab" button)
// * Vertical tab bar
//! # Dock
//! Tabs that can be dragged around and split up in horizontal, vertical, and grid-layouts.
//!
//! ## Overview
//! The user add leaves to a [`Dock`], arranged using [`Branch`]es.
//! This forms a layout tree.
//! Everything is generic over the type of leaves, leaving up to the user what to store in the tree.
//!
//! Each [`Node]` is either a `Leaf` or a [`Branch`].
//! Each [`Node`] is identified by a (random) [`NodeId`].
//! The nodes are stored in [`Nodes`].
//!
//! The entire state is stored in a single [`Dock`] struct which consists of a [`Nodes`] and a root [`NodeId`].
//!
//! The behavior and the look of the dock is controlled by the [`Behavior`] `trait`.
//! 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).
//!
//! ## Implementation notes
//! In many places we want to recursively visit all noted, while also mutating them.
//! In order to not get into trouble with the borrow checker a trick is used:
//! each [`Node`] is removed, mutated, recursed, and then re-added.
//! You'll see this pattern many times reading the following code.
//!
//! ## Shortcomings
//! We use real recursion, so if your trees get too deep you will get a stack overflow.
//!
//! ## Future improvements
//! * A new ui for each node, nested
//! * Per-tab close-buttons
//! * Scrolling of tab-bar
//! * Vertical tab bar
//! * Auto-grid layouts (re-arange as parent is resized)
use std::collections::{HashMap, HashSet};
use egui::{Id, Key, NumExt, Pos2, Rect, Ui};
use egui::{Id, Pos2, Rect};
mod behavior;
mod branch;
mod dock_struct;
mod nodes;
pub use behavior::Behavior;
pub use branch::{Branch, Grid, GridLoc, Layout, Linear, LinearDir, Tabs};
pub use dock_struct::Dock;
pub use nodes::Nodes;
// ----------------------------------------------------------------------------
// Types required for state
/// An identifier for a node in the dock tree, be it a branch or a leaf.
/// An identifier for a [`Node`] in the dock tree, be it a branch or a leaf.
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct NodeId(u128);
impl NodeId {
pub const ZERO: Self = Self(0);
/// Generate a new random [`NodeId`].
pub fn random() -> Self {
use rand::Rng as _;
Self(rand::thread_rng().gen())
@@ -43,85 +69,9 @@ impl std::fmt::Debug for NodeId {
}
}
/// The top level type. Contains all persistent state, including layouts and sizes.
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Dock<Leaf> {
pub root: NodeId,
pub nodes: Nodes<Leaf>,
/// Smoothed avaerage of preview
#[serde(skip)]
pub smoothed_preview_rect: Option<Rect>,
}
impl<Leaf> Default for Dock<Leaf> {
fn default() -> Self {
Self {
root: Default::default(),
nodes: Default::default(),
smoothed_preview_rect: None,
}
}
}
impl<Leaf: std::fmt::Debug> std::fmt::Debug for Dock<Leaf> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn format_node<Leaf: std::fmt::Debug>(
f: &mut std::fmt::Formatter<'_>,
nodes: &Nodes<Leaf>,
indent: usize,
node_id: NodeId,
) -> std::fmt::Result {
write!(f, "{} {node_id:?} ", " ".repeat(indent))?;
if let Some(node) = nodes.get(node_id) {
match node {
Node::Leaf(leaf) => writeln!(f, "Leaf {leaf:?}"),
Node::Branch(branch) => {
writeln!(
f,
"{}",
match branch {
Branch::Tabs(_) => "Tabs",
Branch::Linear(_) => "Linear",
Branch::Grid(_) => "Grid",
}
)?;
for &child in branch.children() {
format_node(f, nodes, indent + 1, child)?;
}
Ok(())
}
}
} else {
write!(f, "DANGLING {node_id:?}")
}
}
writeln!(f, "Dock {{")?;
format_node(f, &self.nodes, 1, self.root)?;
write!(f, "\n}}")
}
}
/// Contains all node state, but no root.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Nodes<Leaf> {
pub nodes: HashMap<NodeId, Node<Leaf>>,
/// Filled in by the layout step at the start of each frame.
#[serde(default, skip)]
pub rects: HashMap<NodeId, Rect>,
}
impl<Leaf> Default for Nodes<Leaf> {
fn default() -> Self {
Self {
nodes: Default::default(),
rects: Default::default(),
}
}
}
// ----------------------------------------------------------------------------
/// A node in the tree. Either a leaf or a [`Branch`].
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum Node<Leaf> {
Leaf(Leaf),
@@ -137,33 +87,6 @@ impl<Leaf> Node<Leaf> {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LayoutInsertion {
Tabs(usize),
Horizontal(usize),
Vertical(usize),
Grid(GridLoc),
}
#[derive(Clone, Copy, Debug)]
struct InsertionPoint {
parent_id: NodeId,
/// Where in the parent?
insertion: LayoutInsertion,
}
impl InsertionPoint {
fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self {
Self {
parent_id,
insertion,
}
}
}
// ----------------------------------------------------------------------------
#[must_use]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UiResponse {
@@ -202,366 +125,43 @@ pub enum ResizeState {
}
// ----------------------------------------------------------------------------
// Construction
impl<Leaf> Dock<Leaf> {
pub fn new(root: NodeId, nodes: Nodes<Leaf>) -> Self {
Self {
root,
nodes,
smoothed_preview_rect: None,
}
}
pub fn parent_of(&self, node_id: NodeId) -> Option<NodeId> {
self.nodes
.nodes
.iter()
.find(|(_, node)| {
if let Node::Branch(branch) = node {
branch.children().contains(&node_id)
} else {
false
}
})
.map(|(id, _)| *id)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LayoutInsertion {
Tabs(usize),
Horizontal(usize),
Vertical(usize),
Grid(GridLoc),
}
impl<Leaf> Nodes<Leaf> {
pub fn try_rect(&self, node_id: NodeId) -> Option<Rect> {
self.rects.get(&node_id).copied()
}
#[derive(Clone, Copy, Debug)]
struct InsertionPoint {
parent_id: NodeId,
pub fn rect(&self, node_id: NodeId) -> Rect {
let rect = self.try_rect(node_id);
debug_assert!(rect.is_some(), "Failed to find rect for {node_id:?}");
rect.unwrap_or(egui::Rect::from_min_max(Pos2::ZERO, Pos2::ZERO))
}
/// Where in the parent?
insertion: LayoutInsertion,
}
pub fn get(&self, node_id: NodeId) -> Option<&Node<Leaf>> {
self.nodes.get(&node_id)
}
pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut Node<Leaf>> {
self.nodes.get_mut(&node_id)
}
#[must_use]
pub fn insert_node(&mut self, node: Node<Leaf>) -> NodeId {
let id = NodeId::random();
self.nodes.insert(id, node);
id
}
#[must_use]
pub fn insert_leaf(&mut self, leaf: Leaf) -> NodeId {
self.insert_node(Node::Leaf(leaf))
}
#[must_use]
pub fn insert_branch(&mut self, branch: Branch) -> NodeId {
self.insert_node(Node::Branch(branch))
}
#[must_use]
pub fn insert_tab_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_tabs(children)))
}
#[must_use]
pub fn insert_horizontal_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_linear(
LinearDir::Horizontal,
children,
)))
}
#[must_use]
pub fn insert_vertical_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_linear(
LinearDir::Vertical,
children,
)))
}
#[must_use]
pub fn insert_grid_node(&mut self, children: Vec<NodeId>) -> NodeId {
self.insert_node(Node::Branch(Branch::new_grid(children)))
}
/// Performs no simplifcations!
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) -> GcAction {
if it == remove {
return GcAction::Remove;
}
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Unexepcted missing node during removal");
return GcAction::Remove;
};
if let Node::Branch(branch) = &mut node {
branch.retain(|child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep);
}
self.nodes.insert(it, node);
GcAction::Keep
}
fn insert(&mut self, insertion_point: InsertionPoint, child_id: NodeId) {
let InsertionPoint {
impl InsertionPoint {
fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self {
Self {
parent_id,
insertion,
} = insertion_point;
let Some(mut node) = self.nodes.remove(&parent_id) else {
log::warn!("Failed to insert: could not find parent {parent_id:?}");
return;
};
match insertion {
LayoutInsertion::Tabs(index) => {
if let Node::Branch(Branch::Tabs(tabs)) = &mut node {
let index = index.min(tabs.children.len());
tabs.children.insert(index, child_id);
tabs.active = child_id;
self.nodes.insert(parent_id, node);
} else {
let new_node_id = self.insert_node(node);
let mut tabs = Tabs::new(vec![new_node_id]);
tabs.children.insert(index.min(1), child_id);
tabs.active = child_id;
self.nodes
.insert(parent_id, Node::Branch(Branch::Tabs(tabs)));
}
}
LayoutInsertion::Horizontal(index) => {
if let Node::Branch(Branch::Linear(Linear {
dir: LinearDir::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 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::Linear(Linear {
dir: LinearDir::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 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::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 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)));
}
}
}
}
}
// Usage
impl<Leaf> Dock<Leaf> {
pub fn root(&self) -> NodeId {
self.root
}
#[derive(PartialEq, Eq)]
enum GcAction {
Keep,
Remove,
}
/// Show all the leaves in the dock.
pub fn ui(&mut self, behavior: &mut dyn Behavior<Leaf>, ui: &mut Ui) {
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.rects.clear();
// Check if anything is being dragged:
let mut drop_context = DropContext {
enabled: true,
dragged_node_id: self.dragged_id(ui.ctx()),
mouse_pos: ui.input(|i| i.pointer.hover_pos()),
best_dist_sq: f32::INFINITY,
best_insertion: None,
preview_rect: None,
};
self.nodes.layout_node(
ui.style(),
behavior,
ui.available_rect_before_wrap(),
self.root,
);
self.nodes
.node_ui(behavior, &mut drop_context, ui, self.root);
if let (Some(mouse_pos), Some(dragged_node_id)) =
(drop_context.mouse_pos, drop_context.dragged_node_id)
{
ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Grabbing);
// Preview what is being dragged:
egui::Area::new(Id::new((dragged_node_id, "preview")))
.pivot(egui::Align2::CENTER_CENTER)
.current_pos(mouse_pos)
.interactable(false)
.show(ui.ctx(), |ui| {
let mut frame = egui::Frame::popup(ui.style());
frame.fill = frame.fill.gamma_multiply(0.5); // Make see-through
frame.show(ui, |ui| {
// TODO: preview contents
let text = behavior.tab_text_for_node(&self.nodes, dragged_node_id);
ui.label(text);
});
});
if let Some(preview_rect) = drop_context.preview_rect {
let preview_rect = self.smooth_preview_rect(ui.ctx(), preview_rect);
let preview_stroke = ui.visuals().selection.stroke;
let preview_color = preview_stroke.color;
if let Some(insertion_point) = &drop_context.best_insertion {
if let Some(parent_rect) = self.nodes.try_rect(insertion_point.parent_id) {
// Show which parent we will be dropped into
ui.painter().rect_stroke(parent_rect, 1.0, preview_stroke);
}
}
ui.painter().rect(
preview_rect,
1.0,
preview_color.gamma_multiply(0.5),
preview_stroke,
);
let preview_child = false;
if preview_child {
// Preview actual child?
if preview_rect.width() > 32.0 && preview_rect.height() > 32.0 {
if let Some(Node::Leaf(leaf)) = self.nodes.get_mut(dragged_node_id) {
let _ = behavior.leaf_ui(
&mut ui.child_ui(preview_rect, *ui.layout()),
dragged_node_id,
leaf,
);
}
}
}
}
if ui.input(|i| i.pointer.any_released()) {
ui.memory_mut(|mem| mem.stop_dragging());
if let Some(insertion_point) = drop_context.best_insertion {
self.move_node(dragged_node_id, insertion_point);
}
self.smoothed_preview_rect = None;
}
} else {
self.smoothed_preview_rect = None;
}
}
fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect {
let dt = ctx.input(|input| input.stable_dt).at_most(0.1);
let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt);
let smoothed = self.smoothed_preview_rect.get_or_insert(new_rect);
*smoothed = smoothed.lerp_towards(&new_rect, t);
let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max);
if diff < 0.5 {
*smoothed = new_rect;
} else {
ctx.request_repaint();
}
*smoothed
}
fn simplify(&mut self, options: &SimplificationOptions) {
match self.nodes.simplify(options, self.root) {
SimplifyAction::Remove => {
log::warn!("Tried to simplify root node!"); // TODO: handle this
}
SimplifyAction::Keep => {}
SimplifyAction::Replace(new_root) => {
self.root = new_root;
}
}
}
fn move_node(&mut self, moved_node_id: NodeId, insertion_point: InsertionPoint) {
log::debug!(
"Moving {moved_node_id:?} into {:?}",
insertion_point.insertion
);
self.remove_node_id_from_parent(moved_node_id);
self.nodes.insert(insertion_point, moved_node_id);
}
/// Find the currently dragged node, if any.
fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
if !is_possible_drag(ctx) {
// We're not sure we're dragging _at all_ yet.
return None;
}
for &node_id in self.nodes.nodes.keys() {
if node_id == self.root {
continue; // now allowed to drag root
}
let id = node_id.id();
let is_node_being_dragged = ctx.memory(|mem| mem.is_being_dragged(id));
if is_node_being_dragged {
// Abort drags on escape:
if ctx.input(|i| i.key_pressed(Key::Escape)) {
ctx.memory_mut(|mem| mem.stop_dragging());
return None;
}
return Some(node_id);
}
}
None
}
/// Performs no simplifcations!
fn remove_node_id_from_parent(&mut self, dragged_node_id: NodeId) {
self.nodes
.remove_node_id_from_parent(self.root, dragged_node_id);
}
#[must_use]
enum SimplifyAction {
Remove,
Keep,
Replace(NodeId),
}
fn is_possible_drag(ctx: &egui::Context) -> bool {
@@ -577,104 +177,8 @@ fn is_being_dragged(ctx: &egui::Context, node_id: NodeId) -> bool {
}
// ----------------------------------------------------------------------------
// gc
#[derive(PartialEq, Eq)]
enum GcAction {
Keep,
Remove,
}
impl<Leaf> Nodes<Leaf> {
fn gc_root(&mut self, behavior: &mut dyn Behavior<Leaf>, root_id: NodeId) {
let mut visited = HashSet::default();
self.gc_node_id(behavior, &mut visited, root_id);
if visited.len() < self.nodes.len() {
log::warn!(
"GC collecting nodes: {:?}",
self.nodes
.keys()
.filter(|id| !visited.contains(id))
.collect::<Vec<_>>()
);
}
self.nodes.retain(|node_id, _| visited.contains(node_id));
}
fn gc_node_id(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
visited: &mut HashSet<NodeId>,
node_id: NodeId,
) -> GcAction {
let Some(mut node) = self.nodes.remove(&node_id) else { return GcAction::Remove; };
if !visited.insert(node_id) {
log::warn!("Cycle or duplication detected");
return GcAction::Remove;
}
match &mut node {
Node::Leaf(leaf) => {
if !behavior.retain_leaf(leaf) {
return GcAction::Remove;
}
}
Node::Branch(branch) => {
branch.retain(|child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
}
}
self.nodes.insert(node_id, node);
GcAction::Keep
}
}
// ----------------------------------------------------------------------------
// layout
impl<Leaf> Nodes<Leaf> {
fn layout_node(
&mut self,
style: &egui::Style,
behavior: &mut dyn Behavior<Leaf>,
rect: Rect,
node_id: NodeId,
) {
let Some(mut node) = self.nodes.remove(&node_id) else {
log::warn!("Failed to find node {node_id:?} during layout");
return;
};
self.rects.insert(node_id, rect);
if let Node::Branch(branch) = &mut node {
branch.layout(self, style, behavior, rect);
}
self.nodes.insert(node_id, node);
}
}
fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec<f32> {
assert!(!shares.is_empty());
let available_size = available_size - gap_width * (shares.len() - 1) as f32;
let available_size = available_size.at_least(0.0);
let total_share: f32 = shares.iter().sum();
if total_share <= 0.0 {
vec![available_size / shares.len() as f32; shares.len()]
} else {
shares
.iter()
.map(|&share| share / total_share * available_size)
.collect()
}
}
// ----------------------------------------------------------------------------
// ui
pub struct DropContext {
struct DropContext {
enabled: bool,
dragged_node_id: Option<NodeId>,
mouse_pos: Option<Pos2>,
@@ -733,143 +237,3 @@ impl DropContext {
}
}
}
impl<Leaf> Nodes<Leaf> {
fn node_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
node_id: NodeId,
) {
// NOTE: important that we get thr rect and node in two steps,
// otherwise we could loose the node when there is no rect.
let Some(rect) = self.try_rect(node_id) else {
log::warn!("Failed to find rect for node {node_id:?} during ui");
return
};
let Some(mut node) = self.nodes.remove(&node_id) else {
log::warn!("Failed to find node {node_id:?} during ui");
return
};
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.enabled = false;
}
drop_context.on_node(node_id, rect, &node);
drop_context.suggest_rect(
InsertionPoint::new(node_id, LayoutInsertion::Tabs(usize::MAX)),
rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(ui.style()))
.1,
);
let mut ui = egui::Ui::new(
ui.ctx().clone(),
ui.layer_id(),
ui.id().with(node_id),
rect,
rect,
);
match &mut node {
Node::Leaf(leaf) => {
if behavior.leaf_ui(&mut ui, node_id, leaf) == UiResponse::DragStarted {
ui.memory_mut(|mem| mem.set_dragged_id(node_id.id()));
}
}
Node::Branch(branch) => {
branch.ui(self, behavior, drop_context, &mut ui, rect, node_id);
}
};
self.nodes.insert(node_id, node);
drop_context.enabled = drop_context_was_enabled;
}
}
// ----------------------------------------------------------------------------
// Simplification
#[must_use]
enum SimplifyAction {
Remove,
Keep,
Replace(NodeId),
}
impl<Leaf> Nodes<Leaf> {
fn simplify(&mut self, options: &SimplificationOptions, it: NodeId) -> SimplifyAction {
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Failed to find node {it:?} during simplify");
return SimplifyAction::Remove;
};
if let Node::Branch(branch) = &mut node {
// TODO: join nested versions of the same horizontal/vertical layouts
branch.simplify_children(|child| self.simplify(options, child));
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.all_leaves_must_have_tabs
&& 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]);
}
}
} else {
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 {
log::debug!("Simplify: collapsing single-child layout node");
return SimplifyAction::Replace(branch.children()[0]);
}
}
}
self.nodes.insert(it, node);
SimplifyAction::Keep
}
}
impl<Leaf> Nodes<Leaf> {
fn make_all_leaves_children_of_tabs(&mut self, parent_is_tabs: bool, it: NodeId) {
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Failed to find node {it:?} during make_all_leaves_children_of_tabs");
return;
};
match &mut node {
Node::Leaf(_) => {
if !parent_is_tabs {
// Add tabs to this leaf:
log::debug!("Auto-adding Tabs-parent to leaf {it:?}");
let new_id = NodeId::random();
self.nodes.insert(new_id, node);
self.nodes
.insert(it, Node::Branch(Branch::new_tabs(vec![new_id])));
return;
}
}
Node::Branch(branch) => {
let is_tabs = branch.get_layout() == Layout::Tabs;
for &child in branch.children() {
self.make_all_leaves_children_of_tabs(is_tabs, child);
}
}
}
self.nodes.insert(it, node);
}
}

View File

@@ -0,0 +1,222 @@
use std::collections::{HashMap, HashSet};
use egui::{Rect, Ui};
use super::{
Behavior, Branch, DropContext, GcAction, InsertionPoint, Layout, LayoutInsertion, Node, NodeId,
SimplificationOptions, SimplifyAction, UiResponse,
};
/// Contains all node state, but no root.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Nodes<Leaf> {
pub nodes: HashMap<NodeId, Node<Leaf>>,
/// Filled in by the layout step at the start of each frame.
#[serde(default, skip)]
pub rects: HashMap<NodeId, Rect>,
}
impl<Leaf> Default for Nodes<Leaf> {
fn default() -> Self {
Self {
nodes: Default::default(),
rects: Default::default(),
}
}
}
// ----------------------------------------------------------------------------
impl<Leaf> Nodes<Leaf> {
pub(super) fn gc_root(&mut self, behavior: &mut dyn Behavior<Leaf>, root_id: NodeId) {
let mut visited = HashSet::default();
self.gc_node_id(behavior, &mut visited, root_id);
if visited.len() < self.nodes.len() {
log::warn!(
"GC collecting nodes: {:?}",
self.nodes
.keys()
.filter(|id| !visited.contains(id))
.collect::<Vec<_>>()
);
}
self.nodes.retain(|node_id, _| visited.contains(node_id));
}
fn gc_node_id(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
visited: &mut HashSet<NodeId>,
node_id: NodeId,
) -> GcAction {
let Some(mut node) = self.nodes.remove(&node_id) else { return GcAction::Remove; };
if !visited.insert(node_id) {
log::warn!("Cycle or duplication detected");
return GcAction::Remove;
}
match &mut node {
Node::Leaf(leaf) => {
if !behavior.retain_leaf(leaf) {
return GcAction::Remove;
}
}
Node::Branch(branch) => {
branch.retain(|child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
}
}
self.nodes.insert(node_id, node);
GcAction::Keep
}
pub(super) fn layout_node(
&mut self,
style: &egui::Style,
behavior: &mut dyn Behavior<Leaf>,
rect: Rect,
node_id: NodeId,
) {
let Some(mut node) = self.nodes.remove(&node_id) else {
log::warn!("Failed to find node {node_id:?} during layout");
return;
};
self.rects.insert(node_id, rect);
if let Node::Branch(branch) = &mut node {
branch.layout(self, style, behavior, rect);
}
self.nodes.insert(node_id, node);
}
pub(super) fn node_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
node_id: NodeId,
) {
// NOTE: important that we get thr rect and node in two steps,
// otherwise we could loose the node when there is no rect.
let Some(rect) = self.try_rect(node_id) else {
log::warn!("Failed to find rect for node {node_id:?} during ui");
return
};
let Some(mut node) = self.nodes.remove(&node_id) else {
log::warn!("Failed to find node {node_id:?} during ui");
return
};
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.enabled = false;
}
drop_context.on_node(node_id, rect, &node);
drop_context.suggest_rect(
InsertionPoint::new(node_id, LayoutInsertion::Tabs(usize::MAX)),
rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(ui.style()))
.1,
);
let mut ui = egui::Ui::new(
ui.ctx().clone(),
ui.layer_id(),
ui.id().with(node_id),
rect,
rect,
);
match &mut node {
Node::Leaf(leaf) => {
if behavior.leaf_ui(&mut ui, node_id, leaf) == UiResponse::DragStarted {
ui.memory_mut(|mem| mem.set_dragged_id(node_id.id()));
}
}
Node::Branch(branch) => {
branch.ui(self, behavior, drop_context, &mut ui, rect, node_id);
}
};
self.nodes.insert(node_id, node);
drop_context.enabled = drop_context_was_enabled;
}
pub(super) fn simplify(
&mut self,
options: &SimplificationOptions,
it: NodeId,
) -> SimplifyAction {
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Failed to find node {it:?} during simplify");
return SimplifyAction::Remove;
};
if let Node::Branch(branch) = &mut node {
// TODO: join nested versions of the same horizontal/vertical layouts
branch.simplify_children(|child| self.simplify(options, child));
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.all_leaves_must_have_tabs
&& 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]);
}
}
} else {
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 {
log::debug!("Simplify: collapsing single-child layout node");
return SimplifyAction::Replace(branch.children()[0]);
}
}
}
self.nodes.insert(it, node);
SimplifyAction::Keep
}
pub(super) fn make_all_leaves_children_of_tabs(&mut self, parent_is_tabs: bool, it: NodeId) {
let Some(mut node) = self.nodes.remove(&it) else {
log::warn!("Failed to find node {it:?} during make_all_leaves_children_of_tabs");
return;
};
match &mut node {
Node::Leaf(_) => {
if !parent_is_tabs {
// Add tabs to this leaf:
log::debug!("Auto-adding Tabs-parent to leaf {it:?}");
let new_id = NodeId::random();
self.nodes.insert(new_id, node);
self.nodes
.insert(it, Node::Branch(Branch::new_tabs(vec![new_id])));
return;
}
}
Node::Branch(branch) => {
let is_tabs = branch.get_layout() == Layout::Tabs;
for &child in branch.children() {
self.make_all_leaves_children_of_tabs(is_tabs, child);
}
}
}
self.nodes.insert(it, node);
}
}