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:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
432
crates/egui_extras/src/dock/dock_struct.rs
Normal file
432
crates/egui_extras/src/dock/dock_struct.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
222
crates/egui_extras/src/dock/nodes.rs
Normal file
222
crates/egui_extras/src/dock/nodes.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user