diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a9f117..b2b995b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # egui_dock changelog +## 0.11.2 - 2024-02-16 + +### Fixed +From [#225](https://github.com/Adanos020/egui_dock/pull/225): +- Tabs now always appear at the pointer position while being dragged. +- Retaining tabs no longer breaks the binary tree leading to a panic. +- Filtering tabs no longer leaves some leaves empty and now correctly rearranges the tree. + +## 0.11.1 - 2024-02-09 + +### Fixed +- Bug where tabs couldn't be re-docked onto the main surface if it's empty ([#222](https://github.com/Adanos020/egui_dock/pull/222)) + ## 0.11.0 - 2024-02-06 ### Added diff --git a/src/dock_state/tree/mod.rs b/src/dock_state/tree/mod.rs index 2a52f25c..3bf861a1 100644 --- a/src/dock_state/tree/mod.rs +++ b/src/dock_state/tree/mod.rs @@ -25,6 +25,7 @@ pub use node_index::NodeIndex; pub use tab_index::TabIndex; pub use tab_iter::TabIter; +use egui::ahash::HashSet; use egui::Rect; use std::{ fmt, @@ -744,20 +745,24 @@ impl Tree { focused_node, nodes, } = self; + let mut emptied_nodes = HashSet::default(); let nodes = nodes .iter() - .filter_map(|node| { + .enumerate() + .map(|(index, node)| { let node = node.filter_map_tabs(function.clone()); - match node { - Node::Leaf { ref tabs, .. } => (!tabs.is_empty()).then_some(node), - _ => Some(node), + if node.is_empty() { + emptied_nodes.insert(NodeIndex(index)); } + node }) .collect(); - Tree { + let mut new_tree = Tree { nodes, focused_node: *focused_node, - } + }; + new_tree.balance(emptied_nodes); + new_tree } /// Returns a new [`Tree`] while mapping the tab type. @@ -784,10 +789,33 @@ impl Tree { where F: Clone + FnMut(&mut Tab) -> bool, { - self.nodes.retain_mut(|node| { + let mut emptied_nodes = HashSet::default(); + for (index, node) in self.nodes.iter_mut().enumerate() { node.retain_tabs(predicate.clone()); - !node.is_empty() - }); + if node.is_empty() { + emptied_nodes.insert(NodeIndex(index)); + } + } + self.balance(emptied_nodes); + } + + fn balance(&mut self, emptied_nodes: HashSet) { + let mut emptied_parents = HashSet::default(); + for parent_index in emptied_nodes.into_iter().filter_map(|ni| ni.parent()) { + if self[parent_index.left()].is_empty() && self[parent_index.right()].is_empty() { + self[parent_index] = Node::Empty; + emptied_parents.insert(parent_index); + } else if self[parent_index.left()].is_empty() { + self.nodes.swap(parent_index.0, parent_index.right().0); + self[parent_index.right()] = Node::Empty; + } else if self[parent_index.right()].is_empty() { + self.nodes.swap(parent_index.0, parent_index.left().0); + self[parent_index.left()] = Node::Empty; + } + } + if !emptied_parents.is_empty() { + self.balance(emptied_parents); + } } } diff --git a/src/style.rs b/src/style.rs index 93042ad3..a40f3613 100644 --- a/src/style.rs +++ b/src/style.rs @@ -46,6 +46,7 @@ pub enum TabAddAlign { /// # /// ``` #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[allow(missing_docs)] pub struct Style { /// Sets padding to indent from the edges of the window. By `Default` it's `None`. @@ -63,6 +64,7 @@ pub struct Style { /// Specifies the look and feel of buttons. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ButtonsStyle { /// Color of the close tab button. pub close_tab_color: Color32, @@ -91,6 +93,7 @@ pub struct ButtonsStyle { /// Specifies the look and feel of node separators. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct SeparatorStyle { /// Width of the rectangle separator between nodes. By `Default` it's `1.0`. pub width: f32, @@ -115,6 +118,7 @@ pub struct SeparatorStyle { /// Specifies the look and feel of tab bars. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TabBarStyle { /// Background color of tab bar. By `Default` it's [`Color32::WHITE`]. pub bg_fill: Color32, @@ -138,6 +142,7 @@ pub struct TabBarStyle { /// Specifies the look and feel of an individual tab. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TabStyle { /// Style of the tab when it is active. pub active: TabInteractionStyle, @@ -177,6 +182,7 @@ pub struct TabStyle { /// Specifies the look and feel of individual tabs while they are being interacted with. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TabInteractionStyle { /// Color of the outline around tabs. By `Default` it's [`Color32::BLACK`]. pub outline_color: Color32, @@ -193,6 +199,7 @@ pub struct TabInteractionStyle { /// Specifies the look and feel of the tab body. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TabBodyStyle { /// Inner margin of tab body. By `Default` it's `Margin::same(4.0)`. pub inner_margin: Margin, @@ -209,6 +216,7 @@ pub struct TabBodyStyle { /// Specifies the look and feel of the tab drop overlay. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct OverlayStyle { /// Sets selection color for the placing area of the tab where this tab targeted on it. /// By `Default` it's `(0, 191, 255)` (light blue) with `0.5` capacity. @@ -246,6 +254,7 @@ pub struct OverlayStyle { /// Specifies the feel of the tab drop overlay, i.e anything non visual about the overlay. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct OverlayFeel { /// range is `0.0..=1.0`. pub window_drop_coverage: f32, @@ -265,6 +274,7 @@ pub struct OverlayFeel { /// Specifies the type of overlay used. #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum OverlayType { /// Shows highlighted areas predicting where a dropped tab would land were it to be dropped this frame. /// @@ -279,6 +289,7 @@ pub enum OverlayType { /// Highlighting on the currently hovered leaf. #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct LeafHighlighting { /// Fill color. pub color: Color32, diff --git a/src/widgets/dock_area/show/leaf.rs b/src/widgets/dock_area/show/leaf.rs index 8d0cec52..569758c2 100644 --- a/src/widgets/dock_area/show/leaf.rs +++ b/src/widgets/dock_area/show/leaf.rs @@ -210,7 +210,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .with((tab_index, "tab")); let tab_index = TabIndex(tab_index); let is_being_dragged = tabs_ui.memory(|mem| mem.is_being_dragged(id)) - && tabs_ui.input(|i| i.pointer.primary_down() || i.pointer.primary_released()) + && tabs_ui.input(|i| i.pointer.is_decidedly_dragging()) && self.draggable_tabs; if is_being_dragged { @@ -255,8 +255,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { .response; let title_id = response.id; - let sense = Sense::click_and_drag(); - let response = tabs_ui.interact(response.rect, id, sense); + let response = tabs_ui.interact(response.rect, id, Sense::click_and_drag()); if let Some(pointer_pos) = tabs_ui.ctx().pointer_interact_pos() { let start = *state.drag_start.get_or_insert(pointer_pos); @@ -369,6 +368,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { // the underlying tab if state.drag_start.is_some() && response.rect.contains(pos) { self.tab_hover_rect = Some((response.rect, tab_index)); + state.drag_start = None; } } diff --git a/src/widgets/dock_area/show/main_surface.rs b/src/widgets/dock_area/show/main_surface.rs index b5dade68..fb8de141 100644 --- a/src/widgets/dock_area/show/main_surface.rs +++ b/src/widgets/dock_area/show/main_surface.rs @@ -20,7 +20,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> { if self.dock_state.main_surface().is_empty() { let rect = ui.available_rect_before_wrap(); let response = ui.allocate_rect(rect, Sense::hover()); - if response.hovered() { + if response.contains_pointer() { self.hover_data = Some(HoverData { rect, dst: TreeComponent::Surface(surf_index),