From abd8cefb784098b15fc3d13810509ad2c1818b32 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 May 2023 12:03:27 +0200 Subject: [PATCH] Remove dock stuff --- Cargo.lock | 16 - crates/egui_extras/Cargo.toml | 6 - crates/egui_extras/src/dock/behavior.rs | 209 -------- crates/egui_extras/src/dock/branch/grid.rs | 442 ----------------- crates/egui_extras/src/dock/branch/linear.rs | 471 ------------------- crates/egui_extras/src/dock/branch/mod.rs | 188 -------- crates/egui_extras/src/dock/branch/tabs.rs | 164 ------- crates/egui_extras/src/dock/dock_struct.rs | 271 ----------- crates/egui_extras/src/dock/mod.rs | 261 ---------- crates/egui_extras/src/dock/nodes.rs | 358 -------------- crates/egui_extras/src/lib.rs | 1 - examples/dock/Cargo.toml | 19 - examples/dock/README.md | 7 - examples/dock/src/main.rs | 286 ----------- 14 files changed, 2699 deletions(-) delete mode 100644 crates/egui_extras/src/dock/behavior.rs delete mode 100644 crates/egui_extras/src/dock/branch/grid.rs delete mode 100644 crates/egui_extras/src/dock/branch/linear.rs delete mode 100644 crates/egui_extras/src/dock/branch/mod.rs delete mode 100644 crates/egui_extras/src/dock/branch/tabs.rs delete mode 100644 crates/egui_extras/src/dock/dock_struct.rs delete mode 100644 crates/egui_extras/src/dock/mod.rs delete mode 100644 crates/egui_extras/src/dock/nodes.rs delete mode 100644 examples/dock/Cargo.toml delete mode 100644 examples/dock/README.md delete mode 100644 examples/dock/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a06d657ce..df8f811ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1128,17 +1128,6 @@ dependencies = [ "libloading 0.7.4", ] -[[package]] -name = "dock" -version = "0.1.0" -dependencies = [ - "eframe", - "egui_extras", - "env_logger", - "log", - "serde", -] - [[package]] name = "document-features" version = "0.2.7" @@ -1330,11 +1319,8 @@ dependencies = [ "chrono", "document-features", "egui", - "getrandom", "image", - "itertools", "log", - "rand", "resvg", "serde", "tiny-skia", @@ -1706,10 +1692,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 82c3e3d99..75d4e9b2a 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -36,13 +36,7 @@ svg = ["resvg", "tiny-skia", "usvg"] [dependencies] egui = { version = "0.21.0", path = "../egui", default-features = false } -# For dock: -# required to make rand work on wasm, see https://github.com/rust-random/rand#wasm-support -getrandom = { version = "0.2", features = ["js"] } -itertools = "0.10" log = { version = "0.4", features = ["std"] } -rand = { version = "0.8.5", features = ["getrandom", "small_rng"] } - serde = { version = "1", features = ["derive"] } #! ### Optional dependencies diff --git a/crates/egui_extras/src/dock/behavior.rs b/crates/egui_extras/src/dock/behavior.rs deleted file mode 100644 index dca8809f8..000000000 --- a/crates/egui_extras/src/dock/behavior.rs +++ /dev/null @@ -1,209 +0,0 @@ -use egui::{ - vec2, Color32, Id, Rect, Response, Rgba, Sense, Stroke, TextStyle, Ui, Visuals, WidgetText, -}; - -use super::{Node, NodeId, Nodes, ResizeState, SimplificationOptions, UiResponse}; - -/// Trait defining how the [`Dock`] and its leaf should be shown. -pub trait Behavior { - /// Show this leaf node in the given [`egui::Ui`]. - /// - /// If this is an unknown node, return [`NodeAction::Remove`] and the node will be removed. - /// - /// You can make the leaf draggable by returning [`UiResponse::DragStarted`] - /// when the user drags some handle. - fn leaf_ui(&mut self, _ui: &mut Ui, _node_id: NodeId, _leaf: &mut Leaf) -> UiResponse; - - /// The title of a leaf tab. - fn tab_title_for_leaf(&mut self, leaf: &Leaf) -> WidgetText; - - /// The title of a general tab. - /// - /// The default implementation uses the name of the layout for branches, and - /// calls [`Self::tab_text_for_leaf`] for leaves. - fn tab_title_for_node(&mut self, nodes: &Nodes, node_id: NodeId) -> WidgetText { - if let Some(node) = nodes.nodes.get(&node_id) { - match node { - Node::Leaf(leaf) => self.tab_title_for_leaf(leaf), - Node::Branch(branch) => format!("{:?}", branch.layout()).into(), - } - } else { - "MISSING NODE".into() - } - } - - /// Show the title of a tab as a button. - /// - /// You can override the default implementation to add e.g. a close button. - fn tab_ui( - &mut self, - nodes: &Nodes, - ui: &mut Ui, - id: Id, - node_id: NodeId, - active: bool, - is_being_dragged: bool, - ) -> Response { - let text = self.tab_title_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 x_margin = self.tab_title_spacing(ui.visuals()); - let (_, rect) = ui.allocate_space(vec2( - galley.size().x + 2.0 * x_margin, - ui.available_height(), - )); - let response = ui.interact(rect, id, Sense::click_and_drag()); - - // Show a gap when dragged - if ui.is_rect_visible(rect) && !is_being_dragged { - let bg_color = self.tab_bg_color(ui.visuals(), node_id, active); - let stroke = self.tab_outline_stroke(ui.visuals(), node_id, active); - ui.painter().rect(rect.shrink(0.5), 0.0, bg_color, stroke); - - if active { - // Make the tab name area connect with the tab ui area: - ui.painter().hline( - rect.x_range(), - rect.bottom(), - Stroke::new(stroke.width + 1.0, bg_color), - ); - } - - let text_color = self.tab_text_color(ui.visuals(), node_id, active); - ui.painter().galley_with_color( - egui::Align2::CENTER_CENTER - .align_size_within_rect(galley.size(), rect) - .min, - galley.galley, - text_color, - ); - } - - response - } - - /// Returns `false` if this leaf should be removed from its parent. - fn retain_leaf(&mut self, _leaf: &Leaf) -> bool { - true - } - - /// Adds some UI to the top right of each tab bar. - /// - /// You can use this to, for instance, add a button for adding new tabs. - /// - /// The widgets will be added right-to-left. - fn top_bar_rtl_ui(&mut self, _ui: &mut Ui, _node_id: NodeId) { - // if ui.button("➕").clicked() { - // } - } - - // -------- - // Settings: - - /// The height of the bar holding tab titles. - fn tab_bar_height(&self, _style: &egui::Style) -> f32 { - 24.0 - } - - /// Width of the gap between nodes in a horizontal or vertical layout, - /// and between rows/columns in a grid layout. - fn gap_width(&self, _style: &egui::Style) -> f32 { - 1.0 - } - - /// No child should shrink below this width nor height. - fn min_size(&self) -> f32 { - 32.0 - } - - /// What are the rules for simplifying the tree? - fn simplification_options(&self) -> SimplificationOptions { - SimplificationOptions::default() - } - - /// The stroke used for the lines in horizontal, vertical, and grid layouts. - 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, - } - } - - /// Extra spacing to left and right of tab titles. - fn tab_title_spacing(&self, _visuals: &Visuals) -> f32 { - 8.0 - } - - /// The background color of the tab bar. - fn tab_bar_color(&self, visuals: &Visuals) -> Color32 { - if visuals.dark_mode { - Color32::BLACK - } else { - (Rgba::from(visuals.window_fill()) * Rgba::from_gray(0.8)).into() - } - } - - /// The background color of a tab. - fn tab_bg_color(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Color32 { - if active { - visuals.window_fill() // same as the tab contents - } else { - Color32::TRANSPARENT // fade into background - } - } - - /// Stroke of the outline around a tab title. - fn tab_outline_stroke(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Stroke { - if active { - Stroke::new(1.0, visuals.widgets.active.bg_fill) - } else { - Stroke::NONE - } - } - - /// Stroke of the line separating the tab title bar and the content of the active tab. - fn tab_bar_hline_stroke(&self, visuals: &Visuals) -> Stroke { - Stroke::new(1.0, visuals.widgets.noninteractive.bg_stroke.color) - } - - /// The color of the title text of the tab. - fn tab_text_color(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Color32 { - if active { - visuals.widgets.active.text_color() - } else { - visuals.widgets.noninteractive.text_color() - } - } - - /// When drag-and-dropping a node, how do we preview what is about to happen? - fn paint_drag_preview( - &self, - visuals: &Visuals, - painter: &egui::Painter, - parent_rect: Option, - preview_rect: Rect, - ) { - let preview_stroke = visuals.selection.stroke; - let preview_color = preview_stroke.color; - - if let Some(parent_rect) = parent_rect { - // Show which parent we will be dropped into - painter.rect_stroke(parent_rect, 1.0, preview_stroke); - } - - painter.rect( - preview_rect, - 1.0, - preview_color.gamma_multiply(0.5), - preview_stroke, - ); - } - - /// Show we preview leaves that are being dragged, - /// i.e. show their ui in the region where they will end up? - fn preview_dragged_leaves(&self) -> bool { - false - } -} diff --git a/crates/egui_extras/src/dock/branch/grid.rs b/crates/egui_extras/src/dock/branch/grid.rs deleted file mode 100644 index 42bad12cd..000000000 --- a/crates/egui_extras/src/dock/branch/grid.rs +++ /dev/null @@ -1,442 +0,0 @@ -use std::collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet}; - -use egui::{emath::Rangef, pos2, vec2, NumExt as _, Rect}; -use itertools::Itertools as _; - -use crate::dock::{ - Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes, ResizeState, -}; - -/// Where in a grid? -#[derive( - Clone, - Copy, - Debug, - Default, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - serde::Serialize, - serde::Deserialize, -)] -pub struct GridLoc { - // Row first for sorting - pub row: usize, - pub col: usize, -} - -impl GridLoc { - #[inline] - pub fn from_col_row(col: usize, row: usize) -> Self { - Self { col, row } - } -} - -#[derive( - Clone, - Copy, - Debug, - Default, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - serde::Serialize, - serde::Deserialize, -)] -pub enum GridLayout { - /// Place children in a grid, with a dynamic number of columns and rows. - /// Resizing the window may change the number of columns and rows. - #[default] - Auto, - - /// Place children in a grid with this many columns, - /// and as many rows as needed. - Columns(usize), -} - -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Grid { - pub children: Vec, - - pub layout: GridLayout, - - /// Where each child is located. - /// - /// If the chils is missing from this set, it will be assgined a location during layout. - pub locations: HashMap, - - /// Share of the available width assigned to each column. - pub col_shares: Vec, - /// Share of the available height assigned to each row. - pub row_shares: Vec, - - /// ui point x ranges for each column, recomputed during layout - #[serde(skip)] - col_ranges: Vec, - - /// ui point y ranges for each row, recomputed during layout - #[serde(skip)] - row_ranges: Vec, -} - -impl Grid { - pub fn new(children: Vec) -> Self { - Self { - children, - ..Default::default() - } - } - - pub fn add_child(&mut self, child: NodeId) { - self.children.push(child); - } - - pub fn layout( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - let gap = behavior.gap_width(style); - let child_ids: HashSet = self.children.iter().copied().collect(); - - let num_cols = match self.layout { - GridLayout::Auto => num_columns_heuristic(self.children.len(), rect, gap), - GridLayout::Columns(num_columns) => num_columns.at_least(1), - }; - let num_rows = (self.children.len() + num_cols - 1) / num_cols; - - // Where to place each node? - let mut node_id_from_location: BTreeMap = Default::default(); - self.locations.retain(|&child_id, &mut loc| { - if child_ids.contains(&child_id) { - match node_id_from_location.entry(loc) { - btree_map::Entry::Occupied(_) => { - false // two nodes assigned to the same position - forget this one for now - } - btree_map::Entry::Vacant(entry) => { - if num_cols <= loc.col || num_rows <= loc.row { - false // out of bounds - } else { - entry.insert(child_id); - true - } - } - } - } else { - false // child no longer exists - } - }); - - // Find location for nodes that don't have one yet - let mut next_pos = 0; - for &child_id in &self.children { - if let hash_map::Entry::Vacant(entry) = self.locations.entry(child_id) { - // find a position: - loop { - let loc = GridLoc::from_col_row(next_pos % num_cols, next_pos / num_cols); - if node_id_from_location.contains_key(&loc) { - next_pos += 1; - continue; - } - entry.insert(loc); - node_id_from_location.insert(loc, child_id); - break; - } - } - } - - // Everything has a location - now we know how many rows we have: - let num_rows = node_id_from_location.keys().last().unwrap().row + 1; - - // Figure out where each column and row goes: - self.col_shares.resize(num_cols, 1.0); - self.row_shares.resize(num_rows, 1.0); - - let col_widths = sizes_from_shares(&self.col_shares, rect.width(), gap); - let row_heights = sizes_from_shares(&self.row_shares, rect.height(), gap); - - { - let mut x = rect.left(); - self.col_ranges.clear(); - for &width in &col_widths { - self.col_ranges.push(Rangef::new(x, x + width)); - x += width + gap; - } - } - { - let mut y = rect.top(); - self.row_ranges.clear(); - for &height in &row_heights { - self.row_ranges.push(Rangef::new(y, y + height)); - y += height + gap; - } - } - - // Each child now has a location. Use this to order them, in case we will ater do auto-layouts: - self.children.sort_by_key(|&child| self.locations[&child]); - - // Place each child: - for &child in &self.children { - let loc = self.locations[&child]; - let child_rect = - Rect::from_x_y_ranges(self.col_ranges[loc.col], self.row_ranges[loc.row]); - nodes.layout_node(style, behavior, child_rect, child); - } - } - - pub(super) fn ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - node_id: NodeId, - ) { - for &child in &self.children { - nodes.node_ui(behavior, drop_context, ui, child); - } - - // Register drop-zones: - for (col, &x_range) in self.col_ranges.iter().enumerate() { - for (row, &y_range) in self.row_ranges.iter().enumerate() { - let cell_rect = Rect::from_x_y_ranges(x_range, y_range); - drop_context.suggest_rect( - InsertionPoint::new( - node_id, - LayoutInsertion::Grid(GridLoc::from_col_row(col, row)), - ), - cell_rect, - ); - } - } - - self.resize_columns(nodes, behavior, ui, node_id); - self.resize_rows(nodes, behavior, ui, node_id); - } - - fn resize_columns( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - ui: &mut egui::Ui, - parent_id: NodeId, - ) { - let parent_rect = nodes.rect(parent_id); - for (i, (left, right)) in self.col_ranges.iter().copied().tuple_windows().enumerate() { - let resize_id = egui::Id::new((parent_id, "resize_col", i)); - - let x = egui::lerp(left.max..=right.min, 0.5); - - let mut resize_state = ResizeState::Idle; - if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let line_rect = Rect::from_center_size( - pos2(x, parent_rect.center().y), - vec2( - 2.0 * ui.style().interaction.resize_grab_radius_side, - parent_rect.height(), - ), - ); - let response = ui.interact(line_rect, resize_id, egui::Sense::click_and_drag()); - resize_state = resize_interaction( - behavior, - &self.col_ranges, - &mut self.col_shares, - &response, - ui.painter().round_to_pixel(pointer.x) - x, - i, - ); - - if resize_state != ResizeState::Idle { - ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); - } - } - - let stroke = behavior.resize_stroke(ui.style(), resize_state); - ui.painter().vline(x, parent_rect.y_range(), stroke); - } - } - - fn resize_rows( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - ui: &mut egui::Ui, - parent_id: NodeId, - ) { - let parent_rect = nodes.rect(parent_id); - for (i, (top, bottom)) in self.row_ranges.iter().copied().tuple_windows().enumerate() { - let resize_id = egui::Id::new((parent_id, "resize_row", i)); - - let y = egui::lerp(top.max..=bottom.min, 0.5); - - let mut resize_state = ResizeState::Idle; - if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let line_rect = Rect::from_center_size( - pos2(parent_rect.center().x, y), - vec2( - parent_rect.width(), - 2.0 * ui.style().interaction.resize_grab_radius_side, - ), - ); - let response = ui.interact(line_rect, resize_id, egui::Sense::click_and_drag()); - resize_state = resize_interaction( - behavior, - &self.row_ranges, - &mut self.row_shares, - &response, - ui.painter().round_to_pixel(pointer.y) - y, - i, - ); - - if resize_state != ResizeState::Idle { - ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical); - } - } - - let stroke = behavior.resize_stroke(ui.style(), resize_state); - ui.painter().hline(parent_rect.x_range(), y, stroke); - } - } -} - -/// How many columns should we use to fit `n` children in a grid? -fn num_columns_heuristic(n: usize, rect: Rect, gap: f32) -> usize { - let desired_aspect = 4.0 / 3.0; - - let mut best_loss = f32::INFINITY; - let mut best_num_columns = 1; - - for ncols in 1..=n { - let nrows = (n + ncols - 1) / ncols; - - let cell_width = (rect.width() - gap * (ncols as f32 - 1.0)) / (ncols as f32); - let cell_height = (rect.height() - gap * (nrows as f32 - 1.0)) / (nrows as f32); - - let cell_aspect = cell_width / cell_height; - let aspect_diff = (desired_aspect - cell_aspect).abs(); - let num_empty_cells = ncols * nrows - n; - - let loss = aspect_diff + 0.1 * num_empty_cells as f32; // TODO(emilk): weight differently? - - if loss < best_loss { - best_loss = loss; - best_num_columns = ncols; - } - } - - best_num_columns -} - -fn resize_interaction( - behavior: &mut dyn Behavior, - ranges: &[Rangef], - shares: &mut [f32], - splitter_response: &egui::Response, - dx: f32, - i: usize, -) -> ResizeState { - assert_eq!(ranges.len(), shares.len()); - let num = ranges.len(); - let node_width = |i: usize| ranges[i].span(); - - let left = i; - let right = i + 1; - - if splitter_response.double_clicked() { - // double-click to center the split between left and right: - let mean = 0.5 * (shares[left] + shares[right]); - shares[left] = mean; - shares[right] = mean; - ResizeState::Hovering - } else if splitter_response.dragged() { - if dx < 0.0 { - // Expand right, shrink stuff to the left: - shares[right] += shrink_shares( - behavior, - shares, - &(0..=i).rev().collect_vec(), - dx.abs(), - node_width, - ); - } else { - // Expand the left, shrink stuff to the right: - shares[left] += shrink_shares( - behavior, - shares, - &(i + 1..num).collect_vec(), - dx.abs(), - node_width, - ); - } - ResizeState::Dragging - } else if splitter_response.hovered() { - ResizeState::Hovering - } else { - ResizeState::Idle - } -} - -/// Try shrink the children by a total of `target_in_points`, -/// making sure no child gets smaller than its minimum size. -fn shrink_shares( - behavior: &dyn Behavior, - shares: &mut [f32], - children: &[usize], - target_in_points: f32, - size_in_point: impl Fn(usize) -> f32, -) -> f32 { - if children.is_empty() { - return 0.0; - } - - let mut total_shares = 0.0; - let mut total_points = 0.0; - for &child in children { - total_shares += shares[child]; - total_points += size_in_point(child); - } - - let shares_per_point = total_shares / total_points; - - let min_size_in_points = shares_per_point * behavior.min_size(); - - let target_in_shares = shares_per_point * target_in_points; - let mut total_shares_lost = 0.0; - - for &child in children { - let share = &mut shares[child]; - let shrink_by = (target_in_shares - total_shares_lost) - .min(*share - min_size_in_points) - .max(0.0); - - *share -= shrink_by; - total_shares_lost += shrink_by; - } - - total_shares_lost -} - -fn sizes_from_shares(shares: &[f32], available_size: f32, gap_width: f32) -> Vec { - if shares.is_empty() { - return vec![]; - } - - let available_size = available_size - gap_width * (shares.len() - 1) as f32; - let available_size = available_size.at_least(0.0); - - 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() - } -} diff --git a/crates/egui_extras/src/dock/branch/linear.rs b/crates/egui_extras/src/dock/branch/linear.rs deleted file mode 100644 index 197a9edb0..000000000 --- a/crates/egui_extras/src/dock/branch/linear.rs +++ /dev/null @@ -1,471 +0,0 @@ -use std::collections::HashMap; - -use egui::{pos2, vec2, NumExt, Rect}; -use itertools::Itertools as _; - -use crate::dock::{ - is_being_dragged, Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes, - ResizeState, -}; - -// ---------------------------------------------------------------------------- - -/// How large of a share of space each child has, on a 1D axis. -/// -/// Used for [`Linear`] layouts (horizontal and vertical). -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Shares { - /// How large of a share each child has. - /// - /// For instance, the shares `[1, 2, 3]` means that the first child gets 1/6 of the space, - /// the second gets 2/6 and the third gets 3/6. - shares: HashMap, -} - -impl Shares { - pub fn replace_with(&mut self, remove: NodeId, new: NodeId) { - if let Some(share) = self.shares.remove(&remove) { - self.shares.insert(new, share); - } - } - - /// Split the given width based on the share of the children. - pub fn split(&self, children: &[NodeId], available_width: f32) -> Vec { - let mut num_shares = 0.0; - for &child in children { - num_shares += self[child]; - } - if num_shares == 0.0 { - num_shares = 1.0; - } - children - .iter() - .map(|&child| available_width * self[child] / num_shares) - .collect() - } -} - -impl std::ops::Index for Shares { - type Output = f32; - - #[inline] - fn index(&self, id: NodeId) -> &Self::Output { - self.shares.get(&id).unwrap_or(&1.0) - } -} - -impl std::ops::IndexMut for Shares { - #[inline] - fn index_mut(&mut self, id: NodeId) -> &mut Self::Output { - self.shares.entry(id).or_insert(1.0) - } -} - -// ---------------------------------------------------------------------------- - -#[derive(Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] -pub enum LinearDir { - #[default] - Horizontal, - Vertical, -} - -/// Horizontal or vertical layout. -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Linear { - pub children: Vec, - pub dir: LinearDir, - pub shares: Shares, -} - -impl Linear { - pub fn new(dir: LinearDir, children: Vec) -> Self { - Self { - children, - dir, - ..Default::default() - } - } - - pub fn add_child(&mut self, child: NodeId) { - self.children.push(child); - } - - pub fn layout( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - match self.dir { - LinearDir::Horizontal => { - self.layout_horizontal(nodes, style, behavior, rect); - } - LinearDir::Vertical => self.layout_vertical(nodes, style, behavior, rect), - } - } - - fn layout_horizontal( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - let num_gaps = self.children.len().saturating_sub(1); - let gap_width = behavior.gap_width(style); - let total_gap_width = gap_width * num_gaps as f32; - let available_width = (rect.width() - total_gap_width).at_least(0.0); - - let widths = self.shares.split(&self.children, available_width); - - let mut x = rect.min.x; - for (child, width) in self.children.iter().zip(widths) { - let child_rect = Rect::from_min_size(pos2(x, rect.min.y), vec2(width, rect.height())); - nodes.layout_node(style, behavior, child_rect, *child); - x += width + gap_width; - } - } - - fn layout_vertical( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - let num_gaps = self.children.len().saturating_sub(1); - let gap_height = behavior.gap_width(style); - let total_gap_height = gap_height * num_gaps as f32; - let available_height = (rect.height() - total_gap_height).at_least(0.0); - - let heights = self.shares.split(&self.children, available_height); - - let mut y = rect.min.y; - for (child, height) in self.children.iter().zip(heights) { - let child_rect = Rect::from_min_size(pos2(rect.min.x, y), vec2(rect.width(), height)); - nodes.layout_node(style, behavior, child_rect, *child); - y += height + gap_height; - } - } - - pub(super) fn ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - node_id: NodeId, - ) { - match self.dir { - LinearDir::Horizontal => self.horizontal_ui(nodes, behavior, drop_context, ui, node_id), - LinearDir::Vertical => self.vertical_ui(nodes, behavior, drop_context, ui, node_id), - } - } - - fn horizontal_ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - parent_id: NodeId, - ) { - for &child in &self.children { - if !is_being_dragged(ui.ctx(), child) { - nodes.node_ui(behavior, drop_context, ui, child); - } - } - - linear_drop_zones(ui.ctx(), nodes, &self.children, self.dir, |rect, i| { - drop_context.suggest_rect( - InsertionPoint::new(parent_id, LayoutInsertion::Horizontal(i)), - rect, - ); - }); - - // ------------------------ - // resizing: - - let parent_rect = nodes.rect(parent_id); - for (i, (left, right)) in self.children.iter().copied().tuple_windows().enumerate() { - let resize_id = egui::Id::new((parent_id, "resize", i)); - - let left_rect = nodes.rect(left); - let right_rect = nodes.rect(right); - let x = egui::lerp(left_rect.right()..=right_rect.left(), 0.5); - - let mut resize_state = ResizeState::Idle; - if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let line_rect = Rect::from_center_size( - pos2(x, parent_rect.center().y), - vec2( - 2.0 * ui.style().interaction.resize_grab_radius_side, - parent_rect.height(), - ), - ); - let response = ui.interact(line_rect, resize_id, egui::Sense::click_and_drag()); - resize_state = resize_interaction( - behavior, - &mut self.shares, - &self.children, - &response, - [left, right], - ui.painter().round_to_pixel(pointer.x) - x, - i, - |node_id: NodeId| nodes.rect(node_id).width(), - ); - - if resize_state != ResizeState::Idle { - ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal); - } - } - - let stroke = behavior.resize_stroke(ui.style(), resize_state); - ui.painter().vline(x, parent_rect.y_range(), stroke); - } - } - - fn vertical_ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - parent_id: NodeId, - ) { - for &child in &self.children { - if !is_being_dragged(ui.ctx(), child) { - nodes.node_ui(behavior, drop_context, ui, child); - } - } - - linear_drop_zones(ui.ctx(), nodes, &self.children, self.dir, |rect, i| { - drop_context.suggest_rect( - InsertionPoint::new(parent_id, LayoutInsertion::Vertical(i)), - rect, - ); - }); - - // ------------------------ - // resizing: - - let parent_rect = nodes.rect(parent_id); - for (i, (top, bottom)) in self.children.iter().copied().tuple_windows().enumerate() { - let resize_id = egui::Id::new((parent_id, "resize", i)); - - let top_rect = nodes.rect(top); - let bottom_rect = nodes.rect(bottom); - let y = egui::lerp(top_rect.bottom()..=bottom_rect.top(), 0.5); - - let mut resize_state = ResizeState::Idle; - if let Some(pointer) = ui.ctx().pointer_latest_pos() { - let line_rect = Rect::from_center_size( - pos2(parent_rect.center().x, y), - vec2( - parent_rect.width(), - 2.0 * ui.style().interaction.resize_grab_radius_side, - ), - ); - let response = ui.interact(line_rect, resize_id, egui::Sense::click_and_drag()); - resize_state = resize_interaction( - behavior, - &mut self.shares, - &self.children, - &response, - [top, bottom], - ui.painter().round_to_pixel(pointer.y) - y, - i, - |node_id: NodeId| nodes.rect(node_id).height(), - ); - - if resize_state != ResizeState::Idle { - ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeVertical); - } - } - - let stroke = behavior.resize_stroke(ui.style(), resize_state); - ui.painter().hline(parent_rect.x_range(), y, stroke); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn resize_interaction( - behavior: &mut dyn Behavior, - shares: &mut Shares, - children: &[NodeId], - splitter_response: &egui::Response, - [left, right]: [NodeId; 2], - dx: f32, - i: usize, - node_width: impl Fn(NodeId) -> f32, -) -> ResizeState { - if splitter_response.double_clicked() { - // double-click to center the split between left and right: - let mean = 0.5 * (shares[left] + shares[right]); - shares[left] = mean; - shares[right] = mean; - ResizeState::Hovering - } else if splitter_response.dragged() { - if dx < 0.0 { - // Expand right, shrink stuff to the left: - shares[right] += shrink_shares( - behavior, - shares, - &children[0..=i].iter().copied().rev().collect_vec(), - dx.abs(), - node_width, - ); - } else { - // Expand the left, shrink stuff to the right: - shares[left] += - shrink_shares(behavior, shares, &children[i + 1..], dx.abs(), node_width); - } - ResizeState::Dragging - } else if splitter_response.hovered() { - ResizeState::Hovering - } else { - ResizeState::Idle - } -} - -/// Try shrink the children by a total of `target_in_points`, -/// making sure no child gets smaller than its minimum size. -fn shrink_shares( - behavior: &dyn Behavior, - shares: &mut Shares, - children: &[NodeId], - target_in_points: f32, - size_in_point: impl Fn(NodeId) -> f32, -) -> f32 { - if children.is_empty() { - return 0.0; - } - - let mut total_shares = 0.0; - let mut total_points = 0.0; - for &child in children { - total_shares += shares[child]; - total_points += size_in_point(child); - } - - let shares_per_point = total_shares / total_points; - - let min_size_in_points = shares_per_point * behavior.min_size(); - - let target_in_shares = shares_per_point * target_in_points; - let mut total_shares_lost = 0.0; - - for &child in children { - let share = &mut shares[child]; - let shrink_by = (target_in_shares - total_shares_lost) - .min(*share - min_size_in_points) - .max(0.0); - - *share -= shrink_by; - total_shares_lost += shrink_by; - } - - total_shares_lost -} - -fn linear_drop_zones( - egui_ctx: &egui::Context, - nodes: &Nodes, - children: &[NodeId], - dir: LinearDir, - add_drop_drect: impl FnMut(Rect, usize), -) { - let preview_thickness = 12.0; - let dragged_index = children - .iter() - .position(|&child| is_being_dragged(egui_ctx, child)); - - let after_rect = |rect: Rect| match dir { - LinearDir::Horizontal => Rect::from_min_max( - rect.right_top() - vec2(preview_thickness, 0.0), - rect.right_bottom(), - ), - LinearDir::Vertical => Rect::from_min_max( - rect.left_bottom() - vec2(0.0, preview_thickness), - rect.right_bottom(), - ), - }; - - drop_zones( - preview_thickness, - children, - dragged_index, - dir, - |node_id| nodes.rect(node_id), - add_drop_drect, - after_rect, - ); -} - -/// Register drop-zones for a linear layout. -pub(super) fn drop_zones( - preview_thickness: f32, - children: &[NodeId], - dragged_index: Option, - dir: LinearDir, - get_rect: impl Fn(NodeId) -> Rect, - mut add_drop_drect: impl FnMut(Rect, usize), - after_rect: impl Fn(Rect) -> Rect, -) { - let before_rect = |rect: Rect| match dir { - LinearDir::Horizontal => Rect::from_min_max( - rect.left_top(), - rect.left_bottom() + vec2(preview_thickness, 0.0), - ), - LinearDir::Vertical => Rect::from_min_max( - rect.left_top(), - rect.right_top() + vec2(0.0, preview_thickness), - ), - }; - let between_rects = |a: Rect, b: Rect| match dir { - LinearDir::Horizontal => Rect::from_center_size( - a.right_center().lerp(b.left_center(), 0.5), - vec2(preview_thickness, a.height()), - ), - LinearDir::Vertical => Rect::from_center_size( - a.center_bottom().lerp(b.center_top(), 0.5), - vec2(a.width(), preview_thickness), - ), - }; - - let mut prev_rect: Option = None; - let mut insertion_index = 0; // skips over drag-source, if any, because it will be removed before its re-inserted - - for (i, &child) in children.iter().enumerate() { - let rect = get_rect(child); - - if Some(i) == dragged_index { - // Suggest hole as a drop-target: - add_drop_drect(rect, i); - } else { - if let Some(prev_rect) = prev_rect { - if Some(i - 1) != dragged_index { - // Suggest dropping between the rects: - add_drop_drect(between_rects(prev_rect, rect), insertion_index); - } - } else { - // Suggest dropping before the first child: - add_drop_drect(before_rect(rect), 0); - } - - insertion_index += 1; - } - - prev_rect = Some(rect); - } - - if let Some(last_rect) = prev_rect { - // Suggest dropping after the last child (unless that's the one being dragged): - if dragged_index != Some(children.len() - 1) { - add_drop_drect(after_rect(last_rect), insertion_index + 1); - } - } -} diff --git a/crates/egui_extras/src/dock/branch/mod.rs b/crates/egui_extras/src/dock/branch/mod.rs deleted file mode 100644 index 4c4750a95..000000000 --- a/crates/egui_extras/src/dock/branch/mod.rs +++ /dev/null @@ -1,188 +0,0 @@ -use egui::Rect; - -use super::{Behavior, DropContext, NodeId, Nodes, SimplifyAction}; - -mod grid; -mod linear; -mod tabs; - -pub use grid::{Grid, GridLoc}; -pub use linear::{Linear, LinearDir, Shares}; -pub use tabs::Tabs; - -// ---------------------------------------------------------------------------- - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum Layout { - #[default] - Tabs, - Horizontal, - Vertical, - Grid, -} - -impl Layout { - pub const ALL: [Self; 4] = [Self::Tabs, Self::Horizontal, Self::Vertical, Self::Grid]; -} - -// ---------------------------------------------------------------------------- - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub enum Branch { - Tabs(Tabs), - Linear(Linear), - Grid(Grid), -} - -impl Branch { - pub fn new_linear(dir: LinearDir, children: Vec) -> Self { - Self::Linear(Linear::new(dir, children)) - } - - pub fn new_tabs(children: Vec) -> Self { - Self::Tabs(Tabs::new(children)) - } - - pub fn new_grid(children: Vec) -> Self { - Self::Grid(Grid::new(children)) - } - - pub fn is_empty(&self) -> bool { - self.children().is_empty() - } - - pub fn children(&self) -> &[NodeId] { - match self { - Self::Tabs(tabs) => &tabs.children, - Self::Linear(linear) => &linear.children, - Self::Grid(grid) => &grid.children, - } - } - - pub fn add_child(&mut self, child: NodeId) { - match self { - Self::Tabs(tabs) => tabs.add_child(child), - Self::Linear(linear) => linear.add_child(child), - Self::Grid(grid) => grid.add_child(child), - } - } - - pub fn layout(&self) -> Layout { - match self { - Self::Tabs(_) => Layout::Tabs, - Self::Linear(linear) => match linear.dir { - LinearDir::Horizontal => Layout::Horizontal, - LinearDir::Vertical => Layout::Vertical, - }, - Self::Grid(_) => Layout::Grid, - } - } - - pub fn set_layout(&mut self, layout: Layout) { - if layout == self.layout() { - return; - } - - *self = match layout { - Layout::Tabs => Self::Tabs(Tabs::new(self.children().to_vec())), - Layout::Horizontal => { - Self::Linear(Linear::new(LinearDir::Horizontal, self.children().to_vec())) - } - Layout::Vertical => { - Self::Linear(Linear::new(LinearDir::Vertical, self.children().to_vec())) - } - Layout::Grid => Self::Grid(Grid::new(self.children().to_vec())), - }; - } - - pub(super) fn retain(&mut self, mut retain: impl FnMut(NodeId) -> bool) { - let retain = |node_id: &NodeId| retain(*node_id); - match self { - Self::Tabs(tabs) => tabs.children.retain(retain), - Self::Linear(linear) => linear.children.retain(retain), - Self::Grid(grid) => grid.children.retain(retain), - } - } - - pub(super) fn simplify_children(&mut self, mut simplify: impl FnMut(NodeId) -> SimplifyAction) { - match self { - Self::Tabs(tabs) => tabs.children.retain_mut(|child| match simplify(*child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - if tabs.active == *child { - tabs.active = new; - } - *child = new; - true - } - }), - Self::Linear(linear) => linear.children.retain_mut(|child| match simplify(*child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - linear.shares.replace_with(*child, new); - *child = new; - true - } - }), - Self::Grid(grid) => grid.children.retain_mut(|child| match simplify(*child) { - SimplifyAction::Remove => false, - SimplifyAction::Keep => true, - SimplifyAction::Replace(new) => { - if let Some(loc) = grid.locations.remove(child) { - grid.locations.insert(new, loc); - } - *child = new; - true - } - }), - } - } -} - -impl Branch { - pub(super) fn layout_recursive( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - if self.is_empty() { - return; - } - - match self { - Branch::Tabs(tabs) => tabs.layout(nodes, style, behavior, rect), - Branch::Linear(linear) => { - linear.layout(nodes, style, behavior, rect); - } - Branch::Grid(grid) => grid.layout(nodes, style, behavior, rect), - } - } -} - -impl Branch { - pub(super) fn ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - rect: Rect, - node_id: NodeId, - ) { - match self { - Branch::Tabs(tabs) => { - tabs.ui(nodes, behavior, drop_context, ui, rect, node_id); - } - Branch::Linear(linear) => { - linear.ui(nodes, behavior, drop_context, ui, node_id); - } - Branch::Grid(grid) => { - grid.ui(nodes, behavior, drop_context, ui, node_id); - } - } - } -} diff --git a/crates/egui_extras/src/dock/branch/tabs.rs b/crates/egui_extras/src/dock/branch/tabs.rs deleted file mode 100644 index f83fdde0b..000000000 --- a/crates/egui_extras/src/dock/branch/tabs.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::collections::HashMap; - -use egui::{vec2, Rect}; - -use crate::dock::{ - is_being_dragged, Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes, -}; - -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct Tabs { - /// The tabs, in order. - pub children: Vec, - - /// The currenlty open tab. - pub active: NodeId, -} - -impl Tabs { - pub fn new(children: Vec) -> Self { - let active = children.first().copied().unwrap_or_default(); - Self { children, active } - } - - pub fn add_child(&mut self, child: NodeId) { - self.children.push(child); - } - - pub fn set_active(&mut self, child: NodeId) { - self.active = child; - } - - pub(super) fn layout( - &mut self, - nodes: &mut Nodes, - style: &egui::Style, - behavior: &mut dyn Behavior, - rect: Rect, - ) { - if !self.children.iter().any(|&child| child == self.active) { - // Make sure something is active: - self.active = self.children.first().copied().unwrap_or_default(); - } - - let mut active_rect = rect; - active_rect.min.y += behavior.tab_bar_height(style); - - // Only lay out the active tab (saves CPU): - nodes.layout_node(style, behavior, active_rect, self.active); - } - - pub(super) fn ui( - &mut self, - nodes: &mut Nodes, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut egui::Ui, - rect: Rect, - node_id: NodeId, - ) { - let next_active = self.tab_bar_ui(behavior, ui, rect, nodes, drop_context, node_id); - - // When dragged, don't show it (it is "being held") - let is_active_being_dragged = is_being_dragged(ui.ctx(), self.active); - if !is_active_being_dragged { - nodes.node_ui(behavior, drop_context, ui, self.active); - } - - // We have only laid out the active tab, so we need to switch active tab _after_ the ui pass above: - self.active = next_active; - } - - /// Returns the next active tab (e.g. the one clicked, or the current). - fn tab_bar_ui( - &self, - behavior: &mut dyn Behavior, - ui: &mut egui::Ui, - rect: Rect, - nodes: &mut Nodes, - drop_context: &mut DropContext, - node_id: NodeId, - ) -> NodeId { - let mut next_active = self.active; - - let tab_bar_height = behavior.tab_bar_height(ui.style()); - let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; - let mut ui = ui.child_ui(tab_bar_rect, *ui.layout()); - - let mut button_rects = HashMap::new(); - let mut dragged_index = None; - - ui.painter() - .rect_filled(ui.max_rect(), 0.0, behavior.tab_bar_color(ui.visuals())); - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // Add buttons such as "add new tab" - behavior.top_bar_rtl_ui(ui, node_id); - - ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - ui.set_clip_rect(ui.max_rect()); // Don't cover the `rtl_ui` buttons. - - for (i, &child_id) in self.children.iter().enumerate() { - let is_being_dragged = is_being_dragged(ui.ctx(), child_id); - - let selected = child_id == self.active; - let id = child_id.id(); - - let response = - behavior.tab_ui(nodes, ui, id, child_id, selected, is_being_dragged); - let response = response.on_hover_cursor(egui::CursorIcon::Grab); - if response.clicked() { - next_active = child_id; - } - - if let Some(mouse_pos) = drop_context.mouse_pos { - if drop_context.dragged_node_id.is_some() - && response.rect.contains(mouse_pos) - { - // Expand this tab - maybe the user wants to drop something into it! - next_active = child_id; - } - } - - button_rects.insert(child_id, response.rect); - if is_being_dragged { - dragged_index = Some(i); - } - } - }); - }); - - // ----------- - // Drop zones: - - let preview_thickness = 6.0; - let after_rect = |rect: Rect| { - let dragged_size = if let Some(dragged_index) = dragged_index { - // We actually know the size of this thing - button_rects[&self.children[dragged_index]].size() - } else { - rect.size() // guess that the size is the same as the last button - }; - Rect::from_min_size( - rect.right_top() + vec2(ui.spacing().item_spacing.x, 0.0), - dragged_size, - ) - }; - super::linear::drop_zones( - preview_thickness, - &self.children, - dragged_index, - super::LinearDir::Horizontal, - |node_id| button_rects[&node_id], - |rect, i| { - drop_context - .suggest_rect(InsertionPoint::new(node_id, LayoutInsertion::Tabs(i)), rect); - }, - after_rect, - ); - - next_active - } -} diff --git a/crates/egui_extras/src/dock/dock_struct.rs b/crates/egui_extras/src/dock/dock_struct.rs deleted file mode 100644 index fc015d39c..000000000 --- a/crates/egui_extras/src/dock/dock_struct.rs +++ /dev/null @@ -1,271 +0,0 @@ -use egui::{Id, NumExt as _, Rect, Ui}; - -use super::{ - is_possible_drag, Behavior, Branch, DropContext, InsertionPoint, Node, NodeId, Nodes, - SimplificationOptions, SimplifyAction, -}; - -/// The top level type. Contains all persistent state, including layouts and sizes. -#[derive(Clone, serde::Serialize, serde::Deserialize)] -pub struct Dock { - pub root: NodeId, - pub nodes: Nodes, - - /// Smoothed avaerage of preview - #[serde(skip)] - pub smoothed_preview_rect: Option, -} - -impl Default for Dock { - fn default() -> Self { - Self { - root: Default::default(), - nodes: Default::default(), - smoothed_preview_rect: None, - } - } -} - -impl std::fmt::Debug for Dock { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Print a hiearchical view of the tree: - fn format_node( - f: &mut std::fmt::Formatter<'_>, - nodes: &Nodes, - 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}}") - } -} - -// ---------------------------------------------------------------------------- - -impl Dock { - pub fn new(root: NodeId, nodes: Nodes) -> Self { - Self { - root, - nodes, - smoothed_preview_rect: None, - } - } - - pub fn root(&self) -> NodeId { - self.root - } - - pub fn parent_of(&self, node_id: NodeId) -> Option { - self.nodes - .nodes - .iter() - .find(|(_, node)| { - if let Node::Branch(branch) = node { - branch.children().contains(&node_id) - } else { - false - } - }) - .map(|(id, _)| *id) - } - - /// Show the dock in the given [`Ui`]. - /// - /// The dock will use upp all the avilable space - nothing more, nothing less. - pub fn ui(&mut self, behavior: &mut dyn Behavior, 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); - - self.preview_dragged_node(behavior, &drop_context, ui); - } - - fn preview_dragged_node( - &mut self, - behavior: &mut dyn Behavior, - drop_context: &DropContext, - ui: &mut Ui, - ) { - 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(emilk): preview contents? - let text = behavior.tab_title_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 parent_rect = drop_context - .best_insertion - .and_then(|insertion_point| self.nodes.try_rect(insertion_point.parent_id)); - - behavior.paint_drag_preview(ui.visuals(), ui.painter(), parent_rect, preview_rect); - - if behavior.preview_dragged_leaves() { - // TODO(emilk): add support for previewing branches too. - 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; - } - } - - /// Take the preview rectangle and smooth it over time. - 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; - } - } - } - - /// Move the given node to the given insertion point. - pub 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. - pub fn dragged_id(&self, ctx: &egui::Context) -> Option { - 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; // not 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, nor does it remove the actual [`Node`]. - pub fn remove_node_id_from_parent(&mut self, remove_me: NodeId) { - for parent in self.nodes.nodes.values_mut() { - if let Node::Branch(branch) = parent { - branch.retain(|child| child != remove_me); - } - } - } -} diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs deleted file mode 100644 index 65f9f0f8d..000000000 --- a/crates/egui_extras/src/dock/mod.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! # 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). -//! -//! ## Shares -//! The relative sizes of linear layout (horizontal or vertical) and grid columns and rows are spcified by _shares_. -//! If the shares are `1,2,3` it means the first element gets `1/6` of the space, the second `2/6`, and the third `3/6`. -//! The default share size is `1`, and when resizing the shares are restributed so that -//! the total shares are always aproximately the same as the number of rows/columns. -//! This makes it easy to add new rows/columns. -//! -//! ## Shortcomings -//! The implementation is recursive, so if your trees get too deep you will get a stack overflow. -//! -//! ## Future improvements -//! * Easy per-tab close-buttons -//! * Scrolling of tab-bar -//! * Vertical tab bar -//! * Auto-join nested horizontal/vertical layouts in the simplify step - -// ## 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. -// -// Each frame consists of two passes: layout, and ui. -// The layout pass figures out where each node should be placed. -// The ui pass does all the painting. -// These two passes could be combined into one pass if we wanted to, -// but having them split up makes the code slightly simpler, and -// leaves the door open for more complex layout (e.g. min/max sizes per node). -// -// Everything is quite dynamic, so we have a bunch of defensive coding that call `warn!` on failure. -// These situations should not happen in normal use, but could happen if the user messes with -// the internals of the tree, putting it in an invalid state. - -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; - -// ---------------------------------------------------------------------------- - -/// 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 { - /// Generate a new random [`NodeId`]. - 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) - } -} - -// ---------------------------------------------------------------------------- - -/// A node in the tree. Either a leaf or a [`Branch`]. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub enum Node { - Leaf(Leaf), - Branch(Branch), -} - -impl Node { - fn layout(&self) -> Option { - match self { - Node::Leaf(_) => None, - Node::Branch(branch) => Some(branch.layout()), - } - } -} - -#[must_use] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum UiResponse { - None, - - /// The viewer is being dragged via some element in the Leaf - DragStarted, -} - -/// What are the rules for simplifying the tree? -#[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, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum ResizeState { - Idle, - Hovering, - Dragging, -} - -// ---------------------------------------------------------------------------- - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum LayoutInsertion { - Tabs(usize), - Horizontal(usize), - Vertical(usize), - Grid(GridLoc), -} - -#[derive(Clone, Copy, Debug)] -pub struct InsertionPoint { - pub parent_id: NodeId, - - /// Where in the parent? - pub insertion: LayoutInsertion, -} - -impl InsertionPoint { - pub fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self { - Self { - parent_id, - insertion, - } - } -} - -#[derive(PartialEq, Eq)] -enum GcAction { - Keep, - Remove, -} - -#[must_use] -enum SimplifyAction { - Remove, - Keep, - Replace(NodeId), -} - -fn is_possible_drag(ctx: &egui::Context) -> bool { - ctx.input(|input| input.pointer.is_decidedly_dragging()) -} - -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) -} - -// ---------------------------------------------------------------------------- - -struct DropContext { - enabled: bool, - dragged_node_id: Option, - mouse_pos: Option, - - best_insertion: Option, - best_dist_sq: f32, - preview_rect: Option, -} - -impl DropContext { - fn on_node( - &mut self, - behavior: &mut dyn Behavior, - style: &egui::Style, - parent_id: NodeId, - rect: Rect, - node: &Node, - ) { - 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, LayoutInsertion::Tabs(usize::MAX)), - rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(style)) - .1, - ); - } - - fn suggest_rect(&mut self, insertion: InsertionPoint, preview_rect: Rect) { - if !self.enabled { - return; - } - let target_point = preview_rect.center(); - 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); - } - } - } -} diff --git a/crates/egui_extras/src/dock/nodes.rs b/crates/egui_extras/src/dock/nodes.rs deleted file mode 100644 index 087821823..000000000 --- a/crates/egui_extras/src/dock/nodes.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use egui::{Pos2, Rect, Ui}; - -use super::{ - Behavior, Branch, DropContext, GcAction, Grid, InsertionPoint, Layout, LayoutInsertion, Linear, - LinearDir, Node, NodeId, SimplificationOptions, SimplifyAction, Tabs, UiResponse, -}; - -/// Contains all node state, but no root. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct Nodes { - pub nodes: HashMap>, - - /// Filled in by the layout step at the start of each frame. - #[serde(default, skip)] - pub(super) rects: HashMap, -} - -impl Default for Nodes { - fn default() -> Self { - Self { - nodes: Default::default(), - rects: Default::default(), - } - } -} - -// ---------------------------------------------------------------------------- - -impl Nodes { - pub(super) fn try_rect(&self, node_id: NodeId) -> Option { - self.rects.get(&node_id).copied() - } - - pub(super) 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> { - self.nodes.get(&node_id) - } - - pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut Node> { - self.nodes.get_mut(&node_id) - } - - #[must_use] - pub fn insert_node(&mut self, node: Node) -> 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 { - self.insert_node(Node::Branch(Branch::new_tabs(children))) - } - - #[must_use] - pub fn insert_horizontal_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new_linear( - LinearDir::Horizontal, - children, - ))) - } - - #[must_use] - pub fn insert_vertical_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new_linear( - LinearDir::Vertical, - children, - ))) - } - - #[must_use] - pub fn insert_grid_node(&mut self, children: Vec) -> NodeId { - self.insert_node(Node::Branch(Branch::new_grid(children))) - } - - pub 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))); - } - } - } - } - - pub(super) fn gc_root(&mut self, behavior: &mut dyn Behavior, 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::>() - ); - } - - self.nodes.retain(|node_id, _| visited.contains(node_id)); - } - - fn gc_node_id( - &mut self, - behavior: &mut dyn Behavior, - visited: &mut HashSet, - 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, - 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_recursive(self, style, behavior, rect); - } - - self.nodes.insert(node_id, node); - } - - pub(super) fn node_ui( - &mut self, - behavior: &mut dyn Behavior, - drop_context: &mut DropContext, - ui: &mut Ui, - node_id: NodeId, - ) { - // NOTE: important that we get the 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(behavior, ui.style(), node_id, rect, &node); - - // Each node gets its own `Ui`, nested inside each other, with proper clip rectangles. - 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(emilk): join nested versions of the same horizontal/vertical layouts - - branch.simplify_children(|child| self.simplify(options, child)); - - if branch.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.layout() == Layout::Tabs; - for &child in branch.children() { - self.make_all_leaves_children_of_tabs(is_tabs, child); - } - } - } - - self.nodes.insert(it, node); - } -} diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index 0c205f0bc..78beaf92f 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -13,7 +13,6 @@ #[cfg(feature = "chrono")] mod datepicker; -pub mod dock; pub mod image; mod layout; mod sizing; diff --git a/examples/dock/Cargo.toml b/examples/dock/Cargo.toml deleted file mode 100644 index fd2282c05..000000000 --- a/examples/dock/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "dock" -version = "0.1.0" -authors = ["Emil Ernerfeldt "] -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" -publish = false - - -[dependencies] -eframe = { path = "../../crates/eframe", features = [ - "persistence", - "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO -] } -egui_extras = { path = "../../crates/egui_extras" } -env_logger = "0.10" -log = "0.4" -serde = { version = "1.0", features = ["derive"] } diff --git a/examples/dock/README.md b/examples/dock/README.md deleted file mode 100644 index a778c575d..000000000 --- a/examples/dock/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Example how to showcase `egui_extras::dock`. - -```sh -cargo run -p dock -``` - -![](screenshot.png) diff --git a/examples/dock/src/main.rs b/examples/dock/src/main.rs deleted file mode 100644 index 4f1dd4c22..000000000 --- a/examples/dock/src/main.rs +++ /dev/null @@ -1,286 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release - -use eframe::egui; - -use egui_extras::dock; - -fn main() -> Result<(), eframe::Error> { - env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). - let options = eframe::NativeOptions { - initial_window_size: Some(egui::vec2(800.0, 600.0)), - ..Default::default() - }; - eframe::run_native( - "Dock", - options, - Box::new(|cc| { - let mut app = MyApp::default(); - if let Some(storage) = cc.storage { - if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) { - app = state; - } - } - Box::new(app) - }), - ) -} - -#[derive(serde::Deserialize, serde::Serialize)] -pub struct View { - nr: usize, -} - -impl std::fmt::Debug for View { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("View").field("nr", &self.nr).finish() - } -} - -impl View { - pub fn with_nr(nr: usize) -> Self { - Self { nr } - } - - pub fn ui(&mut self, ui: &mut egui::Ui) -> dock::UiResponse { - let color = egui::epaint::Hsva::new(0.1 * self.nr as f32, 0.5, 0.5, 1.0); - ui.painter().rect_filled(ui.max_rect(), 0.0, color); - let dragged = ui - .allocate_rect(ui.max_rect(), egui::Sense::drag()) - .on_hover_cursor(egui::CursorIcon::Grab) - .dragged(); - if dragged { - dock::UiResponse::DragStarted - } else { - dock::UiResponse::None - } - } -} - -struct DockBehavior { - simplification_options: dock::SimplificationOptions, - tab_bar_height: f32, - gap_width: f32, - add_child_to: Option, -} - -impl Default for DockBehavior { - fn default() -> Self { - Self { - simplification_options: Default::default(), - tab_bar_height: 24.0, - gap_width: 2.0, - add_child_to: None, - } - } -} - -impl DockBehavior { - fn ui(&mut self, ui: &mut egui::Ui) { - let Self { - simplification_options, - tab_bar_height, - gap_width, - add_child_to: _, - } = self; - - egui::Grid::new("behavior_ui") - .num_columns(2) - .show(ui, |ui| { - ui.label("All leaves must have tabs:"); - ui.checkbox(&mut simplification_options.all_leaves_must_have_tabs, ""); - ui.end_row(); - - ui.label("Tab bar height:"); - ui.add( - egui::DragValue::new(tab_bar_height) - .clamp_range(0.0..=100.0) - .speed(1.0), - ); - ui.end_row(); - - ui.label("Gap width:"); - ui.add( - egui::DragValue::new(gap_width) - .clamp_range(0.0..=20.0) - .speed(1.0), - ); - ui.end_row(); - }); - } -} - -impl dock::Behavior for DockBehavior { - fn leaf_ui( - &mut self, - ui: &mut egui::Ui, - _node_id: dock::NodeId, - view: &mut View, - ) -> dock::UiResponse { - view.ui(ui) - } - - fn tab_title_for_leaf(&mut self, view: &View) -> egui::WidgetText { - format!("View {}", view.nr).into() - } - - fn top_bar_rtl_ui(&mut self, ui: &mut egui::Ui, node_id: dock::NodeId) { - if ui.button("➕").clicked() { - self.add_child_to = Some(node_id); - } - } - - // --- - // Settings: - - fn tab_bar_height(&self, _style: &egui::Style) -> f32 { - self.tab_bar_height - } - - fn gap_width(&self, _style: &egui::Style) -> f32 { - self.gap_width - } - - fn simplification_options(&self) -> dock::SimplificationOptions { - self.simplification_options - } -} - -#[derive(serde::Deserialize, serde::Serialize)] -struct MyApp { - dock: dock::Dock, - - #[serde(skip)] - behavior: DockBehavior, - - #[serde(skip)] - last_dock_debug: String, -} - -impl Default for MyApp { - fn default() -> Self { - let mut next_view_nr = 0; - let mut gen_view = || { - let view = View::with_nr(next_view_nr); - next_view_nr += 1; - view - }; - - let mut nodes = dock::Nodes::default(); - - let mut tabs = vec![]; - let tab_node = { - let children = (0..7).map(|_| nodes.insert_leaf(gen_view())).collect(); - nodes.insert_tab_node(children) - }; - tabs.push(tab_node); - tabs.push({ - let children = (0..7).map(|_| nodes.insert_leaf(gen_view())).collect(); - nodes.insert_horizontal_node(children) - }); - tabs.push({ - let children = (0..7).map(|_| nodes.insert_leaf(gen_view())).collect(); - nodes.insert_vertical_node(children) - }); - tabs.push({ - let cells = (0..11).map(|_| nodes.insert_leaf(gen_view())).collect(); - nodes.insert_grid_node(cells) - }); - tabs.push(nodes.insert_leaf(gen_view())); - - let root = nodes.insert_tab_node(tabs); - - let dock = dock::Dock::new(root, nodes); - - Self { - dock, - behavior: Default::default(), - last_dock_debug: Default::default(), - } - } -} - -impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::SidePanel::left("tree").show(ctx, |ui| { - if ui.button("Reset").clicked() { - *self = Default::default(); - } - self.behavior.ui(ui); - ui.separator(); - - tree_ui(ui, &mut self.behavior, &mut self.dock.nodes, self.dock.root); - - if let Some(parent) = self.behavior.add_child_to.take() { - let new_child = self.dock.nodes.insert_leaf(View::with_nr(100)); - if let Some(dock::Node::Branch(dock::Branch::Tabs(tabs))) = - self.dock.nodes.get_mut(parent) - { - tabs.add_child(new_child); - tabs.set_active(new_child); - } - } - - ui.separator(); - ui.style_mut().wrap = Some(false); - let dock_debug = format!("{:#?}", self.dock); - ui.monospace(&dock_debug); - if self.last_dock_debug != dock_debug { - self.last_dock_debug = dock_debug; - log::debug!("{}", self.last_dock_debug); - } - }); - - egui::CentralPanel::default().show(ctx, |ui| { - self.dock.ui(&mut self.behavior, ui); - }); - } - - fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, eframe::APP_KEY, &self); - } -} - -fn tree_ui( - ui: &mut egui::Ui, - behavior: &mut dyn dock::Behavior, - nodes: &mut dock::Nodes, - node_id: dock::NodeId, -) { - // Get the name BEFORE we remove the node below! - let text = format!( - "{} - {node_id:?}", - behavior.tab_title_for_node(nodes, node_id).text() - ); - - let Some(mut node) = nodes.nodes.remove(&node_id) else { - log::warn!("Missing node {node_id:?}"); - return; - }; - - egui::CollapsingHeader::new(text) - .id_source((node_id, "tree")) - .default_open(true) - .show(ui, |ui| match &mut node { - dock::Node::Leaf(_) => {} - dock::Node::Branch(branch) => { - let mut layout = branch.layout(); - egui::ComboBox::from_label("Layout") - .selected_text(format!("{:?}", layout)) - .show_ui(ui, |ui| { - for typ in dock::Layout::ALL { - ui.selectable_value(&mut layout, typ, format!("{:?}", typ)) - .clicked(); - } - }); - if layout != branch.layout() { - branch.set_layout(layout); - } - - for &child in branch.children() { - tree_ui(ui, behavior, nodes, child); - } - } - }); - - nodes.nodes.insert(node_id, node); -}