Skip to main content

astrelis_ui/widgets/docking/
context.rs

1//! Container registry cache for efficient docking lookups.
2//!
3//! During cross-container tab dragging, the event handler needs to find all
4//! DockTabs containers to test for drop targets. Without caching, this requires
5//! an O(N) tree walk on every mouse move. `DockingContext` caches this data and
6//! invalidates it when the tree structure changes.
7
8use super::DockTabs;
9use super::splitter::{
10    DEFAULT_SEPARATOR_SIZE, default_separator_color, default_separator_hover_color,
11};
12use super::tabs::{
13    DEFAULT_TAB_BAR_HEIGHT, default_active_tab_color, default_inactive_tab_color,
14    default_tab_bar_color, default_tab_hover_color, default_tab_text_color,
15};
16use crate::tree::{LayoutRect, NodeId, UiTree};
17use astrelis_core::alloc::HashMap;
18use astrelis_render::Color;
19
20/// Centralized styling defaults for the docking system.
21///
22/// Controls separator appearance, tab bar colors/sizing, and content padding.
23/// Set on [`DockingContext`] to apply defaults to all docking widgets.
24/// Individual widgets can override specific values (e.g. per-widget `content_padding`).
25#[derive(Debug, Clone)]
26pub struct DockingStyle {
27    /// Width of splitter separators in pixels.
28    pub separator_size: f32,
29    /// Normal separator color.
30    pub separator_color: Color,
31    /// Separator color when hovered or dragged.
32    pub separator_hover_color: Color,
33    /// Height of the tab bar in pixels.
34    pub tab_bar_height: f32,
35    /// Tab bar background color.
36    pub tab_bar_color: Color,
37    /// Active tab background color.
38    pub active_tab_color: Color,
39    /// Inactive tab background color.
40    pub inactive_tab_color: Color,
41    /// Tab label text color.
42    pub tab_text_color: Color,
43    /// Tab hover background color.
44    pub tab_hover_color: Color,
45    /// Tab label font size.
46    pub tab_font_size: f32,
47    /// Whether tabs show a close button by default.
48    pub closable: bool,
49    /// Padding between the tab content area edges and child content (pixels).
50    pub content_padding: f32,
51    /// Extra hit-test tolerance around the separator (pixels per side).
52    ///
53    /// The separator visual is `separator_size` wide, but the grabbable area
54    /// extends by this many pixels on each side perpendicular to the separator.
55    pub separator_tolerance: f32,
56}
57
58impl Default for DockingStyle {
59    fn default() -> Self {
60        Self {
61            separator_size: DEFAULT_SEPARATOR_SIZE,
62            separator_color: default_separator_color(),
63            separator_hover_color: default_separator_hover_color(),
64            tab_bar_height: DEFAULT_TAB_BAR_HEIGHT,
65            tab_bar_color: default_tab_bar_color(),
66            active_tab_color: default_active_tab_color(),
67            inactive_tab_color: default_inactive_tab_color(),
68            tab_text_color: default_tab_text_color(),
69            tab_hover_color: default_tab_hover_color(),
70            tab_font_size: 11.0,
71            closable: false,
72            content_padding: 4.0,
73            separator_tolerance: 4.0,
74        }
75    }
76}
77
78impl DockingStyle {
79    /// Set the separator size.
80    pub fn separator_size(mut self, size: f32) -> Self {
81        self.separator_size = size;
82        self
83    }
84
85    /// Set the separator colors.
86    pub fn separator_colors(mut self, normal: Color, hover: Color) -> Self {
87        self.separator_color = normal;
88        self.separator_hover_color = hover;
89        self
90    }
91
92    /// Set the tab bar height.
93    pub fn tab_bar_height(mut self, height: f32) -> Self {
94        self.tab_bar_height = height;
95        self
96    }
97
98    /// Set the tab bar background color.
99    pub fn tab_bar_color(mut self, color: Color) -> Self {
100        self.tab_bar_color = color;
101        self
102    }
103
104    /// Set the active tab color.
105    pub fn active_tab_color(mut self, color: Color) -> Self {
106        self.active_tab_color = color;
107        self
108    }
109
110    /// Set the inactive tab color.
111    pub fn inactive_tab_color(mut self, color: Color) -> Self {
112        self.inactive_tab_color = color;
113        self
114    }
115
116    /// Set the tab text color.
117    pub fn tab_text_color(mut self, color: Color) -> Self {
118        self.tab_text_color = color;
119        self
120    }
121
122    /// Set the tab hover color.
123    pub fn tab_hover_color(mut self, color: Color) -> Self {
124        self.tab_hover_color = color;
125        self
126    }
127
128    /// Set the tab font size.
129    pub fn tab_font_size(mut self, size: f32) -> Self {
130        self.tab_font_size = size;
131        self
132    }
133
134    /// Set whether tabs are closable by default.
135    pub fn closable(mut self, closable: bool) -> Self {
136        self.closable = closable;
137        self
138    }
139
140    /// Set the content padding (inset from panel edges).
141    pub fn content_padding(mut self, padding: f32) -> Self {
142        self.content_padding = padding;
143        self
144    }
145
146    /// Set the separator hit-test tolerance (extra pixels per side).
147    pub fn separator_tolerance(mut self, tolerance: f32) -> Self {
148        self.separator_tolerance = tolerance;
149        self
150    }
151}
152
153/// Cached information about a DockTabs container.
154#[derive(Debug, Clone)]
155pub struct CachedContainerInfo {
156    /// Absolute layout of the container.
157    pub layout: LayoutRect,
158    /// Number of tabs in the container.
159    pub tab_count: usize,
160}
161
162/// Registry of DockTabs containers for efficient lookup during drag operations.
163///
164/// Caches the locations and metadata of all DockTabs widgets in the tree
165/// so that cross-container drop target detection does not need a full tree
166/// traversal on every mouse move.
167pub struct DockingContext {
168    /// Cached container info keyed by NodeId.
169    tab_containers: HashMap<NodeId, CachedContainerInfo>,
170    /// Whether the cache needs rebuilding.
171    cache_dirty: bool,
172    /// Centralized docking style defaults.
173    style: DockingStyle,
174}
175
176impl DockingContext {
177    /// Create a new empty docking context.
178    pub fn new() -> Self {
179        Self {
180            tab_containers: HashMap::new(),
181            cache_dirty: true,
182            style: DockingStyle::default(),
183        }
184    }
185
186    /// Get a reference to the docking style.
187    pub fn style(&self) -> &DockingStyle {
188        &self.style
189    }
190
191    /// Get a mutable reference to the docking style.
192    pub fn style_mut(&mut self) -> &mut DockingStyle {
193        &mut self.style
194    }
195
196    /// Replace the docking style.
197    pub fn set_style(&mut self, style: DockingStyle) {
198        self.style = style;
199    }
200
201    /// Mark the cache as needing a rebuild.
202    ///
203    /// Call this after any tree structure change (node add/remove, tab transfer, split).
204    pub fn invalidate(&mut self) {
205        self.cache_dirty = true;
206    }
207
208    /// Rebuild the container cache from the tree.
209    ///
210    /// Walks the tree to find all DockTabs widgets and caches their layout and tab count.
211    pub fn rebuild_cache(&mut self, tree: &UiTree) {
212        self.tab_containers.clear();
213
214        let all_tabs = tree.find_widgets_with_layout::<DockTabs>();
215        for (node_id, layout) in all_tabs {
216            let tab_count = tree
217                .get_widget(node_id)
218                .and_then(|w| w.as_any().downcast_ref::<DockTabs>())
219                .map(|t| t.tab_count())
220                .unwrap_or(0);
221
222            self.tab_containers
223                .insert(node_id, CachedContainerInfo { layout, tab_count });
224        }
225
226        self.cache_dirty = false;
227    }
228
229    /// Get the cached tab containers, rebuilding if necessary.
230    pub fn find_tab_containers(&mut self, tree: &UiTree) -> &HashMap<NodeId, CachedContainerInfo> {
231        if self.cache_dirty {
232            self.rebuild_cache(tree);
233        }
234        &self.tab_containers
235    }
236
237    /// Check if the cache is dirty and needs a rebuild.
238    pub fn is_dirty(&self) -> bool {
239        self.cache_dirty
240    }
241
242    /// Get the number of cached containers.
243    pub fn container_count(&self) -> usize {
244        self.tab_containers.len()
245    }
246}
247
248impl Default for DockingContext {
249    fn default() -> Self {
250        Self::new()
251    }
252}