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