1use std::sync::Arc;
4
5use gpui::{App, Axis, Bounds, Entity, Pixels};
6
7use super::node::{NodeKind, PaneNode, PanelId, TilePanel};
8use super::tree::{PaneTree, RootKind};
9use crate::dock::panel::{Panel, PanelView};
10
11pub struct DockLayout {
25 kind: BuilderKind,
26}
27
28enum BuilderKind {
29 Split {
30 axis: Axis,
31 children: Vec<(DockLayout, Option<Pixels>)>,
32 },
33 Tabs {
34 panels: Vec<(PanelId, Arc<dyn PanelView>)>,
35 active_ix: usize,
36 },
37 Tiles {
38 panels: Vec<(PanelId, Arc<dyn PanelView>, Bounds<Pixels>)>,
39 },
40}
41
42impl DockLayout {
43 pub fn h_split() -> Self {
45 Self::split(Axis::Horizontal)
46 }
47
48 pub fn v_split() -> Self {
50 Self::split(Axis::Vertical)
51 }
52
53 pub fn tabs() -> Self {
55 Self {
56 kind: BuilderKind::Tabs {
57 panels: Vec::new(),
58 active_ix: 0,
59 },
60 }
61 }
62
63 pub fn tiles() -> Self {
66 Self {
67 kind: BuilderKind::Tiles { panels: Vec::new() },
68 }
69 }
70
71 fn split(axis: Axis) -> Self {
72 Self {
73 kind: BuilderKind::Split {
74 axis,
75 children: Vec::new(),
76 },
77 }
78 }
79
80 pub fn child(mut self, child: DockLayout, size: Option<Pixels>) -> Self {
83 debug_assert!(
84 matches!(self.kind, BuilderKind::Split { .. }),
85 "child() is only valid on h_split() or v_split()"
86 );
87 if let BuilderKind::Split { children, .. } = &mut self.kind {
88 children.push((child, size));
89 }
90 self
91 }
92
93 pub fn panel<P: Panel>(mut self, panel: Entity<P>) -> Self {
95 debug_assert!(
96 matches!(self.kind, BuilderKind::Tabs { .. }),
97 "panel() is only valid on tabs()"
98 );
99 if let BuilderKind::Tabs { panels, .. } = &mut self.kind {
100 panels.push((PanelId::from(panel.entity_id()), Arc::new(panel)));
101 }
102 self
103 }
104
105 pub fn panel_view(mut self, panel: Arc<dyn PanelView>, cx: &App) -> Self {
113 debug_assert!(
114 matches!(self.kind, BuilderKind::Tabs { .. }),
115 "panel_view() is only valid on tabs()"
116 );
117 if let BuilderKind::Tabs { panels, .. } = &mut self.kind {
118 panels.push((panel.panel_id(cx), panel));
119 }
120 self
121 }
122
123 pub fn tile<P: Panel>(mut self, panel: Entity<P>, bounds: Bounds<Pixels>) -> Self {
125 debug_assert!(
126 matches!(self.kind, BuilderKind::Tiles { .. }),
127 "tile() is only valid on tiles()"
128 );
129 if let BuilderKind::Tiles { panels } = &mut self.kind {
130 panels.push((PanelId::from(panel.entity_id()), Arc::new(panel), bounds));
131 }
132 self
133 }
134
135 pub fn tile_view(
139 mut self,
140 panel: Arc<dyn PanelView>,
141 bounds: Bounds<Pixels>,
142 cx: &App,
143 ) -> Self {
144 debug_assert!(
145 matches!(self.kind, BuilderKind::Tiles { .. }),
146 "tile_view() is only valid on tiles()"
147 );
148 if let BuilderKind::Tiles { panels } = &mut self.kind {
149 panels.push((panel.panel_id(cx), panel, bounds));
150 }
151 self
152 }
153
154 pub fn active_index(mut self, ix: usize) -> Self {
157 debug_assert!(
158 matches!(self.kind, BuilderKind::Tabs { .. }),
159 "active_index() is only valid on tabs()"
160 );
161 if let BuilderKind::Tabs { active_ix, .. } = &mut self.kind {
162 *active_ix = ix;
163 }
164 self
165 }
166
167 pub(crate) fn build(
174 self,
175 tree: &mut PaneTree,
176 ) -> (PaneNode, Vec<(PanelId, Arc<dyn PanelView>)>) {
177 let mut panels = Vec::new();
178 let node = self.build_node(tree, &mut panels);
179 (node, panels)
180 }
181
182 fn build_node(
183 self,
184 tree: &mut PaneTree,
185 collected: &mut Vec<(PanelId, Arc<dyn PanelView>)>,
186 ) -> PaneNode {
187 let id = tree.allocate_node_id();
188 match self.kind {
189 BuilderKind::Split { axis, children } => {
190 let mut nodes = Vec::with_capacity(children.len());
191 let mut sizes = Vec::with_capacity(children.len());
192 for (child, size) in children {
193 nodes.push(child.build_node(tree, collected));
194 sizes.push(size);
195 }
196 PaneNode::new(
197 id,
198 NodeKind::Split {
199 axis,
200 children: nodes,
201 sizes,
202 },
203 )
204 }
205 BuilderKind::Tabs { panels, active_ix } => {
206 let ids = panels.iter().map(|(id, _)| *id).collect();
207 collected.extend(panels);
208 PaneNode::new(
209 id,
210 NodeKind::Tabs {
211 panels: ids,
212 active_ix,
213 },
214 )
215 }
216 BuilderKind::Tiles { panels } => {
217 let tiles = panels
218 .iter()
219 .enumerate()
220 .map(|(ix, (panel, _, bounds))| {
221 TilePanel::new(*panel, *bounds).with_z_index(ix)
222 })
223 .collect();
224 collected.extend(panels.into_iter().map(|(id, view, _)| (id, view)));
225 PaneNode::new(id, NodeKind::Tiles { panels: tiles })
226 }
227 }
228 }
229}
230
231impl PaneTree {
232 pub(crate) fn from_layout(
239 layout: DockLayout,
240 root_kind: RootKind,
241 ) -> (Self, Vec<(PanelId, Arc<dyn PanelView>)>) {
242 let mut tree = PaneTree::new(root_kind);
243 let (root, panels) = layout.build(&mut tree);
244
245 let root = match (root_kind, root.kind_ref()) {
246 (RootKind::Split, NodeKind::Split { .. }) | (RootKind::Any, _) => root,
247 (RootKind::Split, _) => {
248 let id = tree.allocate_node_id();
249 PaneNode::new(
250 id,
251 NodeKind::Split {
252 axis: Axis::Horizontal,
253 children: vec![root],
254 sizes: vec![None],
255 },
256 )
257 }
258 };
259
260 tree.replace_root(root);
261 tree.normalize();
262 (tree, panels)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use gpui::{TestAppContext, px};
269
270 use super::super::PaneRef;
271 use super::*;
272 use crate::dock::test_support::TestPanel;
273
274 #[gpui::test]
275 fn a_described_split_lowers_to_a_split_of_tab_groups(cx: &mut TestAppContext) {
276 let (tree, panels) = cx.update(|cx| {
277 let alpha = TestPanel::new("Alpha", cx);
278 let beta = TestPanel::new("Beta", cx);
279 PaneTree::from_layout(
280 DockLayout::h_split()
281 .child(DockLayout::tabs().panel(alpha), Some(px(300.)))
282 .child(DockLayout::tabs().panel(beta), None),
283 RootKind::Split,
284 )
285 });
286
287 assert_eq!(panels.len(), 2);
288 let PaneRef::Split {
289 axis,
290 children,
291 sizes,
292 } = tree.root().kind()
293 else {
294 panic!("expected a split root");
295 };
296 assert_eq!(axis, gpui::Axis::Horizontal);
297 assert_eq!(children.len(), 2);
298 assert_eq!(sizes, &[Some(px(300.)), None]);
299 assert!(matches!(children[0].kind(), PaneRef::Tabs { panels, .. } if panels.len() == 1));
300 }
301
302 #[gpui::test]
305 fn split_with_sizes_adds_each_child_once(cx: &mut TestAppContext) {
306 let (tree, _) = cx.update(|cx| {
307 let alpha = TestPanel::new("Alpha", cx);
308 let beta = TestPanel::new("Beta", cx);
309 PaneTree::from_layout(
310 DockLayout::h_split()
311 .child(DockLayout::tabs().panel(alpha), None)
312 .child(DockLayout::tabs().panel(beta), None),
313 RootKind::Split,
314 )
315 });
316
317 let PaneRef::Split {
318 children, sizes, ..
319 } = tree.root().kind()
320 else {
321 panic!("expected a split root");
322 };
323 assert_eq!(
324 children.len(),
325 2,
326 "each described child appears exactly once"
327 );
328 assert_eq!(sizes.len(), children.len());
329 assert_ne!(children[0].id(), children[1].id());
330 }
331
332 #[gpui::test]
333 fn a_bare_tab_group_is_wrapped_for_a_split_root(cx: &mut TestAppContext) {
334 let (tree, _) = cx.update(|cx| {
335 let alpha = TestPanel::new("Alpha", cx);
336 PaneTree::from_layout(DockLayout::tabs().panel(alpha), RootKind::Split)
337 });
338
339 assert!(matches!(tree.root().kind(), PaneRef::Split { .. }));
340 }
341
342 #[gpui::test]
343 fn an_active_index_selects_the_displayed_tab(cx: &mut TestAppContext) {
344 let (tree, _) = cx.update(|cx| {
345 let alpha = TestPanel::new("Alpha", cx);
346 let beta = TestPanel::new("Beta", cx);
347 PaneTree::from_layout(
348 DockLayout::tabs().panel(alpha).panel(beta).active_index(1),
349 RootKind::Any,
350 )
351 });
352
353 let PaneRef::Tabs { active_ix, .. } = tree.root().kind() else {
354 panic!("expected a tab group root");
355 };
356 assert_eq!(active_ix, 1);
357 }
358
359 #[gpui::test]
360 fn tiles_are_stacked_in_the_order_they_are_placed(cx: &mut TestAppContext) {
361 let bounds = Bounds {
362 origin: gpui::point(px(0.), px(0.)),
363 size: gpui::size(px(100.), px(100.)),
364 };
365 let (tree, _) = cx.update(|cx| {
366 let alpha = TestPanel::new("Alpha", cx);
367 let beta = TestPanel::new("Beta", cx);
368 PaneTree::from_layout(
369 DockLayout::tiles().tile(alpha, bounds).tile(beta, bounds),
370 RootKind::Any,
371 )
372 });
373
374 let PaneRef::Tiles { panels } = tree.root().kind() else {
375 panic!("expected a tiles root");
376 };
377 assert_eq!(panels[0].z_index(), 0);
378 assert_eq!(panels[1].z_index(), 1);
379 }
380}