1pub use crate::pane_group::element::{PaneAxisElement, pane_axis};
2use crate::{
3 AnyActiveCall, AppState, CollaboratorId, FollowerState, Pane, ParticipantLocation, Workspace,
4 WorkspaceSettings,
5 workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
6};
7use anyhow::Result;
8use collections::HashMap;
9use gpui::{
10 Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, MouseButton, Pixels,
11 Point, StyleRefinement, WeakEntity, Window, point, size,
12};
13use project::Project;
14use schemars::JsonSchema;
15use serde::Deserialize;
16use settings::Settings;
17use std::{cell::RefCell, rc::Rc, sync::Arc};
18use ui::prelude::*;
19
20pub const HANDLE_HITBOX_SIZE: f32 = 4.0;
21const HORIZONTAL_MIN_SIZE: f32 = 80.;
22const VERTICAL_MIN_SIZE: f32 = 100.;
23
24/// One or many panes, arranged in a horizontal or vertical axis due to a split.
25/// Panes have all their tabs and capabilities preserved, and can be split again or resized.
26/// Single-pane group is a regular pane.
27#[derive(Clone)]
28pub struct PaneGroup {
29 pub root: Member,
30 pub is_center: bool,
31}
32
33pub struct PaneRenderResult {
34 pub element: gpui::AnyElement,
35 pub contains_active_pane: bool,
36}
37
38impl PaneGroup {
39 pub fn with_root(root: Member) -> Self {
40 Self {
41 root,
42 is_center: false,
43 }
44 }
45
46 pub fn new(pane: Entity<Pane>) -> Self {
47 Self::with_root(Member::Pane(pane))
48 }
49
50 pub fn set_is_center(&mut self, is_center: bool) {
51 self.is_center = is_center;
52 }
53
54 pub fn split(
55 &mut self,
56 old_pane: &Entity<Pane>,
57 new_pane: &Entity<Pane>,
58 direction: SplitDirection,
59 cx: &mut App,
60 ) {
61 let found = match &mut self.root {
62 Member::Pane(pane) => {
63 if pane == old_pane {
64 self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
65 true
66 } else {
67 false
68 }
69 }
70 Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
71 };
72
73 // If the pane wasn't found, fall back to splitting the first pane in the tree.
74 if !found {
75 let first_pane = self.root.first_pane();
76 match &mut self.root {
77 Member::Pane(_) => {
78 self.root = Member::new_axis(first_pane, new_pane.clone(), direction);
79 }
80 Member::Axis(axis) => {
81 let _ = axis.split(&first_pane, new_pane, direction);
82 }
83 }
84 }
85
86 self.mark_positions(cx);
87 }
88
89 pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
90 match &self.root {
91 Member::Pane(_) => None,
92 Member::Axis(axis) => axis.bounding_box_for_pane(pane),
93 }
94 }
95
96 pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
97 match &self.root {
98 Member::Pane(pane) => Some(pane),
99 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
100 }
101 }
102
103 /// Moves active pane to span the entire border in the given direction,
104 /// similar to Vim ctrl+w shift-[hjkl] motion.
105 ///
106 /// Returns:
107 /// - Ok(true) if it found and moved a pane
108 /// - Ok(false) if it found but did not move the pane
109 /// - Err(_) if it did not find the pane
110 pub fn move_to_border(
111 &mut self,
112 active_pane: &Entity<Pane>,
113 direction: SplitDirection,
114 cx: &mut App,
115 ) -> Result<bool> {
116 if let Some(pane) = self.find_pane_at_border(direction)
117 && pane == active_pane
118 {
119 return Ok(false);
120 }
121
122 if !self.remove_internal(active_pane)? {
123 return Ok(false);
124 }
125
126 if let Member::Axis(root) = &mut self.root
127 && direction.axis() == root.axis
128 {
129 let idx = if direction.increasing() {
130 root.members.len()
131 } else {
132 0
133 };
134 root.insert_pane(idx, active_pane);
135 self.mark_positions(cx);
136 return Ok(true);
137 }
138
139 let members = if direction.increasing() {
140 vec![self.root.clone(), Member::Pane(active_pane.clone())]
141 } else {
142 vec![Member::Pane(active_pane.clone()), self.root.clone()]
143 };
144 self.root = Member::Axis(PaneAxis::new(direction.axis(), members));
145 self.mark_positions(cx);
146 Ok(true)
147 }
148
149 fn find_pane_at_border(&self, direction: SplitDirection) -> Option<&Entity<Pane>> {
150 match &self.root {
151 Member::Pane(pane) => Some(pane),
152 Member::Axis(axis) => axis.find_pane_at_border(direction),
153 }
154 }
155
156 /// Returns:
157 /// - Ok(true) if it found and removed a pane
158 /// - Ok(false) if it found but did not remove the pane
159 /// - Err(_) if it did not find the pane
160 pub fn remove(&mut self, pane: &Entity<Pane>, cx: &mut App) -> Result<bool> {
161 let result = self.remove_internal(pane);
162 if let Ok(true) = result {
163 self.mark_positions(cx);
164 }
165 result
166 }
167
168 fn remove_internal(&mut self, pane: &Entity<Pane>) -> Result<bool> {
169 match &mut self.root {
170 Member::Pane(_) => Ok(false),
171 Member::Axis(axis) => {
172 if let Some(last_pane) = axis.remove(pane)? {
173 self.root = last_pane;
174 }
175 Ok(true)
176 }
177 }
178 }
179
180 pub fn resize(
181 &mut self,
182 pane: &Entity<Pane>,
183 direction: Axis,
184 amount: Pixels,
185 bounds: &Bounds<Pixels>,
186 cx: &mut App,
187 ) {
188 match &mut self.root {
189 Member::Pane(_) => {}
190 Member::Axis(axis) => {
191 let _ = axis.resize(pane, direction, amount, bounds);
192 }
193 };
194 self.mark_positions(cx);
195 }
196
197 pub fn reset_pane_sizes(&mut self, cx: &mut App) {
198 match &mut self.root {
199 Member::Pane(_) => {}
200 Member::Axis(axis) => {
201 let _ = axis.reset_pane_sizes();
202 }
203 };
204 self.mark_positions(cx);
205 }
206
207 pub fn swap(&mut self, from: &Entity<Pane>, to: &Entity<Pane>, cx: &mut App) {
208 match &mut self.root {
209 Member::Pane(_) => {}
210 Member::Axis(axis) => axis.swap(from, to),
211 };
212 self.mark_positions(cx);
213 }
214
215 pub fn mark_positions(&mut self, cx: &mut App) {
216 self.root.mark_positions(self.is_center, cx);
217 }
218
219 pub fn render(
220 &self,
221 zoomed: Option<&AnyWeakView>,
222 render_cx: &dyn PaneLeaderDecorator,
223 window: &mut Window,
224 cx: &mut App,
225 ) -> impl IntoElement {
226 self.root.render(0, zoomed, render_cx, window, cx).element
227 }
228
229 pub fn panes(&self) -> Vec<&Entity<Pane>> {
230 let mut panes = Vec::new();
231 self.root.collect_panes(&mut panes);
232 panes
233 }
234
235 pub fn first_pane(&self) -> Entity<Pane> {
236 self.root.first_pane()
237 }
238
239 pub fn last_pane(&self) -> Entity<Pane> {
240 self.root.last_pane()
241 }
242
243 pub fn find_pane_in_direction(
244 &mut self,
245 active_pane: &Entity<Pane>,
246 direction: SplitDirection,
247 cx: &App,
248 ) -> Option<&Entity<Pane>> {
249 let bounding_box = self.bounding_box_for_pane(active_pane)?;
250 let cursor = active_pane.read(cx).pixel_position_of_cursor(cx);
251 let center = match cursor {
252 Some(cursor) if bounding_box.contains(&cursor) => cursor,
253 _ => bounding_box.center(),
254 };
255
256 let distance_to_next = crate::HANDLE_HITBOX_SIZE;
257
258 let target = match direction {
259 SplitDirection::Left => {
260 Point::new(bounding_box.left() - distance_to_next.into(), center.y)
261 }
262 SplitDirection::Right => {
263 Point::new(bounding_box.right() + distance_to_next.into(), center.y)
264 }
265 SplitDirection::Up => {
266 Point::new(center.x, bounding_box.top() - distance_to_next.into())
267 }
268 SplitDirection::Down => {
269 Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
270 }
271 };
272 self.pane_at_pixel_position(target)
273 }
274
275 pub fn invert_axies(&mut self, cx: &mut App) {
276 self.root.invert_pane_axies();
277 self.mark_positions(cx);
278 }
279}
280
281#[derive(Debug, Clone)]
282pub enum Member {
283 Axis(PaneAxis),
284 Pane(Entity<Pane>),
285}
286
287impl Member {
288 pub fn mark_positions(&mut self, in_center_group: bool, cx: &mut App) {
289 match self {
290 Member::Axis(pane_axis) => {
291 for member in pane_axis.members.iter_mut() {
292 member.mark_positions(in_center_group, cx);
293 }
294 }
295 Member::Pane(entity) => entity.update(cx, |pane, _| {
296 pane.in_center_group = in_center_group;
297 }),
298 }
299 }
300}
301
302#[derive(Clone, Copy)]
303pub struct PaneRenderContext<'a> {
304 pub project: &'a Entity<Project>,
305 pub follower_states: &'a HashMap<CollaboratorId, FollowerState>,
306 pub active_call: Option<&'a dyn AnyActiveCall>,
307 pub active_pane: &'a Entity<Pane>,
308 pub app_state: &'a Arc<AppState>,
309 pub workspace: &'a WeakEntity<Workspace>,
310}
311
312#[derive(Default)]
313pub struct LeaderDecoration {
314 border: Option<Hsla>,
315 status_box: Option<AnyElement>,
316}
317
318pub trait PaneLeaderDecorator {
319 fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration;
320 fn active_pane(&self) -> &Entity<Pane>;
321 fn workspace(&self) -> &WeakEntity<Workspace>;
322}
323
324pub struct ActivePaneDecorator<'a> {
325 active_pane: &'a Entity<Pane>,
326 workspace: &'a WeakEntity<Workspace>,
327}
328
329impl<'a> ActivePaneDecorator<'a> {
330 pub fn new(active_pane: &'a Entity<Pane>, workspace: &'a WeakEntity<Workspace>) -> Self {
331 Self {
332 active_pane,
333 workspace,
334 }
335 }
336}
337
338impl PaneLeaderDecorator for ActivePaneDecorator<'_> {
339 fn decorate(&self, _: &Entity<Pane>, _: &App) -> LeaderDecoration {
340 LeaderDecoration::default()
341 }
342 fn active_pane(&self) -> &Entity<Pane> {
343 self.active_pane
344 }
345
346 fn workspace(&self) -> &WeakEntity<Workspace> {
347 self.workspace
348 }
349}
350
351impl PaneLeaderDecorator for PaneRenderContext<'_> {
352 fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration {
353 let follower_state = self.follower_states.iter().find_map(|(leader_id, state)| {
354 if state.center_pane == *pane {
355 Some((*leader_id, state))
356 } else {
357 None
358 }
359 });
360 let Some((leader_id, follower_state)) = follower_state else {
361 return LeaderDecoration::default();
362 };
363
364 let mut leader_color;
365 let status_box;
366 match leader_id {
367 CollaboratorId::PeerId(peer_id) => {
368 let Some(leader) = self
369 .active_call
370 .as_ref()
371 .and_then(|call| call.remote_participant_for_peer_id(peer_id, cx))
372 else {
373 return LeaderDecoration::default();
374 };
375
376 let is_in_unshared_view = follower_state.active_view_id.is_some_and(|view_id| {
377 !follower_state
378 .items_by_leader_view_id
379 .contains_key(&view_id)
380 });
381
382 let mut leader_join_data = None;
383 let leader_status_box = match leader.location {
384 ParticipantLocation::SharedProject {
385 project_id: leader_project_id,
386 } => {
387 if Some(leader_project_id) == self.project.read(cx).remote_id() {
388 is_in_unshared_view.then(|| {
389 Label::new(format!(
390 "{} is in an unshared pane",
391 leader.user.github_login
392 ))
393 })
394 } else {
395 leader_join_data = Some((leader_project_id, leader.user.id));
396 Some(Label::new(format!(
397 "Follow {} to their active project",
398 leader.user.github_login,
399 )))
400 }
401 }
402 ParticipantLocation::UnsharedProject => Some(Label::new(format!(
403 "{} is viewing an unshared Zed project",
404 leader.user.github_login
405 ))),
406 ParticipantLocation::External => Some(Label::new(format!(
407 "{} is viewing a window outside of Zed",
408 leader.user.github_login
409 ))),
410 };
411 status_box = leader_status_box.map(|status| {
412 div()
413 .absolute()
414 .w_96()
415 .bottom_3()
416 .right_3()
417 .elevation_2(cx)
418 .p_1()
419 .child(status)
420 .when_some(
421 leader_join_data,
422 |this, (leader_project_id, leader_user_id)| {
423 let app_state = self.app_state.clone();
424 this.cursor_pointer().on_mouse_down(
425 MouseButton::Left,
426 move |_, _, cx| {
427 crate::join_in_room_project(
428 leader_project_id,
429 leader_user_id,
430 app_state.clone(),
431 cx,
432 )
433 .detach_and_log_err(cx);
434 },
435 )
436 },
437 )
438 .into_any_element()
439 });
440 leader_color = cx
441 .theme()
442 .players()
443 .color_for_participant(leader.participant_index.0)
444 .cursor;
445 }
446 CollaboratorId::Agent => {
447 status_box = None;
448 leader_color = cx.theme().players().agent().cursor;
449 }
450 }
451
452 let is_in_panel = follower_state.dock_pane.is_some();
453 if is_in_panel {
454 leader_color.fade_out(0.75);
455 } else {
456 leader_color.fade_out(0.3);
457 }
458
459 LeaderDecoration {
460 status_box,
461 border: Some(leader_color),
462 }
463 }
464
465 fn active_pane(&self) -> &Entity<Pane> {
466 self.active_pane
467 }
468
469 fn workspace(&self) -> &WeakEntity<Workspace> {
470 self.workspace
471 }
472}
473
474impl Member {
475 fn new_axis(old_pane: Entity<Pane>, new_pane: Entity<Pane>, direction: SplitDirection) -> Self {
476 use Axis::*;
477 use SplitDirection::*;
478
479 let axis = match direction {
480 Up | Down => Vertical,
481 Left | Right => Horizontal,
482 };
483
484 let members = match direction {
485 Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
486 Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
487 };
488
489 Member::Axis(PaneAxis::new(axis, members))
490 }
491
492 fn first_pane(&self) -> Entity<Pane> {
493 match self {
494 Member::Axis(axis) => axis.members[0].first_pane(),
495 Member::Pane(pane) => pane.clone(),
496 }
497 }
498
499 fn last_pane(&self) -> Entity<Pane> {
500 match self {
501 Member::Axis(axis) => axis.members.last().unwrap().last_pane(),
502 Member::Pane(pane) => pane.clone(),
503 }
504 }
505
506 pub fn render(
507 &self,
508 basis: usize,
509 zoomed: Option<&AnyWeakView>,
510 render_cx: &dyn PaneLeaderDecorator,
511 window: &mut Window,
512 cx: &mut App,
513 ) -> PaneRenderResult {
514 match self {
515 Member::Pane(pane) => {
516 if zoomed == Some(&pane.downgrade().into()) {
517 return PaneRenderResult {
518 element: div().into_any(),
519 contains_active_pane: false,
520 };
521 }
522
523 let decoration = render_cx.decorate(pane, cx);
524 let is_active = pane == render_cx.active_pane();
525
526 PaneRenderResult {
527 element: div()
528 .relative()
529 .flex_1()
530 .size_full()
531 .child(
532 AnyView::from(pane.clone())
533 .cached(StyleRefinement::default().v_flex().size_full()),
534 )
535 .when_some(decoration.border, |this, color| {
536 this.child(
537 div()
538 .absolute()
539 .size_full()
540 .left_0()
541 .top_0()
542 .border_2()
543 .border_color(color),
544 )
545 })
546 .children(decoration.status_box)
547 .into_any(),
548 contains_active_pane: is_active,
549 }
550 }
551 Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx),
552 }
553 }
554
555 fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a Entity<Pane>>) {
556 match self {
557 Member::Axis(axis) => {
558 for member in &axis.members {
559 member.collect_panes(panes);
560 }
561 }
562 Member::Pane(pane) => panes.push(pane),
563 }
564 }
565
566 fn invert_pane_axies(&mut self) {
567 match self {
568 Self::Axis(axis) => {
569 axis.axis = axis.axis.invert();
570 for member in axis.members.iter_mut() {
571 member.invert_pane_axies();
572 }
573 }
574 Self::Pane(_) => {}
575 }
576 }
577}
578
579#[derive(Debug, Clone)]
580pub struct PaneAxisState(Rc<RefCell<PaneAxisStateInner>>);
581
582#[derive(Debug)]
583struct PaneAxisStateInner {
584 entries: Vec<PaneAxisStateEntry>,
585}
586
587#[derive(Clone, Copy, Debug)]
588struct PaneAxisStateEntry {
589 flex: f32,
590 bounding_box: Option<Bounds<Pixels>>,
591}
592
593impl PaneAxisState {
594 pub fn new(member_count: usize) -> Self {
595 Self(Rc::new(RefCell::new(PaneAxisStateInner {
596 entries: vec![
597 PaneAxisStateEntry {
598 flex: 1.,
599 bounding_box: None
600 };
601 member_count
602 ],
603 })))
604 }
605
606 fn with_flexes(flexes: Vec<f32>) -> Self {
607 Self(Rc::new(RefCell::new(PaneAxisStateInner {
608 entries: flexes
609 .into_iter()
610 .map(|flex| PaneAxisStateEntry {
611 flex,
612 bounding_box: None,
613 })
614 .collect(),
615 })))
616 }
617
618 pub fn flexes(&self) -> Vec<f32> {
619 self.0.borrow().entries.iter().map(|e| e.flex).collect()
620 }
621
622 pub fn len(&self) -> usize {
623 self.0.borrow().entries.len()
624 }
625
626 fn resize(&self, len: usize) {
627 let mut inner = self.0.borrow_mut();
628 inner.entries.clear();
629 inner.entries.resize(
630 len,
631 PaneAxisStateEntry {
632 flex: 1.,
633 bounding_box: None,
634 },
635 );
636 }
637
638 fn reset_flexes(&self) {
639 let mut inner = self.0.borrow_mut();
640 inner.entries.iter_mut().for_each(|e| {
641 e.flex = 1.;
642 e.bounding_box = None;
643 });
644 }
645
646 fn bounding_box_at(&self, index: usize) -> Option<Bounds<Pixels>> {
647 self.0
648 .borrow()
649 .entries
650 .get(index)
651 .and_then(|e| e.bounding_box)
652 }
653
654 fn bounds(&self) -> Option<Bounds<Pixels>> {
655 self.0
656 .borrow()
657 .entries
658 .iter()
659 .filter_map(|e| e.bounding_box)
660 .reduce(|acc, e| acc.union(&e))
661 }
662}
663
664#[derive(Debug, Clone)]
665pub struct PaneAxis {
666 pub axis: Axis,
667 pub members: Vec<Member>,
668 pub state: PaneAxisState,
669}
670
671impl PaneAxis {
672 pub fn new(axis: Axis, members: Vec<Member>) -> Self {
673 let state = PaneAxisState::new(members.len());
674 Self {
675 axis,
676 members,
677 state,
678 }
679 }
680
681 pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
682 let mut flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
683 if flexes.len() != members.len()
684 || (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() >= 0.001
685 {
686 flexes = vec![1.; members.len()];
687 }
688
689 let state = PaneAxisState::with_flexes(flexes);
690 Self {
691 axis,
692 members,
693 state,
694 }
695 }
696
697 fn split(
698 &mut self,
699 old_pane: &Entity<Pane>,
700 new_pane: &Entity<Pane>,
701 direction: SplitDirection,
702 ) -> bool {
703 for (mut idx, member) in self.members.iter_mut().enumerate() {
704 match member {
705 Member::Axis(axis) => {
706 if axis.split(old_pane, new_pane, direction) {
707 return true;
708 }
709 }
710 Member::Pane(pane) => {
711 if pane == old_pane {
712 if direction.axis() == self.axis {
713 if direction.increasing() {
714 idx += 1;
715 }
716 self.insert_pane(idx, new_pane);
717 } else {
718 *member =
719 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
720 }
721 return true;
722 }
723 }
724 }
725 }
726 false
727 }
728
729 fn insert_pane(&mut self, idx: usize, new_pane: &Entity<Pane>) {
730 self.members.insert(idx, Member::Pane(new_pane.clone()));
731 self.state.resize(self.members.len());
732 }
733
734 fn find_pane_at_border(&self, direction: SplitDirection) -> Option<&Entity<Pane>> {
735 if self.axis != direction.axis() {
736 return None;
737 }
738 let member = if direction.increasing() {
739 self.members.last()
740 } else {
741 self.members.first()
742 };
743 member.and_then(|e| match e {
744 Member::Pane(pane) => Some(pane),
745 Member::Axis(_) => None,
746 })
747 }
748
749 fn remove(&mut self, pane_to_remove: &Entity<Pane>) -> Result<Option<Member>> {
750 let mut found_pane = false;
751 let mut remove_member = None;
752 for (idx, member) in self.members.iter_mut().enumerate() {
753 match member {
754 Member::Axis(axis) => {
755 if let Ok(last_pane) = axis.remove(pane_to_remove) {
756 if let Some(last_pane) = last_pane {
757 *member = last_pane;
758 }
759 found_pane = true;
760 break;
761 }
762 }
763 Member::Pane(pane) => {
764 if pane == pane_to_remove {
765 found_pane = true;
766 remove_member = Some(idx);
767 break;
768 }
769 }
770 }
771 }
772
773 if found_pane {
774 if let Some(idx) = remove_member {
775 self.members.remove(idx);
776 self.state.resize(self.members.len());
777 }
778
779 if self.members.len() == 1 {
780 let result = self.members.pop();
781 self.state.resize(self.members.len());
782 Ok(result)
783 } else {
784 Ok(None)
785 }
786 } else {
787 anyhow::bail!("Pane not found");
788 }
789 }
790
791 fn reset_pane_sizes(&self) {
792 self.state.resize(self.members.len());
793 for member in self.members.iter() {
794 if let Member::Axis(axis) = member {
795 axis.reset_pane_sizes();
796 }
797 }
798 }
799
800 fn resize(
801 &mut self,
802 pane: &Entity<Pane>,
803 axis: Axis,
804 amount: Pixels,
805 bounds: &Bounds<Pixels>,
806 ) -> Option<bool> {
807 let container_size = self.state.bounds().unwrap_or(*bounds).size;
808
809 let found_pane = self
810 .members
811 .iter()
812 .any(|member| matches!(member, Member::Pane(p) if p == pane));
813
814 if found_pane && self.axis != axis {
815 return Some(false); // pane found but this is not the correct axis direction
816 }
817 let mut found_axis_index: Option<usize> = None;
818 if !found_pane {
819 for (i, pa) in self.members.iter_mut().enumerate() {
820 if let Member::Axis(pa) = pa
821 && let Some(done) = pa.resize(pane, axis, amount, bounds)
822 {
823 if done {
824 return Some(true); // pane found and operations already done
825 } else if self.axis != axis {
826 return Some(false); // pane found but this is not the correct axis direction
827 } else {
828 found_axis_index = Some(i); // pane found and this is correct direction
829 }
830 }
831 }
832 found_axis_index?; // no pane found
833 }
834
835 let min_size = match axis {
836 Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
837 Axis::Vertical => px(VERTICAL_MIN_SIZE),
838 };
839 let mut state = self.state.0.borrow_mut();
840
841 let ix = if found_pane {
842 self.members.iter().position(|m| {
843 if let Member::Pane(p) = m {
844 p == pane
845 } else {
846 false
847 }
848 })
849 } else {
850 found_axis_index
851 };
852
853 if ix.is_none() {
854 return Some(true);
855 }
856
857 let ix = ix.unwrap_or(0);
858
859 let size = move |ix: usize, state: &PaneAxisStateInner| {
860 container_size.along(axis) * (state.entries[ix].flex / state.entries.len() as f32)
861 };
862
863 // Don't allow resizing to less than the minimum size, if elements are already too small
864 if min_size - px(1.) > size(ix, &state) {
865 return Some(true);
866 }
867
868 let flex_changes = |pixel_dx, target_ix: usize, next: isize, state: &PaneAxisStateInner| {
869 let flex_change = state.entries.len() as f32 * pixel_dx / container_size.along(axis);
870 let current_target_flex = state.entries[target_ix].flex + flex_change;
871 let next_target_flex =
872 state.entries[(target_ix as isize + next) as usize].flex - flex_change;
873 (current_target_flex, next_target_flex)
874 };
875
876 let apply_changes = |current_ix: usize,
877 proposed_current_pixel_change: Pixels,
878 state: &mut PaneAxisStateInner| {
879 let next_target_size = Pixels::max(
880 size(current_ix + 1, state) - proposed_current_pixel_change,
881 min_size,
882 );
883 let current_target_size = Pixels::max(
884 size(current_ix, state) + size(current_ix + 1, state) - next_target_size,
885 min_size,
886 );
887
888 let current_pixel_change = current_target_size - size(current_ix, state);
889
890 let (current_target_flex, next_target_flex) =
891 flex_changes(current_pixel_change, current_ix, 1, state);
892
893 state.entries[current_ix].flex = current_target_flex;
894 state.entries[current_ix + 1].flex = next_target_flex;
895 };
896
897 if ix + 1 == state.entries.len() {
898 apply_changes(ix - 1, -1.0 * amount, &mut state);
899 } else {
900 apply_changes(ix, amount, &mut state);
901 }
902 Some(true)
903 }
904
905 fn swap(&mut self, from: &Entity<Pane>, to: &Entity<Pane>) {
906 for member in self.members.iter_mut() {
907 match member {
908 Member::Axis(axis) => axis.swap(from, to),
909 Member::Pane(pane) => {
910 if pane == from {
911 *member = Member::Pane(to.clone());
912 } else if pane == to {
913 *member = Member::Pane(from.clone())
914 }
915 }
916 }
917 }
918 }
919
920 fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
921 debug_assert!(self.members.len() == self.state.len());
922
923 for (idx, member) in self.members.iter().enumerate() {
924 match member {
925 Member::Pane(found) => {
926 if pane == found {
927 return self.state.bounding_box_at(idx);
928 }
929 }
930 Member::Axis(axis) => {
931 if let Some(rect) = axis.bounding_box_for_pane(pane) {
932 return Some(rect);
933 }
934 }
935 }
936 }
937 None
938 }
939
940 fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
941 debug_assert!(self.members.len() == self.state.len());
942
943 for (idx, member) in self.members.iter().enumerate() {
944 if let Some(coordinates) = self.state.bounding_box_at(idx)
945 && coordinates.contains(&coordinate)
946 {
947 return match member {
948 Member::Pane(found) => Some(found),
949 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
950 };
951 }
952 }
953 None
954 }
955
956 fn render(
957 &self,
958 basis: usize,
959 zoomed: Option<&AnyWeakView>,
960 render_cx: &dyn PaneLeaderDecorator,
961 window: &mut Window,
962 cx: &mut App,
963 ) -> PaneRenderResult {
964 debug_assert!(self.members.len() == self.state.len());
965 let mut active_pane_ix = None;
966 let mut contains_active_pane = false;
967 let mut is_leaf_pane = vec![false; self.members.len()];
968
969 let rendered_children = self
970 .members
971 .iter()
972 .enumerate()
973 .map(|(ix, member)| {
974 match member {
975 Member::Pane(pane) => {
976 is_leaf_pane[ix] = true;
977 if pane == render_cx.active_pane() {
978 active_pane_ix = Some(ix);
979 contains_active_pane = true;
980 }
981 }
982 Member::Axis(_) => {
983 is_leaf_pane[ix] = false;
984 }
985 }
986
987 let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx);
988 if result.contains_active_pane {
989 contains_active_pane = true;
990 }
991 result.element.into_any_element()
992 })
993 .collect::<Vec<_>>();
994
995 let element = pane_axis(
996 self.axis,
997 basis,
998 self.state.clone(),
999 render_cx.workspace().clone(),
1000 )
1001 .with_is_leaf_pane_mask(is_leaf_pane)
1002 .children(rendered_children)
1003 .with_active_pane(active_pane_ix)
1004 .into_any_element();
1005
1006 PaneRenderResult {
1007 element,
1008 contains_active_pane,
1009 }
1010 }
1011}
1012
1013#[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
1014#[serde(rename_all = "snake_case")]
1015pub enum SplitDirection {
1016 Up,
1017 Down,
1018 Left,
1019 Right,
1020}
1021
1022impl std::fmt::Display for SplitDirection {
1023 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1024 match self {
1025 SplitDirection::Up => write!(f, "up"),
1026 SplitDirection::Down => write!(f, "down"),
1027 SplitDirection::Left => write!(f, "left"),
1028 SplitDirection::Right => write!(f, "right"),
1029 }
1030 }
1031}
1032
1033impl SplitDirection {
1034 pub fn all() -> [Self; 4] {
1035 [Self::Up, Self::Down, Self::Left, Self::Right]
1036 }
1037
1038 pub fn vertical(cx: &mut App) -> Self {
1039 match WorkspaceSettings::get_global(cx).pane_split_direction_vertical {
1040 PaneSplitDirectionVertical::Left => SplitDirection::Left,
1041 PaneSplitDirectionVertical::Right => SplitDirection::Right,
1042 }
1043 }
1044
1045 pub fn horizontal(cx: &mut App) -> Self {
1046 match WorkspaceSettings::get_global(cx).pane_split_direction_horizontal {
1047 PaneSplitDirectionHorizontal::Down => SplitDirection::Down,
1048 PaneSplitDirectionHorizontal::Up => SplitDirection::Up,
1049 }
1050 }
1051
1052 pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
1053 match self {
1054 Self::Up => rect.origin.y,
1055 Self::Down => rect.bottom_left().y,
1056 Self::Left => rect.bottom_left().x,
1057 Self::Right => rect.bottom_right().x,
1058 }
1059 }
1060
1061 pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
1062 match self {
1063 Self::Up => Bounds {
1064 origin: bounds.origin,
1065 size: size(bounds.size.width, length),
1066 },
1067 Self::Down => Bounds {
1068 origin: point(bounds.bottom_left().x, bounds.bottom_left().y - length),
1069 size: size(bounds.size.width, length),
1070 },
1071 Self::Left => Bounds {
1072 origin: bounds.origin,
1073 size: size(length, bounds.size.height),
1074 },
1075 Self::Right => Bounds {
1076 origin: point(bounds.bottom_right().x - length, bounds.bottom_left().y),
1077 size: size(length, bounds.size.height),
1078 },
1079 }
1080 }
1081
1082 pub fn axis(&self) -> Axis {
1083 match self {
1084 Self::Up | Self::Down => Axis::Vertical,
1085 Self::Left | Self::Right => Axis::Horizontal,
1086 }
1087 }
1088
1089 pub fn increasing(&self) -> bool {
1090 match self {
1091 Self::Left | Self::Up => false,
1092 Self::Down | Self::Right => true,
1093 }
1094 }
1095
1096 pub fn opposite(&self) -> SplitDirection {
1097 match self {
1098 Self::Down => Self::Up,
1099 Self::Up => Self::Down,
1100 Self::Left => Self::Right,
1101 Self::Right => Self::Left,
1102 }
1103 }
1104}
1105
1106pub mod element {
1107 use std::mem;
1108 use std::{cell::RefCell, iter, rc::Rc};
1109
1110 use gpui::{
1111 Along, AnyElement, App, Axis, BorderStyle, Bounds, Element, GlobalElementId,
1112 HitboxBehavior, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
1113 Pixels, Point, Size, Style, WeakEntity, Window, px, relative, size,
1114 };
1115 use gpui::{CursorStyle, Hitbox};
1116 use settings::Settings;
1117 use smallvec::SmallVec;
1118 use ui::prelude::*;
1119 use util::ResultExt;
1120
1121 use crate::Workspace;
1122
1123 use crate::WorkspaceSettings;
1124 use crate::pane_group::PaneAxisStateEntry;
1125
1126 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, PaneAxisState, VERTICAL_MIN_SIZE};
1127
1128 const DIVIDER_SIZE: f32 = 1.0;
1129
1130 pub fn pane_axis(
1131 axis: Axis,
1132 basis: usize,
1133 state: PaneAxisState,
1134 workspace: WeakEntity<Workspace>,
1135 ) -> PaneAxisElement {
1136 PaneAxisElement {
1137 axis,
1138 basis,
1139 state,
1140 children: SmallVec::new(),
1141 active_pane_ix: None,
1142 workspace,
1143 is_leaf_pane_mask: Vec::new(),
1144 }
1145 }
1146
1147 pub struct PaneAxisElement {
1148 axis: Axis,
1149 basis: usize,
1150 state: PaneAxisState,
1151 children: SmallVec<[AnyElement; 2]>,
1152 active_pane_ix: Option<usize>,
1153 workspace: WeakEntity<Workspace>,
1154 // Track which children are leaf panes (Member::Pane) vs axes (Member::Axis)
1155 is_leaf_pane_mask: Vec<bool>,
1156 }
1157
1158 pub struct PaneAxisLayout {
1159 dragged_handle: Rc<RefCell<Option<usize>>>,
1160 children: Vec<PaneAxisChildLayout>,
1161 }
1162
1163 struct PaneAxisChildLayout {
1164 bounds: Bounds<Pixels>,
1165 element: AnyElement,
1166 handle: Option<PaneAxisHandleLayout>,
1167 is_leaf_pane: bool,
1168 }
1169
1170 struct PaneAxisHandleLayout {
1171 hitbox: Hitbox,
1172 divider_bounds: Bounds<Pixels>,
1173 }
1174
1175 impl PaneAxisElement {
1176 pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
1177 self.active_pane_ix = active_pane_ix;
1178 self
1179 }
1180
1181 pub fn with_is_leaf_pane_mask(mut self, mask: Vec<bool>) -> Self {
1182 self.is_leaf_pane_mask = mask;
1183 self
1184 }
1185
1186 fn compute_resize(
1187 state: &PaneAxisState,
1188 e: &MouseMoveEvent,
1189 ix: usize,
1190 axis: Axis,
1191 child_start: Point<Pixels>,
1192 container_size: Size<Pixels>,
1193 workspace: WeakEntity<Workspace>,
1194 window: &mut Window,
1195 cx: &mut App,
1196 ) {
1197 let entries = &mut state.0.borrow_mut().entries;
1198
1199 let min_size = match axis {
1200 Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
1201 Axis::Vertical => px(VERTICAL_MIN_SIZE),
1202 };
1203
1204 #[cfg(debug_assertions)]
1205 check_flex_values_in_bounds(entries);
1206
1207 // Math to convert a flex value to a pixel value
1208 let size = move |ix: usize, entries: &[PaneAxisStateEntry]| {
1209 container_size.along(axis) * (entries[ix].flex / entries.len() as f32)
1210 };
1211
1212 // Don't allow resizing to less than the minimum size, if elements are already too small
1213 if min_size - px(1.) > size(ix, entries) {
1214 return;
1215 }
1216
1217 // This is basically a "bucket" of pixel changes that need to be applied in response to this
1218 // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels
1219 let mut proposed_current_pixel_change =
1220 (e.position - child_start).along(axis) - size(ix, entries);
1221
1222 // This takes a pixel change, and computes the flex changes that correspond to this pixel change
1223 // as well as the next one, for some reason
1224 let flex_changes =
1225 |pixel_dx, target_ix: usize, next: isize, entries: &[PaneAxisStateEntry]| {
1226 let flex_change = pixel_dx / container_size.along(axis);
1227 let current_target_flex = entries[target_ix].flex + flex_change;
1228 let next_target_flex =
1229 entries[(target_ix as isize + next) as usize].flex - flex_change;
1230 (current_target_flex, next_target_flex)
1231 };
1232
1233 // Generate the list of flex successors, from the current index.
1234 // If you're dragging column 3 forward, out of 6 columns, then this code will produce [4, 5, 6]
1235 // If you're dragging column 3 backward, out of 6 columns, then this code will produce [2, 1, 0]
1236 let mut successors = iter::from_fn({
1237 let forward = proposed_current_pixel_change > px(0.);
1238 let mut ix_offset = 0;
1239 let len = entries.len();
1240 move || {
1241 let result = if forward {
1242 (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
1243 } else {
1244 (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
1245 };
1246
1247 ix_offset += 1;
1248
1249 result
1250 }
1251 });
1252
1253 // Now actually loop over these, and empty our bucket of pixel changes
1254 while proposed_current_pixel_change.abs() > px(0.) {
1255 let Some(current_ix) = successors.next() else {
1256 break;
1257 };
1258
1259 let next_target_size = Pixels::max(
1260 size(current_ix + 1, entries) - proposed_current_pixel_change,
1261 min_size,
1262 );
1263
1264 let current_target_size = Pixels::max(
1265 size(current_ix, entries) + size(current_ix + 1, entries) - next_target_size,
1266 min_size,
1267 );
1268
1269 let current_pixel_change = current_target_size - size(current_ix, entries);
1270
1271 let (current_target_flex, next_target_flex) =
1272 flex_changes(current_pixel_change, current_ix, 1, entries);
1273
1274 entries[current_ix].flex = current_target_flex;
1275 entries[current_ix + 1].flex = next_target_flex;
1276
1277 proposed_current_pixel_change -= current_pixel_change;
1278 }
1279
1280 workspace
1281 .update(cx, |this, cx| this.serialize_workspace(window, cx))
1282 .log_err();
1283 cx.stop_propagation();
1284 window.refresh();
1285 }
1286
1287 fn layout_handle(
1288 axis: Axis,
1289 pane_bounds: Bounds<Pixels>,
1290 window: &mut Window,
1291 _cx: &mut App,
1292 ) -> PaneAxisHandleLayout {
1293 let handle_bounds = Bounds {
1294 origin: pane_bounds.origin.apply_along(axis, |origin| {
1295 origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
1296 }),
1297 size: pane_bounds
1298 .size
1299 .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
1300 };
1301 let divider_bounds = Bounds {
1302 origin: pane_bounds
1303 .origin
1304 .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
1305 size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
1306 };
1307
1308 PaneAxisHandleLayout {
1309 hitbox: window.insert_hitbox(handle_bounds, HitboxBehavior::BlockMouse),
1310 divider_bounds,
1311 }
1312 }
1313 }
1314
1315 impl IntoElement for PaneAxisElement {
1316 type Element = Self;
1317
1318 fn into_element(self) -> Self::Element {
1319 self
1320 }
1321 }
1322
1323 impl Element for PaneAxisElement {
1324 type RequestLayoutState = ();
1325 type PrepaintState = PaneAxisLayout;
1326
1327 fn id(&self) -> Option<ElementId> {
1328 Some(self.basis.into())
1329 }
1330
1331 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1332 None
1333 }
1334
1335 fn request_layout(
1336 &mut self,
1337 _global_id: Option<&GlobalElementId>,
1338 _inspector_id: Option<&gpui::InspectorElementId>,
1339 window: &mut Window,
1340 cx: &mut App,
1341 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
1342 let style = Style {
1343 flex_grow: 1.,
1344 flex_shrink: 1.,
1345 flex_basis: relative(0.).into(),
1346 size: size(relative(1.).into(), relative(1.).into()),
1347 ..Style::default()
1348 };
1349 (window.request_layout(style, None, cx), ())
1350 }
1351
1352 fn prepaint(
1353 &mut self,
1354 global_id: Option<&GlobalElementId>,
1355 _inspector_id: Option<&gpui::InspectorElementId>,
1356 bounds: Bounds<Pixels>,
1357 _state: &mut Self::RequestLayoutState,
1358 window: &mut Window,
1359 cx: &mut App,
1360 ) -> PaneAxisLayout {
1361 let dragged_handle = window.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
1362 global_id.unwrap(),
1363 |state, _cx| {
1364 let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
1365 (state.clone(), state)
1366 },
1367 );
1368 let entries = &mut self.state.0.borrow_mut().entries;
1369
1370 let len = self.children.len();
1371 debug_assert!(entries.len() == len);
1372
1373 #[cfg(debug_assertions)]
1374 check_flex_values_in_bounds(entries);
1375
1376 let total_flex = len as f32;
1377
1378 let mut origin = bounds.origin;
1379 let space_per_flex = bounds.size.along(self.axis) / total_flex;
1380
1381 // self.state.0.borrow_mut().bounding_boxes.clear();
1382
1383 let mut layout = PaneAxisLayout {
1384 dragged_handle,
1385 children: Vec::new(),
1386 };
1387 for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
1388 let child_flex = entries[ix].flex;
1389
1390 let child_size = bounds
1391 .size
1392 .apply_along(self.axis, |_| space_per_flex * child_flex)
1393 .map(|d| d.round());
1394
1395 let child_bounds = Bounds {
1396 origin,
1397 size: child_size,
1398 };
1399
1400 entries[ix].bounding_box = Some(child_bounds);
1401 child.layout_as_root(child_size.into(), window, cx);
1402 child.prepaint_at(origin, window, cx);
1403
1404 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
1405
1406 let is_leaf_pane = self.is_leaf_pane_mask.get(ix).copied().unwrap_or(true);
1407
1408 layout.children.push(PaneAxisChildLayout {
1409 bounds: child_bounds,
1410 element: child,
1411 handle: None,
1412 is_leaf_pane,
1413 })
1414 }
1415
1416 for (ix, child_layout) in layout.children.iter_mut().enumerate() {
1417 if ix < len - 1 {
1418 child_layout.handle = Some(Self::layout_handle(
1419 self.axis,
1420 child_layout.bounds,
1421 window,
1422 cx,
1423 ));
1424 }
1425 }
1426
1427 layout
1428 }
1429
1430 fn paint(
1431 &mut self,
1432 _id: Option<&GlobalElementId>,
1433 _inspector_id: Option<&gpui::InspectorElementId>,
1434 bounds: gpui::Bounds<ui::prelude::Pixels>,
1435 _: &mut Self::RequestLayoutState,
1436 layout: &mut Self::PrepaintState,
1437 window: &mut Window,
1438 cx: &mut App,
1439 ) {
1440 for child in &mut layout.children {
1441 child.element.paint(window, cx);
1442 }
1443
1444 let overlay_opacity = WorkspaceSettings::get(None, cx)
1445 .active_pane_modifiers
1446 .inactive_opacity
1447 .map(|val| val.0.clamp(0.0, 1.0))
1448 .and_then(|val| (val <= 1.).then_some(val));
1449
1450 let mut overlay_background = cx.theme().colors().editor_background;
1451 if let Some(opacity) = overlay_opacity {
1452 overlay_background.fade_out(opacity);
1453 }
1454
1455 let overlay_border = WorkspaceSettings::get(None, cx)
1456 .active_pane_modifiers
1457 .border_size
1458 .and_then(|val| (val >= 0.).then_some(val));
1459
1460 for (ix, child) in &mut layout.children.iter_mut().enumerate() {
1461 if overlay_opacity.is_some() || overlay_border.is_some() {
1462 // the overlay has to be painted in origin+1px with size width-1px
1463 // in order to accommodate the divider between panels
1464 let overlay_bounds = Bounds {
1465 origin: child
1466 .bounds
1467 .origin
1468 .apply_along(Axis::Horizontal, |val| val + px(1.)),
1469 size: child
1470 .bounds
1471 .size
1472 .apply_along(Axis::Horizontal, |val| val - px(1.)),
1473 };
1474
1475 if overlay_opacity.is_some()
1476 && child.is_leaf_pane
1477 && self.active_pane_ix != Some(ix)
1478 {
1479 window.paint_quad(gpui::fill(overlay_bounds, overlay_background));
1480 }
1481
1482 if let Some(border) = overlay_border
1483 && self.active_pane_ix == Some(ix)
1484 && child.is_leaf_pane
1485 {
1486 window.paint_quad(gpui::quad(
1487 overlay_bounds,
1488 0.,
1489 gpui::transparent_black(),
1490 border,
1491 cx.theme().colors().border_selected,
1492 BorderStyle::Solid,
1493 ));
1494 }
1495 }
1496
1497 if let Some(handle) = child.handle.as_mut() {
1498 let cursor_style = match self.axis {
1499 Axis::Vertical => CursorStyle::ResizeRow,
1500 Axis::Horizontal => CursorStyle::ResizeColumn,
1501 };
1502
1503 if layout
1504 .dragged_handle
1505 .borrow()
1506 .is_some_and(|dragged_ix| dragged_ix == ix)
1507 {
1508 window.set_window_cursor_style(cursor_style);
1509 } else {
1510 window.set_cursor_style(cursor_style, &handle.hitbox);
1511 }
1512
1513 window.paint_quad(gpui::fill(
1514 handle.divider_bounds,
1515 cx.theme().colors().pane_group_border,
1516 ));
1517
1518 window.on_mouse_event({
1519 let dragged_handle = layout.dragged_handle.clone();
1520 let state = self.state.clone();
1521 let workspace = self.workspace.clone();
1522 let handle_hitbox = handle.hitbox.clone();
1523 move |e: &MouseDownEvent, phase, window, cx| {
1524 if phase.bubble() && handle_hitbox.is_hovered(window) {
1525 dragged_handle.replace(Some(ix));
1526 if e.click_count >= 2 {
1527 state.reset_flexes();
1528 workspace
1529 .update(cx, |this, cx| this.serialize_workspace(window, cx))
1530 .log_err();
1531
1532 window.refresh();
1533 }
1534 cx.stop_propagation();
1535 }
1536 }
1537 });
1538 window.on_mouse_event({
1539 let workspace = self.workspace.clone();
1540 let dragged_handle = layout.dragged_handle.clone();
1541 let state = self.state.clone();
1542 let child_bounds = child.bounds;
1543 let axis = self.axis;
1544 move |e: &MouseMoveEvent, phase, window, cx| {
1545 let dragged_handle = dragged_handle.borrow();
1546 if phase.bubble() && *dragged_handle == Some(ix) {
1547 Self::compute_resize(
1548 &state,
1549 e,
1550 ix,
1551 axis,
1552 child_bounds.origin,
1553 bounds.size,
1554 workspace.clone(),
1555 window,
1556 cx,
1557 )
1558 }
1559 }
1560 });
1561 }
1562 }
1563
1564 window.on_mouse_event({
1565 let dragged_handle = layout.dragged_handle.clone();
1566 move |_: &MouseUpEvent, phase, _window, _cx| {
1567 if phase.bubble() {
1568 dragged_handle.replace(None);
1569 }
1570 }
1571 });
1572 }
1573 }
1574
1575 impl ParentElement for PaneAxisElement {
1576 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1577 self.children.extend(elements)
1578 }
1579 }
1580
1581 #[cfg(debug_assertions)]
1582 fn check_flex_values_in_bounds(inner: &[PaneAxisStateEntry]) {
1583 if (inner.iter().map(|e| e.flex).sum::<f32>() - inner.len() as f32).abs() >= 0.001 {
1584 panic!(
1585 "flex values out of bounds: {:?}",
1586 inner.iter().map(|e| e.flex).collect::<Vec<_>>()
1587 );
1588 }
1589 }
1590}