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};
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::Split { .. } => {}
94        });
95        found.into_iter()
96    }
97
98    pub fn find_node(&self, id: NodeId) -> Option<&PaneNode> {
99        self.path_of_node(id).map(|path| self.node_at(&path))
100    }
101
102    /// The tab node holding `panel`.
103    pub fn find_panel_node(&self, panel: PanelId) -> Option<NodeId> {
104        let mut found = None;
105        self.root.walk(&mut |node| {
106            let holds = match node.kind() {
107                PaneRef::Tabs { panels, .. } => panels.contains(&panel),
108                PaneRef::Split { .. } => false,
109            };
110            if holds {
111                found = Some(node.id());
112            }
113        });
114        found
115    }
116
117    pub(crate) fn path_of_node(&self, id: NodeId) -> Option<NodePath> {
118        fn search(node: &PaneNode, id: NodeId, path: &mut NodePath) -> bool {
119            if node.id() == id {
120                return true;
121            }
122            if let NodeKind::Split { children, .. } = node.kind_ref() {
123                for (ix, child) in children.iter().enumerate() {
124                    path.push(ix);
125                    if search(child, id, path) {
126                        return true;
127                    }
128                    path.pop();
129                }
130            }
131            false
132        }
133
134        let mut path = NodePath::new();
135        search(&self.root, id, &mut path).then_some(path)
136    }
137
138    pub(crate) fn node_at(&self, path: &NodePath) -> &PaneNode {
139        let mut node = &self.root;
140        for ix in path {
141            let NodeKind::Split { children, .. } = node.kind_ref() else {
142                unreachable!("path traverses a non-split node");
143            };
144            node = &children[*ix];
145        }
146        node
147    }
148
149    pub(crate) fn node_at_mut(&mut self, path: &NodePath) -> &mut PaneNode {
150        let mut node = &mut self.root;
151        for ix in path {
152            let NodeKind::Split { children, .. } = node.kind_mut() else {
153                unreachable!("path traverses a non-split node");
154            };
155            node = &mut children[*ix];
156        }
157        node
158    }
159
160    /// Replace the whole tree with `node`, keeping `node`'s own id and
161    /// `root_kind` unchanged. Used by normalization's root-collapse rule.
162    pub(crate) fn replace_root(&mut self, node: PaneNode) {
163        self.root = node;
164    }
165}
166
167#[cfg(test)]
168use gpui::{Axis, Pixels};
169
170#[cfg(test)]
171impl PaneTree {
172    pub(crate) fn push_tabs_for_test(&mut self, parent: NodeId, panels: Vec<PanelId>) -> NodeId {
173        let id = self.allocate_node_id();
174        let path = self.path_of_node(parent).expect("parent must exist");
175        let NodeKind::Split {
176            children, sizes, ..
177        } = self.node_at_mut(&path).kind_mut()
178        else {
179            panic!("parent must be a split");
180        };
181        children.push(PaneNode::new(
182            id,
183            NodeKind::Tabs {
184                panels,
185                active_ix: 0,
186            },
187        ));
188        sizes.push(None);
189        id
190    }
191
192    /// Like [`Self::push_tabs_for_test`], but with a concrete slot size
193    /// instead of always pushing `None`. Needed to exercise the scaling
194    /// arithmetic in `normalize`'s same-axis splice rule, which only runs
195    /// when every sibling size is known.
196    pub(crate) fn push_sized_tabs_for_test(
197        &mut self,
198        parent: NodeId,
199        panels: Vec<PanelId>,
200        size: Option<Pixels>,
201    ) -> NodeId {
202        let id = self.allocate_node_id();
203        let path = self.path_of_node(parent).expect("parent must exist");
204        let NodeKind::Split {
205            children, sizes, ..
206        } = self.node_at_mut(&path).kind_mut()
207        else {
208            panic!("parent must be a split");
209        };
210        children.push(PaneNode::new(
211            id,
212            NodeKind::Tabs {
213                panels,
214                active_ix: 0,
215            },
216        ));
217        sizes.push(size);
218        id
219    }
220
221    pub(crate) fn set_root_split_for_test(&mut self, axis: Axis) -> NodeId {
222        let id = self.allocate_node_id();
223        self.root = PaneNode::new(
224            id,
225            NodeKind::Split {
226                axis,
227                children: Vec::new(),
228                sizes: Vec::new(),
229            },
230        );
231        id
232    }
233
234    pub(crate) fn set_root_axis_for_test(&mut self, new_axis: Axis) {
235        if let NodeKind::Split { axis, .. } = self.root.kind_mut() {
236            *axis = new_axis;
237        }
238    }
239
240    pub(crate) fn set_root_tabs_for_test(
241        &mut self,
242        panels: Vec<PanelId>,
243        active_ix: usize,
244    ) -> NodeId {
245        let id = self.allocate_node_id();
246        self.root = PaneNode::new(id, NodeKind::Tabs { panels, active_ix });
247        id
248    }
249
250    pub(crate) fn push_split_for_test(
251        &mut self,
252        parent: NodeId,
253        axis: Axis,
254        size: Option<Pixels>,
255    ) -> NodeId {
256        let id = self.allocate_node_id();
257        let path = self.path_of_node(parent).expect("parent must exist");
258        let NodeKind::Split {
259            children, sizes, ..
260        } = self.node_at_mut(&path).kind_mut()
261        else {
262            panic!("parent must be a split");
263        };
264        children.push(PaneNode::new(
265            id,
266            NodeKind::Split {
267                axis,
268                children: Vec::new(),
269                sizes: Vec::new(),
270            },
271        ));
272        sizes.push(size);
273        id
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    fn panel_id(n: u64) -> PanelId {
282        PanelId::from_u64(n)
283    }
284
285    #[test]
286    fn a_fresh_split_root_tree_has_no_panels() {
287        let tree = PaneTree::new(RootKind::Split);
288        assert!(
289            matches!(tree.root().kind(), PaneRef::Split { children, .. } if children.is_empty())
290        );
291        assert_eq!(tree.panels().count(), 0);
292    }
293
294    #[test]
295    fn node_ids_are_unique_and_resolvable() {
296        let mut tree = PaneTree::new(RootKind::Split);
297        let root = tree.root().id();
298        let tabs = tree.push_tabs_for_test(root, vec![panel_id(1)]);
299        assert_ne!(tabs, tree.root().id());
300        assert!(tree.find_node(tabs).is_some());
301        assert_eq!(tree.find_panel_node(panel_id(1)), Some(tabs));
302    }
303}