1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00
Files
egui/crates/egui_extras/src/dock/mod.rs
2023-05-02 19:30:59 +02:00

889 lines
28 KiB
Rust

// # TODO
// * A new ui for each node, nested
// * Better drag-and-drop around the "empty" drag source
// * Resizing of vertical layouts and grids
// * Styling
// * Handle rects without a lot of unwraps
use std::collections::{HashMap, HashSet};
use egui::{Id, Key, NumExt, Pos2, Rect, Response, Sense, TextStyle, Ui, WidgetText};
mod branch;
pub use branch::{Branch, Grid, GridLoc, Layout, Linear, LinearDir, Tabs};
// ----------------------------------------------------------------------------
// Types required for state
/// 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);
pub fn random() -> Self {
use rand::Rng as _;
Self(rand::thread_rng().gen())
}
/// Corresponding [`egui::Id`], used for dragging.
pub fn id(&self) -> Id {
Id::new(self)
}
}
impl std::fmt::Debug for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:08X}", self.0 as u32)
}
}
/// The top level type. Contains all peristent state, including layouts and sizes.
#[derive(Clone, Debug, 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,
}
}
}
/// 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(),
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum Node<Leaf> {
Leaf(Leaf),
Branch(Branch),
}
impl<Leaf> Node<Leaf> {
fn layout(&self) -> Option<Layout> {
match self {
Node::Leaf(_) => None,
Node::Branch(branch) => Some(branch.get_layout()),
}
}
}
#[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 {
None,
/// The viewer is being dragged via some element in the Leaf
DragStarted,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SimplificationOptions {
pub prune_empty_tabs: bool,
pub prune_single_child_tabs: bool,
pub prune_empty_layouts: bool,
pub prune_single_child_layouts: bool,
pub all_leaves_must_have_tabs: bool,
}
impl Default for SimplificationOptions {
fn default() -> Self {
Self {
prune_empty_tabs: true,
prune_single_child_tabs: true,
prune_empty_layouts: true,
prune_single_child_layouts: true,
all_leaves_must_have_tabs: false,
}
}
}
/// Trait defining how the [`Dock`] and its leaf should be shown.
pub trait Behavior<Leaf> {
/// Show this leaf node in the given [`egui::Ui`].
///
/// If this is an unknown node, return [`NodeAction::Remove`] and the node will be removed.
fn leaf_ui(&mut self, _ui: &mut Ui, _node_id: NodeId, _leaf: &mut Leaf) -> UiResponse;
fn tab_text_for_leaf(&mut self, leaf: &Leaf) -> WidgetText;
fn tab_text_for_node(&mut self, nodes: &Nodes<Leaf>, node_id: NodeId) -> WidgetText {
match &nodes.nodes[&node_id] {
Node::Leaf(leaf) => self.tab_text_for_leaf(leaf),
Node::Branch(branch) => format!("{:?}", branch.get_layout()).into(),
}
}
fn tab_ui(
&mut self,
nodes: &Nodes<Leaf>,
ui: &mut Ui,
id: Id,
node_id: NodeId,
selected: bool,
is_being_dragged: bool,
) -> Response {
let text = self.tab_text_for_node(nodes, node_id);
let font_id = TextStyle::Button.resolve(ui.style());
let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
let (_, rect) = ui.allocate_space(galley.size());
let response = ui.interact(rect, id, Sense::click_and_drag());
let widget_style = ui.style().interact_selectable(&response, selected);
// Show a gap when dragged
if ui.is_rect_visible(rect) && !is_being_dragged {
if selected {
ui.painter().rect_filled(rect, 0.0, widget_style.bg_fill);
}
ui.painter()
.galley_with_color(rect.min, galley.galley, widget_style.text_color());
}
response
}
/// Returns `false` if this leaf should be removed from its parent.
fn retain_leaf(&mut self, _leaf: &Leaf) -> bool {
true
}
// ---
// Settings:
/// The height of the bar holding tab names.
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
20.0
}
/// Width of the gap between nodes in a horizontal or vertical layout
fn gap_width(&self, _style: &egui::Style) -> f32 {
1.0
}
// No child should shrink below this size
fn min_size(&self) -> f32 {
32.0
}
fn simplification_options(&self) -> SimplificationOptions {
SimplificationOptions::default()
}
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> egui::Stroke {
match resize_state {
ResizeState::Idle => egui::Stroke::NONE, // Let the gap speak for itself
ResizeState::Hovering => style.visuals.widgets.hovered.fg_stroke,
ResizeState::Dragging => style.visuals.widgets.active.fg_stroke,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResizeState {
Idle,
Hovering,
Dragging,
}
// ----------------------------------------------------------------------------
// Construction
impl<Leaf> Dock<Leaf> {
pub fn new(root: NodeId, nodes: Nodes<Leaf>) -> Self {
Self {
root,
nodes,
smoothed_preview_rect: None,
}
}
}
impl<Leaf> Nodes<Leaf> {
pub fn rect(&self, node_id: NodeId) -> Option<Rect> {
self.rects.get(&node_id).copied()
}
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_branch(&mut self, branch: Branch) -> NodeId {
self.insert_node(Node::Branch(branch))
}
#[must_use]
pub fn insert_leaf(&mut self, leaf: Leaf) -> NodeId {
self.insert_node(Node::Leaf(leaf))
}
#[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)))
}
fn parent(&self, it: NodeId, needle_child: NodeId) -> Option<NodeId> {
match &self.nodes.get(&it)? {
Node::Leaf(_) => None,
Node::Branch(branch) => {
for &child in branch.children() {
if child == needle_child {
return Some(it);
}
if let Some(parent) = self.parent(child, needle_child) {
return Some(parent);
}
}
None
}
}
}
/// 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 { 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);
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);
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,
&mut drop_context,
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.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);
}
}
fn is_possible_drag(ctx: &egui::Context) -> bool {
ctx.input(|input| {
!input.pointer.any_pressed()
&& !input.pointer.could_any_button_be_click()
&& !input.pointer.any_click()
})
}
fn is_being_dragged(ctx: &egui::Context, node_id: NodeId) -> bool {
ctx.memory(|mem| mem.is_being_dragged(node_id.id())) && is_possible_drag(ctx)
}
// ----------------------------------------------------------------------------
// 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>,
drop_context: &mut DropContext,
rect: Rect,
node_id: NodeId,
) {
let Some(mut node) = self.nodes.remove(&node_id) else { return; };
self.rects.insert(node_id, rect);
if let Node::Branch(branch) = &mut node {
branch.layout(self, style, behavior, drop_context, rect, node_id);
}
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 {
enabled: bool,
dragged_node_id: Option<NodeId>,
mouse_pos: Option<Pos2>,
best_insertion: Option<InsertionPoint>,
best_dist_sq: f32,
preview_rect: Option<Rect>,
}
impl DropContext {
fn on_node<Leaf>(&mut self, parent_id: NodeId, rect: Rect, node: &Node<Leaf>) {
if !self.enabled {
return;
}
if node.layout() != Some(Layout::Horizontal) {
self.suggest_rect(
InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(0)),
rect.split_left_right_at_fraction(0.5).0,
);
self.suggest_rect(
InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(usize::MAX)),
rect.split_left_right_at_fraction(0.5).1,
);
}
if node.layout() != Some(Layout::Vertical) {
self.suggest_rect(
InsertionPoint::new(parent_id, LayoutInsertion::Vertical(0)),
rect.split_top_bottom_at_fraction(0.5).0,
);
self.suggest_rect(
InsertionPoint::new(parent_id, LayoutInsertion::Vertical(usize::MAX)),
rect.split_top_bottom_at_fraction(0.5).1,
);
}
// self.suggest_rect(InsertionPoint::new(parent_id, LayoutType::Tabs, 1), rect);
}
fn suggest_rect(&mut self, insertion: InsertionPoint, preview_rect: Rect) {
self.suggest_point(insertion, preview_rect.center(), preview_rect);
}
fn suggest_point(&mut self, insertion: InsertionPoint, target_point: Pos2, preview_rect: Rect) {
if !self.enabled {
return;
}
if let Some(mouse_pos) = self.mouse_pos {
let dist_sq = mouse_pos.distance_sq(target_point);
if dist_sq < self.best_dist_sq {
self.best_dist_sq = dist_sq;
self.best_insertion = Some(insertion);
self.preview_rect = Some(preview_rect);
}
}
}
}
impl<Leaf> Nodes<Leaf> {
fn node_ui(
&mut self,
behavior: &mut dyn Behavior<Leaf>,
drop_context: &mut DropContext,
ui: &mut Ui,
node_id: NodeId,
) {
let (Some(rect), Some(mut node)) = (self.rect(node_id), self.nodes.remove(&node_id)) else { 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 { 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 { 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);
}
}