1use std::collections::HashMap;
2
3use gpui::{Bounds, Pixels};
4
5use crate::Placement;
6
7use super::node::{NodeId, NodeKind, PaneNode, PanelId, TilePanel};
8use super::tree::PaneTree;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum InsertTarget {
13 Tabs {
15 node: NodeId,
16 ix: Option<usize>,
17 activate: bool,
18 },
19 Split {
21 node: NodeId,
22 placement: Placement,
23 size: Option<Pixels>,
24 },
25 Tile {
27 node: NodeId,
28 bounds: Bounds<Pixels>,
29 },
30}
31
32#[derive(Clone, Debug, Default, PartialEq)]
41pub struct EditResult {
42 changed: bool,
43}
44
45impl EditResult {
46 pub fn changed(&self) -> bool {
47 self.changed
48 }
49}
50
51impl PaneTree {
52 pub fn insert_panel(&mut self, panel: PanelId, target: InsertTarget) -> EditResult {
53 self.edit(|tree| tree.apply_insert(panel, target))
54 }
55
56 pub fn remove_panel(&mut self, panel: PanelId) -> EditResult {
57 self.edit(|tree| tree.detach_panel(panel))
58 }
59
60 pub fn move_panel(&mut self, panel: PanelId, target: InsertTarget) -> EditResult {
63 self.edit(|tree| {
64 let detached = tree.detach_panel(panel);
65 let inserted = tree.apply_insert(panel, target);
66 detached || inserted
67 })
68 }
69
70 pub fn split(
71 &mut self,
72 at: NodeId,
73 panel: PanelId,
74 placement: Placement,
75 size: Option<Pixels>,
76 ) -> EditResult {
77 self.insert_panel(
78 panel,
79 InsertTarget::Split {
80 node: at,
81 placement,
82 size,
83 },
84 )
85 }
86
87 pub fn set_active(&mut self, node: NodeId, ix: usize) -> EditResult {
88 self.edit(|tree| {
89 let Some(path) = tree.path_of_node(node) else {
90 return false;
91 };
92 let NodeKind::Tabs { active_ix, .. } = tree.node_at_mut(&path).kind_mut() else {
93 return false;
94 };
95 if *active_ix == ix {
96 return false;
97 }
98 *active_ix = ix;
99 true
100 })
101 }
102
103 pub fn set_sizes(&mut self, node: NodeId, new_sizes: Vec<Option<Pixels>>) -> EditResult {
111 self.edit(|tree| {
112 let Some(path) = tree.path_of_node(node) else {
113 return false;
114 };
115 let NodeKind::Split {
116 children, sizes, ..
117 } = tree.node_at_mut(&path).kind_mut()
118 else {
119 return false;
120 };
121 if new_sizes.len() != children.len() || *sizes == new_sizes {
122 return false;
123 }
124 *sizes = new_sizes;
125 true
126 })
127 }
128
129 pub fn set_tile_bounds(&mut self, panel: PanelId, bounds: Bounds<Pixels>) -> EditResult {
130 self.edit(|tree| {
131 let mut changed = false;
132 tree.with_tile(panel, |tile| {
133 if tile.bounds() != bounds {
134 *tile = tile.with_bounds(bounds);
135 changed = true;
136 }
137 });
138 changed
139 })
140 }
141
142 pub fn bring_to_front(&mut self, panel: PanelId) -> EditResult {
143 self.edit(|tree| {
144 let top = tree.max_z_index();
145 let mut changed = false;
146 tree.with_tile(panel, |tile| {
147 if tile.z_index() < top {
151 *tile = tile.with_z_index(top + 1);
152 changed = true;
153 }
154 });
155 changed
156 })
157 }
158}
159
160impl PaneTree {
161 pub(crate) fn adopt_measured_sizes(&mut self, measured: &HashMap<NodeId, Vec<Pixels>>) {
172 fn walk(node: &mut PaneNode, measured: &HashMap<NodeId, Vec<Pixels>>) {
173 let id = node.id();
174 let NodeKind::Split {
175 children, sizes, ..
176 } = node.kind_mut()
177 else {
178 return;
179 };
180
181 if let Some(actual) = measured.get(&id) {
182 if actual.len() == sizes.len() {
183 for (slot, size) in sizes.iter_mut().zip(actual) {
184 if *size > Pixels::ZERO {
187 *slot = Some(*size);
188 }
189 }
190 }
191 }
192
193 for child in children.iter_mut() {
194 walk(child, measured);
195 }
196 }
197
198 walk(self.root_mut(), measured);
199 }
200}
201
202impl PaneTree {
203 fn edit(&mut self, apply: impl FnOnce(&mut Self) -> bool) -> EditResult {
210 let mutated = apply(self);
211 let collapsed = self.normalize_reporting();
212
213 EditResult {
214 changed: mutated || collapsed,
215 }
216 }
217
218 pub fn contains_panel(&self, panel: PanelId) -> bool {
220 self.panels().any(|candidate| candidate == panel)
221 }
222}
223
224impl PaneTree {
225 fn apply_insert(&mut self, panel: PanelId, target: InsertTarget) -> bool {
229 match target {
230 InsertTarget::Tabs { node, ix, activate } => {
231 let Some(path) = self.path_of_node(node) else {
232 return false;
233 };
234 let NodeKind::Tabs { panels, active_ix } = self.node_at_mut(&path).kind_mut()
235 else {
236 return false;
237 };
238 let ix = ix.unwrap_or(panels.len()).min(panels.len());
239 panels.insert(ix, panel);
240 if activate {
241 *active_ix = ix;
242 } else if ix <= *active_ix && panels.len() > 1 {
243 *active_ix += 1;
245 }
246 true
247 }
248 InsertTarget::Split {
249 node,
250 placement,
251 size,
252 } => self.insert_beside(node, panel, placement, size),
253 InsertTarget::Tile { node, bounds } => {
254 let Some(path) = self.path_of_node(node) else {
255 return false;
256 };
257 let top = self.max_z_index();
258 let NodeKind::Tiles { panels } = self.node_at_mut(&path).kind_mut() else {
259 return false;
260 };
261 panels.push(TilePanel::new(panel, bounds).with_z_index(top + 1));
262 true
263 }
264 }
265 }
266
267 fn insert_beside(
275 &mut self,
276 node: NodeId,
277 panel: PanelId,
278 placement: Placement,
279 size: Option<Pixels>,
280 ) -> bool {
281 let Some(path) = self.path_of_node(node) else {
282 return false;
283 };
284 let group_id = self.allocate_node_id();
285 let group = PaneNode::new(
286 group_id,
287 NodeKind::Tabs {
288 panels: vec![panel],
289 active_ix: 0,
290 },
291 );
292 let before = matches!(placement, Placement::Left | Placement::Top);
293
294 if let Some((parent_path, ix)) = split_parent_of(&path) {
295 let parent_axis = match self.node_at(&parent_path).kind_ref() {
296 NodeKind::Split { axis, .. } => Some(*axis),
297 _ => None,
298 };
299
300 if parent_axis == Some(placement.axis()) {
301 let NodeKind::Split {
302 children, sizes, ..
303 } = self.node_at_mut(&parent_path).kind_mut()
304 else {
305 return false;
306 };
307 let share = match size {
318 Some(size) => Some(size),
319 None => {
320 let half = sizes[ix].map(|slot| slot / 2.);
321 sizes[ix] = half;
322 half
323 }
324 };
325 let at = if before { ix } else { ix + 1 };
326 children.insert(at, group);
327 sizes.insert(at, share);
328 return true;
329 }
330 }
331
332 let wrapper_id = self.allocate_node_id();
336 let target = std::mem::replace(
337 self.node_at_mut(&path),
338 PaneNode::new(
339 wrapper_id,
340 NodeKind::Tabs {
341 panels: Vec::new(),
342 active_ix: 0,
343 },
344 ),
345 );
346 let (children, sizes) = if before {
347 (vec![group, target], vec![size, None])
348 } else {
349 (vec![target, group], vec![None, size])
350 };
351 let wrapper = PaneNode::new(
352 wrapper_id,
353 NodeKind::Split {
354 axis: placement.axis(),
355 children,
356 sizes,
357 },
358 );
359 *self.node_at_mut(&path) = wrapper;
360 true
361 }
362
363 fn detach_panel(&mut self, panel: PanelId) -> bool {
365 let Some(node) = self.find_panel_node(panel) else {
366 return false;
367 };
368 let Some(path) = self.path_of_node(node) else {
369 return false;
370 };
371
372 match self.node_at_mut(&path).kind_mut() {
373 NodeKind::Tabs { panels, active_ix } => {
374 let Some(ix) = panels.iter().position(|p| *p == panel) else {
375 return false;
376 };
377 panels.remove(ix);
378 if ix < *active_ix {
379 *active_ix -= 1;
380 }
381 true
382 }
383 NodeKind::Tiles { panels } => {
384 let Some(ix) = panels.iter().position(|p| p.panel() == panel) else {
385 return false;
386 };
387 panels.remove(ix);
388 true
389 }
390 NodeKind::Split { .. } => false,
391 }
392 }
393
394 fn with_tile(&mut self, panel: PanelId, f: impl FnOnce(&mut TilePanel)) {
395 let Some(node) = self.find_panel_node(panel) else {
396 return;
397 };
398 let Some(path) = self.path_of_node(node) else {
399 return;
400 };
401 if let NodeKind::Tiles { panels } = self.node_at_mut(&path).kind_mut() {
402 if let Some(tile) = panels.iter_mut().find(|tile| tile.panel() == panel) {
403 f(tile);
404 }
405 }
406 }
407
408 fn max_z_index(&self) -> usize {
410 let mut top = 0;
411 self.root().walk(&mut |node| {
412 if let NodeKind::Tiles { panels } = node.kind_ref() {
413 top = top.max(panels.iter().map(TilePanel::z_index).max().unwrap_or(0));
414 }
415 });
416 top
417 }
418}
419
420fn split_parent_of(path: &super::tree::NodePath) -> Option<(super::tree::NodePath, usize)> {
422 let (&ix, parent) = path.split_last()?;
423 Some((parent.iter().copied().collect(), ix))
424}
425
426#[cfg(test)]
427mod tests {
428 use super::super::*;
429 use crate::Placement;
430 use gpui::{Axis, Bounds, Pixels, point, px, size};
431
432 fn panel(n: u64) -> PanelId {
433 PanelId::from_u64(n)
434 }
435
436 fn tree_with_one_group() -> (PaneTree, NodeId) {
437 let mut tree = PaneTree::new(RootKind::Split);
438 let tabs = tree.push_tabs_for_test(tree.root().id(), vec![panel(1)]);
439 tree.normalize();
440 (tree, tabs)
441 }
442
443 #[test]
444 fn inserting_into_a_tab_group_appends_and_can_activate() {
445 let (mut tree, tabs) = tree_with_one_group();
446 let result = tree.insert_panel(
447 panel(2),
448 InsertTarget::Tabs {
449 node: tabs,
450 ix: None,
451 activate: true,
452 },
453 );
454
455 assert!(result.changed());
456 let PaneRef::Tabs { panels, active_ix } = tree.find_node(tabs).unwrap().kind() else {
457 panic!()
458 };
459 assert_eq!(panels, [panel(1), panel(2)]);
460 assert_eq!(active_ix, 1, "the inserted panel becomes the displayed one");
461 }
462
463 #[test]
464 fn a_background_insert_leaves_the_active_panel_alone() {
465 let (mut tree, tabs) = tree_with_one_group();
466 let result = tree.insert_panel(
467 panel(2),
468 InsertTarget::Tabs {
469 node: tabs,
470 ix: None,
471 activate: false,
472 },
473 );
474
475 assert!(result.changed());
476 let PaneRef::Tabs { panels, active_ix } = tree.find_node(tabs).unwrap().kind() else {
477 panic!()
478 };
479 assert_eq!(panels, [panel(1), panel(2)]);
480 assert_eq!(active_ix, 0, "the displayed panel is left alone");
481 }
482
483 #[test]
484 fn removing_the_last_panel_collapses_the_group_and_reports_it() {
485 let (mut tree, tabs) = tree_with_one_group();
486 let result = tree.remove_panel(panel(1));
487
488 assert!(result.changed());
489 assert!(!tree.contains_panel(panel(1)), "the panel left the tree");
490 assert!(tree.find_node(tabs).is_none(), "its empty group collapsed");
491 assert!(
492 matches!(tree.root().kind(), PaneRef::Split { children, .. } if children.is_empty())
493 );
494 }
495
496 #[test]
497 fn splitting_creates_a_sibling_group_on_the_requested_side() {
498 let (mut tree, tabs) = tree_with_one_group();
499 let result = tree.split(tabs, panel(2), Placement::Right, Some(px(240.)));
500
501 assert!(result.changed());
502 let PaneRef::Split {
503 axis,
504 children,
505 sizes,
506 } = tree.root().kind()
507 else {
508 panic!()
509 };
510 assert_eq!(axis, Axis::Horizontal);
511 assert_eq!(children.len(), 2);
512 assert_eq!(sizes[1], Some(px(240.)));
513
514 let PaneRef::Tabs { panels, .. } = children[0].kind() else {
515 panic!()
516 };
517 assert_eq!(panels, [panel(1)]);
518 let PaneRef::Tabs { panels, .. } = children[1].kind() else {
519 panic!()
520 };
521 assert_eq!(panels, [panel(2)]);
522 }
523
524 #[test]
525 fn splitting_left_puts_the_new_group_first() {
526 let (mut tree, tabs) = tree_with_one_group();
527 tree.split(tabs, panel(2), Placement::Left, None);
528
529 let PaneRef::Split { children, .. } = tree.root().kind() else {
530 panic!()
531 };
532 let PaneRef::Tabs { panels, .. } = children[0].kind() else {
533 panic!()
534 };
535 assert_eq!(panels, [panel(2)]);
536 }
537
538 #[test]
539 fn splitting_across_the_parent_axis_nests_a_new_split() {
540 let (mut tree, tabs) = tree_with_one_group();
541 tree.push_tabs_for_test(tree.root().id(), vec![panel(9)]);
542 tree.normalize();
543
544 tree.split(tabs, panel(2), Placement::Bottom, None);
545
546 let PaneRef::Split { axis, children, .. } = tree.root().kind() else {
547 panic!()
548 };
549 assert_eq!(axis, Axis::Horizontal);
550 let PaneRef::Split {
551 axis: inner,
552 children: inner_children,
553 ..
554 } = children[0].kind()
555 else {
556 panic!("the split target is wrapped in a vertical split")
557 };
558 assert_eq!(inner, Axis::Vertical);
559 assert_eq!(inner_children.len(), 2);
560
561 let PaneRef::Tabs { panels, .. } = inner_children[0].kind() else {
564 panic!()
565 };
566 assert_eq!(panels, [panel(1)], "the original target stays first");
567 let PaneRef::Tabs { panels, .. } = inner_children[1].kind() else {
568 panic!()
569 };
570 assert_eq!(panels, [panel(2)], "the new group lands second, below");
571 }
572
573 #[test]
574 fn splitting_top_across_the_parent_axis_puts_the_new_group_first() {
575 let (mut tree, tabs) = tree_with_one_group();
576 tree.push_tabs_for_test(tree.root().id(), vec![panel(9)]);
577 tree.normalize();
578
579 tree.split(tabs, panel(2), Placement::Top, None);
580
581 let PaneRef::Split { children, .. } = tree.root().kind() else {
582 panic!()
583 };
584 let PaneRef::Split {
585 axis: inner,
586 children: inner_children,
587 ..
588 } = children[0].kind()
589 else {
590 panic!("the split target is wrapped in a vertical split")
591 };
592 assert_eq!(inner, Axis::Vertical);
593 assert_eq!(inner_children.len(), 2);
594
595 let PaneRef::Tabs { panels, .. } = inner_children[0].kind() else {
598 panic!()
599 };
600 assert_eq!(panels, [panel(2)], "the new group lands first, above");
601 let PaneRef::Tabs { panels, .. } = inner_children[1].kind() else {
602 panic!()
603 };
604 assert_eq!(panels, [panel(1)], "the original target moves second");
605 }
606
607 #[test]
608 fn the_split_target_keeps_its_node_id_so_its_entity_survives() {
609 let (mut tree, tabs) = tree_with_one_group();
610 tree.split(tabs, panel(2), Placement::Right, None);
611
612 assert!(
613 tree.find_node(tabs).is_some(),
614 "the target group is reused, not rebuilt"
615 );
616 }
617
618 #[test]
619 fn moving_a_panel_between_groups_preserves_its_identity() {
620 let (mut tree, tabs) = tree_with_one_group();
621 assert!(tree.split(tabs, panel(2), Placement::Right, None).changed());
622 let other = tree
623 .find_panel_node(panel(2))
624 .expect("the split put panel 2 in a group of its own");
625
626 let result = tree.move_panel(
627 panel(1),
628 InsertTarget::Tabs {
629 node: other,
630 ix: None,
631 activate: true,
632 },
633 );
634
635 assert!(result.changed());
636 assert_eq!(
637 tree.panels().collect::<Vec<_>>(),
638 vec![panel(2), panel(1)],
639 "a move is not a removal; both panels are still in the tree"
640 );
641 assert!(
642 tree.find_node(tabs).is_none(),
643 "the emptied group collapses"
644 );
645 }
646
647 #[test]
648 fn a_no_op_edit_reports_no_change() {
649 let (mut tree, tabs) = tree_with_one_group();
650 let result = tree.set_active(tabs, 0);
651 assert!(!result.changed());
652 }
653
654 #[test]
655 fn set_sizes_replaces_a_matching_length_vector() {
656 let (mut tree, tabs) = tree_with_one_group();
657 tree.split(tabs, panel(2), Placement::Right, None);
658 let root = tree.root().id();
659
660 let result = tree.set_sizes(root, vec![Some(px(100.)), Some(px(200.))]);
661
662 assert!(result.changed());
663 let PaneRef::Split { sizes, .. } = tree.root().kind() else {
664 panic!()
665 };
666 assert_eq!(sizes, &[Some(px(100.)), Some(px(200.))]);
667 }
668
669 #[test]
670 fn set_sizes_ignores_a_mismatched_length_vector() {
671 let (mut tree, tabs) = tree_with_one_group();
672 tree.split(tabs, panel(2), Placement::Right, None);
673 let root = tree.root().id();
674 let PaneRef::Split {
675 sizes: before_sizes,
676 ..
677 } = tree.root().kind()
678 else {
679 panic!()
680 };
681 let before_sizes = before_sizes.to_vec();
682
683 let result = tree.set_sizes(root, vec![Some(px(10.)), Some(px(20.)), Some(px(30.))]);
685
686 assert!(
687 !result.changed(),
688 "a mismatched vector must not report a change"
689 );
690 let PaneRef::Split { sizes, .. } = tree.root().kind() else {
691 panic!()
692 };
693 assert_eq!(
694 sizes,
695 before_sizes.as_slice(),
696 "the mismatched vector is rejected"
697 );
698 }
699
700 fn tile_bounds(x: f32) -> Bounds<Pixels> {
701 Bounds {
702 origin: point(px(x), px(0.)),
703 size: size(px(10.), px(10.)),
704 }
705 }
706
707 #[test]
708 fn inserting_a_tile_places_it_at_the_given_bounds_on_top() {
709 let mut tree = PaneTree::new(RootKind::Any);
710 let canvas = tree.set_root_tiles_for_test(vec![
711 TilePanel::new(panel(1), tile_bounds(0.)).with_z_index(4),
712 ]);
713
714 let result = tree.insert_panel(
715 panel(2),
716 InsertTarget::Tile {
717 node: canvas,
718 bounds: tile_bounds(70.),
719 },
720 );
721
722 assert!(result.changed());
723 let PaneRef::Tiles { panels } = tree.root().kind() else {
724 panic!("the canvas stays a canvas rather than being split")
725 };
726 assert_eq!(panels.len(), 2);
727 let added = panels.iter().find(|tile| tile.panel() == panel(2)).unwrap();
728 assert_eq!(added.bounds(), tile_bounds(70.), "the bounds are honoured");
729 assert!(
730 added.z_index() > panels[0].z_index(),
731 "a new tile lands on top of the ones already there"
732 );
733 }
734
735 #[test]
736 fn set_tile_bounds_moves_one_tile_and_leaves_its_peers_alone() {
737 let mut tree = PaneTree::new(RootKind::Any);
738 tree.set_root_tiles_for_test(vec![
739 TilePanel::new(panel(1), tile_bounds(0.)).with_z_index(1),
740 TilePanel::new(panel(2), tile_bounds(40.)).with_z_index(2),
741 ]);
742
743 let result = tree.set_tile_bounds(panel(1), tile_bounds(90.));
744
745 assert!(result.changed());
746 let PaneRef::Tiles { panels } = tree.root().kind() else {
747 panic!()
748 };
749 let moved = panels.iter().find(|tile| tile.panel() == panel(1)).unwrap();
750 let other = panels.iter().find(|tile| tile.panel() == panel(2)).unwrap();
751 assert_eq!(moved.bounds(), tile_bounds(90.));
752 assert_eq!(
753 moved.z_index(),
754 1,
755 "moving a tile does not raise it; that is `bring_to_front`'s job"
756 );
757 assert_eq!(other.bounds(), tile_bounds(40.), "its peer does not move");
758 }
759
760 #[test]
761 fn bring_to_front_raises_the_tile_above_its_peers() {
762 let mut tree = PaneTree::new(RootKind::Any);
763 let bounds = Bounds {
764 origin: point(px(0.), px(0.)),
765 size: size(px(10.), px(10.)),
766 };
767 tree.set_root_tiles_for_test(vec![
768 TilePanel::new(panel(1), bounds).with_z_index(0),
769 TilePanel::new(panel(2), bounds).with_z_index(1),
770 ]);
771
772 tree.bring_to_front(panel(1));
773
774 let PaneRef::Tiles { panels } = tree.root().kind() else {
775 panic!()
776 };
777 let raised = panels.iter().find(|p| p.panel() == panel(1)).unwrap();
778 let other = panels.iter().find(|p| p.panel() == panel(2)).unwrap();
779 assert!(raised.z_index() > other.z_index());
780 }
781
782 #[test]
783 fn every_edit_leaves_the_tree_normalized() {
784 let (mut tree, tabs) = tree_with_one_group();
785 tree.split(tabs, panel(2), Placement::Bottom, None);
786 tree.insert_panel(
787 panel(3),
788 InsertTarget::Tabs {
789 node: tabs,
790 ix: None,
791 activate: false,
792 },
793 );
794 tree.remove_panel(panel(2));
795 tree.remove_panel(panel(3));
796
797 assert!(tree.is_normalized());
798 }
799}