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