Skip to main content

gpui_base/dock/layout/
tree.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2
3use smallvec::SmallVec;
4
5use super::node::{NodeId, NodeKind, PaneNode, PaneRef, PanelId, TilePanel};
6
7/// Whether the root of this tree is pinned to a split.
8///
9/// The center of a `DockArea` must serialize as a `StackPanel` even when
10/// empty, which `RootKind::Split` guarantees. A dock's root is unconstrained.
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
12pub enum RootKind {
13    Split,
14    Any,
15}
16
17/// Path from the root as a sequence of child indices.
18pub(crate) type NodePath = SmallVec<[usize; 8]>;
19
20/// Source of every [`NodeId`], shared by every tree in the process.
21///
22/// A per-tree counter would restart at the same value in each tree, and a
23/// `DockArea` owns four of them (the center plus one per dock). Its entity
24/// cache is keyed by `NodeId` alone, so two trees minting the same id would
25/// hand two different containers the same cache slot. Allocating globally is
26/// what makes `NodeId` the stable *container* identity its documentation
27/// claims, rather than an identity that is only unique within one tree.
28static NEXT_NODE_ID: AtomicU64 = AtomicU64::new(0);
29
30fn next_node_id() -> NodeId {
31    NodeId::from_u64(NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed))
32}
33
34/// One region's layout, as pure data.
35///
36/// A `DockArea` owns one of these per region — the center plus each dock it
37/// has. Containers are addressed by [`NodeId`] and panels by [`PanelId`];
38/// no entity handle lives in here, so a tree can be built, edited, compared
39/// and serialized with no `App` in sight.
40///
41/// Every edit method normalizes before returning and reports what it did as
42/// an [`EditResult`](super::EditResult), so there is no window in which a
43/// caller can observe a tree with an empty container, a one-child split, or an
44/// out-of-range active index.
45#[derive(Clone, PartialEq, Debug)]
46pub struct PaneTree {
47    root: PaneNode,
48    root_kind: RootKind,
49}
50
51impl PaneTree {
52    pub fn new(root_kind: RootKind) -> Self {
53        let root = PaneNode::new(
54            next_node_id(),
55            NodeKind::Split {
56                axis: gpui::Axis::Horizontal,
57                children: Vec::new(),
58                sizes: Vec::new(),
59            },
60        );
61        Self { root, root_kind }
62    }
63
64    pub fn root(&self) -> &PaneNode {
65        &self.root
66    }
67
68    /// Mutable access to the root, for normalization's post-order pass.
69    pub(crate) fn root_mut(&mut self) -> &mut PaneNode {
70        &mut self.root
71    }
72
73    pub fn root_kind(&self) -> RootKind {
74        self.root_kind
75    }
76
77    pub(crate) fn allocate_node_id(&mut self) -> NodeId {
78        next_node_id()
79    }
80
81    /// Every container id in the tree, in pre-order.
82    pub fn node_ids(&self) -> Vec<NodeId> {
83        let mut ids = Vec::new();
84        self.root.walk(&mut |node| ids.push(node.id()));
85        ids
86    }
87
88    /// Every panel in the tree, in pre-order.
89    pub fn panels(&self) -> impl Iterator<Item = PanelId> {
90        let mut found = Vec::new();
91        self.root.walk(&mut |node| match node.kind() {
92            PaneRef::Tabs { panels, .. } => found.extend_from_slice(panels),
93            PaneRef::Tiles { panels } => found.extend(panels.iter().map(TilePanel::panel)),
94            PaneRef::Split { .. } => {}
95        });
96        found.into_iter()
97    }
98
99    pub fn find_node(&self, id: NodeId) -> Option<&PaneNode> {
100        self.path_of_node(id).map(|path| self.node_at(&path))
101    }
102
103    /// The tab or tiles node holding `panel`.
104    pub fn find_panel_node(&self, panel: PanelId) -> Option<NodeId> {
105        let mut found = None;
106        self.root.walk(&mut |node| {
107            let holds = match node.kind() {
108                PaneRef::Tabs { panels, .. } => panels.contains(&panel),
109                PaneRef::Tiles { panels } => panels.iter().any(|p| p.panel() == panel),
110                PaneRef::Split { .. } => false,
111            };
112            if holds {
113                found = Some(node.id());
114            }
115        });
116        found
117    }
118
119    pub(crate) fn path_of_node(&self, id: NodeId) -> Option<NodePath> {
120        fn search(node: &PaneNode, id: NodeId, path: &mut NodePath) -> bool {
121            if node.id() == id {
122                return true;
123            }
124            if let NodeKind::Split { children, .. } = node.kind_ref() {
125                for (ix, child) in children.iter().enumerate() {
126                    path.push(ix);
127                    if search(child, id, path) {
128                        return true;
129                    }
130                    path.pop();
131                }
132            }
133            false
134        }
135
136        let mut path = NodePath::new();
137        search(&self.root, id, &mut path).then_some(path)
138    }
139
140    pub(crate) fn node_at(&self, path: &NodePath) -> &PaneNode {
141        let mut node = &self.root;
142        for ix in path {
143            let NodeKind::Split { children, .. } = node.kind_ref() else {
144                unreachable!("path traverses a non-split node");
145            };
146            node = &children[*ix];
147        }
148        node
149    }
150
151    pub(crate) fn node_at_mut(&mut self, path: &NodePath) -> &mut PaneNode {
152        let mut node = &mut self.root;
153        for ix in path {
154            let NodeKind::Split { children, .. } = node.kind_mut() else {
155                unreachable!("path traverses a non-split node");
156            };
157            node = &mut children[*ix];
158        }
159        node
160    }
161
162    /// Replace the whole tree with `node`, keeping `node`'s own id and
163    /// `root_kind` unchanged. Used by normalization's root-collapse rule.
164    pub(crate) fn replace_root(&mut self, node: PaneNode) {
165        self.root = node;
166    }
167}
168
169#[cfg(test)]
170use gpui::{Axis, Pixels};
171
172#[cfg(test)]
173impl PaneTree {
174    pub(crate) fn push_tabs_for_test(&mut self, parent: NodeId, panels: Vec<PanelId>) -> NodeId {
175        let id = self.allocate_node_id();
176        let path = self.path_of_node(parent).expect("parent must exist");
177        let NodeKind::Split {
178            children, sizes, ..
179        } = self.node_at_mut(&path).kind_mut()
180        else {
181            panic!("parent must be a split");
182        };
183        children.push(PaneNode::new(
184            id,
185            NodeKind::Tabs {
186                panels,
187                active_ix: 0,
188            },
189        ));
190        sizes.push(None);
191        id
192    }
193
194    /// Like [`Self::push_tabs_for_test`], but with a concrete slot size
195    /// instead of always pushing `None`. Needed to exercise the scaling
196    /// arithmetic in `normalize`'s same-axis splice rule, which only runs
197    /// when every sibling size is known.
198    pub(crate) fn push_sized_tabs_for_test(
199        &mut self,
200        parent: NodeId,
201        panels: Vec<PanelId>,
202        size: Option<Pixels>,
203    ) -> NodeId {
204        let id = self.allocate_node_id();
205        let path = self.path_of_node(parent).expect("parent must exist");
206        let NodeKind::Split {
207            children, sizes, ..
208        } = self.node_at_mut(&path).kind_mut()
209        else {
210            panic!("parent must be a split");
211        };
212        children.push(PaneNode::new(
213            id,
214            NodeKind::Tabs {
215                panels,
216                active_ix: 0,
217            },
218        ));
219        sizes.push(size);
220        id
221    }
222
223    pub(crate) fn set_root_tiles_for_test(&mut self, panels: Vec<TilePanel>) -> NodeId {
224        let id = self.allocate_node_id();
225        self.root = PaneNode::new(id, NodeKind::Tiles { panels });
226        id
227    }
228
229    pub(crate) fn set_root_split_for_test(&mut self, axis: Axis) -> NodeId {
230        let id = self.allocate_node_id();
231        self.root = PaneNode::new(
232            id,
233            NodeKind::Split {
234                axis,
235                children: Vec::new(),
236                sizes: Vec::new(),
237            },
238        );
239        id
240    }
241
242    pub(crate) fn set_root_axis_for_test(&mut self, new_axis: Axis) {
243        if let NodeKind::Split { axis, .. } = self.root.kind_mut() {
244            *axis = new_axis;
245        }
246    }
247
248    pub(crate) fn set_root_tabs_for_test(
249        &mut self,
250        panels: Vec<PanelId>,
251        active_ix: usize,
252    ) -> NodeId {
253        let id = self.allocate_node_id();
254        self.root = PaneNode::new(id, NodeKind::Tabs { panels, active_ix });
255        id
256    }
257
258    pub(crate) fn push_split_for_test(
259        &mut self,
260        parent: NodeId,
261        axis: Axis,
262        size: Option<Pixels>,
263    ) -> NodeId {
264        let id = self.allocate_node_id();
265        let path = self.path_of_node(parent).expect("parent must exist");
266        let NodeKind::Split {
267            children, sizes, ..
268        } = self.node_at_mut(&path).kind_mut()
269        else {
270            panic!("parent must be a split");
271        };
272        children.push(PaneNode::new(
273            id,
274            NodeKind::Split {
275                axis,
276                children: Vec::new(),
277                sizes: Vec::new(),
278            },
279        ));
280        sizes.push(size);
281        id
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use gpui::{Bounds, point, px, size};
288
289    use super::*;
290
291    fn panel_id(n: u64) -> PanelId {
292        PanelId::from_u64(n)
293    }
294
295    #[test]
296    fn a_fresh_split_root_tree_has_no_panels() {
297        let tree = PaneTree::new(RootKind::Split);
298        assert!(
299            matches!(tree.root().kind(), PaneRef::Split { children, .. } if children.is_empty())
300        );
301        assert_eq!(tree.panels().count(), 0);
302    }
303
304    #[test]
305    fn node_ids_are_unique_and_resolvable() {
306        let mut tree = PaneTree::new(RootKind::Split);
307        let root = tree.root().id();
308        let tabs = tree.push_tabs_for_test(root, vec![panel_id(1)]);
309        assert_ne!(tabs, tree.root().id());
310        assert!(tree.find_node(tabs).is_some());
311        assert_eq!(tree.find_panel_node(panel_id(1)), Some(tabs));
312    }
313
314    #[test]
315    fn tile_panels_carry_bounds_and_z_index() {
316        let mut tree = PaneTree::new(RootKind::Any);
317        let tiles = tree.set_root_tiles_for_test(vec![
318            TilePanel::new(
319                panel_id(7),
320                Bounds {
321                    origin: point(px(10.), px(20.)),
322                    size: size(px(100.), px(50.)),
323                },
324            )
325            .with_z_index(3),
326        ]);
327        let PaneRef::Tiles { panels } = tree.find_node(tiles).unwrap().kind() else {
328            panic!("expected tiles root");
329        };
330        assert_eq!(panels[0].panel(), panel_id(7));
331        assert_eq!(panels[0].z_index(), 3);
332        assert_eq!(panels[0].bounds().size.width, px(100.));
333    }
334}