pane_group.rs

  1use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
  2use anyhow::{anyhow, Result};
  3use call::{ActiveCall, ParticipantLocation};
  4use collections::HashMap;
  5use gpui::{
  6    point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
  7    Point, View, ViewContext,
  8};
  9use parking_lot::Mutex;
 10use project::Project;
 11use serde::Deserialize;
 12use std::sync::Arc;
 13use ui::prelude::*;
 14
 15pub const HANDLE_HITBOX_SIZE: f32 = 4.0;
 16const HORIZONTAL_MIN_SIZE: f32 = 80.;
 17const VERTICAL_MIN_SIZE: f32 = 100.;
 18
 19#[derive(Clone)]
 20pub struct PaneGroup {
 21    pub(crate) root: Member,
 22}
 23
 24impl PaneGroup {
 25    pub(crate) fn with_root(root: Member) -> Self {
 26        Self { root }
 27    }
 28
 29    pub fn new(pane: View<Pane>) -> Self {
 30        Self {
 31            root: Member::Pane(pane),
 32        }
 33    }
 34
 35    pub fn split(
 36        &mut self,
 37        old_pane: &View<Pane>,
 38        new_pane: &View<Pane>,
 39        direction: SplitDirection,
 40    ) -> Result<()> {
 41        match &mut self.root {
 42            Member::Pane(pane) => {
 43                if pane == old_pane {
 44                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 45                    Ok(())
 46                } else {
 47                    Err(anyhow!("Pane not found"))
 48                }
 49            }
 50            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
 51        }
 52    }
 53
 54    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
 55        match &self.root {
 56            Member::Pane(_) => None,
 57            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
 58        }
 59    }
 60
 61    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
 62        match &self.root {
 63            Member::Pane(pane) => Some(pane),
 64            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 65        }
 66    }
 67
 68    /// Returns:
 69    /// - Ok(true) if it found and removed a pane
 70    /// - Ok(false) if it found but did not remove the pane
 71    /// - Err(_) if it did not find the pane
 72    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
 73        match &mut self.root {
 74            Member::Pane(_) => Ok(false),
 75            Member::Axis(axis) => {
 76                if let Some(last_pane) = axis.remove(pane)? {
 77                    self.root = last_pane;
 78                }
 79                Ok(true)
 80            }
 81        }
 82    }
 83
 84    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
 85        match &mut self.root {
 86            Member::Pane(_) => {}
 87            Member::Axis(axis) => axis.swap(from, to),
 88        };
 89    }
 90
 91    pub(crate) fn render(
 92        &self,
 93        project: &Model<Project>,
 94        follower_states: &HashMap<View<Pane>, FollowerState>,
 95        active_call: Option<&Model<ActiveCall>>,
 96        active_pane: &View<Pane>,
 97        zoomed: Option<&AnyWeakView>,
 98        app_state: &Arc<AppState>,
 99        cx: &mut ViewContext<Workspace>,
100    ) -> impl IntoElement {
101        self.root.render(
102            project,
103            0,
104            follower_states,
105            active_call,
106            active_pane,
107            zoomed,
108            app_state,
109            cx,
110        )
111    }
112
113    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
114        let mut panes = Vec::new();
115        self.root.collect_panes(&mut panes);
116        panes
117    }
118
119    pub(crate) fn first_pane(&self) -> View<Pane> {
120        self.root.first_pane()
121    }
122}
123
124#[derive(Clone)]
125pub(crate) enum Member {
126    Axis(PaneAxis),
127    Pane(View<Pane>),
128}
129
130impl Member {
131    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
132        use Axis::*;
133        use SplitDirection::*;
134
135        let axis = match direction {
136            Up | Down => Vertical,
137            Left | Right => Horizontal,
138        };
139
140        let members = match direction {
141            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
142            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
143        };
144
145        Member::Axis(PaneAxis::new(axis, members))
146    }
147
148    fn contains(&self, needle: &View<Pane>) -> bool {
149        match self {
150            Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
151            Member::Pane(pane) => pane == needle,
152        }
153    }
154
155    fn first_pane(&self) -> View<Pane> {
156        match self {
157            Member::Axis(axis) => axis.members[0].first_pane(),
158            Member::Pane(pane) => pane.clone(),
159        }
160    }
161
162    pub fn render(
163        &self,
164        project: &Model<Project>,
165        basis: usize,
166        follower_states: &HashMap<View<Pane>, FollowerState>,
167        active_call: Option<&Model<ActiveCall>>,
168        active_pane: &View<Pane>,
169        zoomed: Option<&AnyWeakView>,
170        app_state: &Arc<AppState>,
171        cx: &mut ViewContext<Workspace>,
172    ) -> impl IntoElement {
173        match self {
174            Member::Pane(pane) => {
175                if zoomed == Some(&pane.downgrade().into()) {
176                    return div().into_any();
177                }
178
179                let follower_state = follower_states.get(pane);
180
181                let leader = follower_state.and_then(|state| {
182                    let room = active_call?.read(cx).room()?.read(cx);
183                    room.remote_participant_for_peer_id(state.leader_id)
184                });
185
186                let is_in_unshared_view = follower_state.map_or(false, |state| {
187                    state.active_view_id.is_some_and(|view_id| {
188                        !state.items_by_leader_view_id.contains_key(&view_id)
189                    })
190                });
191
192                let mut leader_border = None;
193                let mut leader_status_box = None;
194                let mut leader_join_data = None;
195                if let Some(leader) = &leader {
196                    let mut leader_color = cx
197                        .theme()
198                        .players()
199                        .color_for_participant(leader.participant_index.0)
200                        .cursor;
201                    leader_color.fade_out(0.3);
202                    leader_border = Some(leader_color);
203
204                    leader_status_box = match leader.location {
205                        ParticipantLocation::SharedProject {
206                            project_id: leader_project_id,
207                        } => {
208                            if Some(leader_project_id) == project.read(cx).remote_id() {
209                                if is_in_unshared_view {
210                                    Some(Label::new(format!(
211                                        "{} is in an unshared pane",
212                                        leader.user.github_login
213                                    )))
214                                } else {
215                                    None
216                                }
217                            } else {
218                                leader_join_data = Some((leader_project_id, leader.user.id));
219                                Some(Label::new(format!(
220                                    "Follow {} to their active project",
221                                    leader.user.github_login,
222                                )))
223                            }
224                        }
225                        ParticipantLocation::UnsharedProject => Some(Label::new(format!(
226                            "{} is viewing an unshared Zed project",
227                            leader.user.github_login
228                        ))),
229                        ParticipantLocation::External => Some(Label::new(format!(
230                            "{} is viewing a window outside of Zed",
231                            leader.user.github_login
232                        ))),
233                    };
234                }
235
236                div()
237                    .relative()
238                    .flex_1()
239                    .size_full()
240                    .child(AnyView::from(pane.clone()).cached())
241                    .when_some(leader_border, |this, color| {
242                        this.child(
243                            div()
244                                .absolute()
245                                .size_full()
246                                .left_0()
247                                .top_0()
248                                .border_2()
249                                .border_color(color),
250                        )
251                    })
252                    .when_some(leader_status_box, |this, status_box| {
253                        this.child(
254                            div()
255                                .absolute()
256                                .w_96()
257                                .bottom_3()
258                                .right_3()
259                                .elevation_2(cx)
260                                .p_1()
261                                .z_index(1)
262                                .child(status_box)
263                                .when_some(
264                                    leader_join_data,
265                                    |this, (leader_project_id, leader_user_id)| {
266                                        this.cursor_pointer().on_mouse_down(
267                                            MouseButton::Left,
268                                            cx.listener(move |this, _, cx| {
269                                                crate::join_remote_project(
270                                                    leader_project_id,
271                                                    leader_user_id,
272                                                    this.app_state().clone(),
273                                                    cx,
274                                                )
275                                                .detach_and_log_err(cx);
276                                            }),
277                                        )
278                                    },
279                                ),
280                        )
281                    })
282                    .into_any()
283            }
284            Member::Axis(axis) => axis
285                .render(
286                    project,
287                    basis + 1,
288                    follower_states,
289                    active_call,
290                    active_pane,
291                    zoomed,
292                    app_state,
293                    cx,
294                )
295                .into_any(),
296        }
297    }
298
299    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
300        match self {
301            Member::Axis(axis) => {
302                for member in &axis.members {
303                    member.collect_panes(panes);
304                }
305            }
306            Member::Pane(pane) => panes.push(pane),
307        }
308    }
309}
310
311#[derive(Clone)]
312pub(crate) struct PaneAxis {
313    pub axis: Axis,
314    pub members: Vec<Member>,
315    pub flexes: Arc<Mutex<Vec<f32>>>,
316    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
317}
318
319impl PaneAxis {
320    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
321        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
322        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
323        Self {
324            axis,
325            members,
326            flexes,
327            bounding_boxes,
328        }
329    }
330
331    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
332        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
333        debug_assert!(members.len() == flexes.len());
334
335        let flexes = Arc::new(Mutex::new(flexes));
336        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
337        Self {
338            axis,
339            members,
340            flexes,
341            bounding_boxes,
342        }
343    }
344
345    fn split(
346        &mut self,
347        old_pane: &View<Pane>,
348        new_pane: &View<Pane>,
349        direction: SplitDirection,
350    ) -> Result<()> {
351        for (mut idx, member) in self.members.iter_mut().enumerate() {
352            match member {
353                Member::Axis(axis) => {
354                    if axis.split(old_pane, new_pane, direction).is_ok() {
355                        return Ok(());
356                    }
357                }
358                Member::Pane(pane) => {
359                    if pane == old_pane {
360                        if direction.axis() == self.axis {
361                            if direction.increasing() {
362                                idx += 1;
363                            }
364
365                            self.members.insert(idx, Member::Pane(new_pane.clone()));
366                            *self.flexes.lock() = vec![1.; self.members.len()];
367                        } else {
368                            *member =
369                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
370                        }
371                        return Ok(());
372                    }
373                }
374            }
375        }
376        Err(anyhow!("Pane not found"))
377    }
378
379    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
380        let mut found_pane = false;
381        let mut remove_member = None;
382        for (idx, member) in self.members.iter_mut().enumerate() {
383            match member {
384                Member::Axis(axis) => {
385                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
386                        if let Some(last_pane) = last_pane {
387                            *member = last_pane;
388                        }
389                        found_pane = true;
390                        break;
391                    }
392                }
393                Member::Pane(pane) => {
394                    if pane == pane_to_remove {
395                        found_pane = true;
396                        remove_member = Some(idx);
397                        break;
398                    }
399                }
400            }
401        }
402
403        if found_pane {
404            if let Some(idx) = remove_member {
405                self.members.remove(idx);
406                *self.flexes.lock() = vec![1.; self.members.len()];
407            }
408
409            if self.members.len() == 1 {
410                let result = self.members.pop();
411                *self.flexes.lock() = vec![1.; self.members.len()];
412                Ok(result)
413            } else {
414                Ok(None)
415            }
416        } else {
417            Err(anyhow!("Pane not found"))
418        }
419    }
420
421    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
422        for member in self.members.iter_mut() {
423            match member {
424                Member::Axis(axis) => axis.swap(from, to),
425                Member::Pane(pane) => {
426                    if pane == from {
427                        *member = Member::Pane(to.clone());
428                    } else if pane == to {
429                        *member = Member::Pane(from.clone())
430                    }
431                }
432            }
433        }
434    }
435
436    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
437        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
438
439        for (idx, member) in self.members.iter().enumerate() {
440            match member {
441                Member::Pane(found) => {
442                    if pane == found {
443                        return self.bounding_boxes.lock()[idx];
444                    }
445                }
446                Member::Axis(axis) => {
447                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
448                        return Some(rect);
449                    }
450                }
451            }
452        }
453        None
454    }
455
456    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
457        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
458
459        let bounding_boxes = self.bounding_boxes.lock();
460
461        for (idx, member) in self.members.iter().enumerate() {
462            if let Some(coordinates) = bounding_boxes[idx] {
463                if coordinates.contains(&coordinate) {
464                    return match member {
465                        Member::Pane(found) => Some(found),
466                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
467                    };
468                }
469            }
470        }
471        None
472    }
473
474    fn render(
475        &self,
476        project: &Model<Project>,
477        basis: usize,
478        follower_states: &HashMap<View<Pane>, FollowerState>,
479        active_call: Option<&Model<ActiveCall>>,
480        active_pane: &View<Pane>,
481        zoomed: Option<&AnyWeakView>,
482        app_state: &Arc<AppState>,
483        cx: &mut ViewContext<Workspace>,
484    ) -> gpui::AnyElement {
485        debug_assert!(self.members.len() == self.flexes.lock().len());
486        let mut active_pane_ix = None;
487
488        pane_axis(
489            self.axis,
490            basis,
491            self.flexes.clone(),
492            self.bounding_boxes.clone(),
493            cx.view().downgrade(),
494        )
495        .children(self.members.iter().enumerate().map(|(ix, member)| {
496            if member.contains(active_pane) {
497                active_pane_ix = Some(ix);
498            }
499            member
500                .render(
501                    project,
502                    (basis + ix) * 10,
503                    follower_states,
504                    active_call,
505                    active_pane,
506                    zoomed,
507                    app_state,
508                    cx,
509                )
510                .into_any_element()
511        }))
512        .with_active_pane(active_pane_ix)
513        .into_any_element()
514    }
515}
516
517#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
518pub enum SplitDirection {
519    Up,
520    Down,
521    Left,
522    Right,
523}
524
525impl std::fmt::Display for SplitDirection {
526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        match self {
528            SplitDirection::Up => write!(f, "up"),
529            SplitDirection::Down => write!(f, "down"),
530            SplitDirection::Left => write!(f, "left"),
531            SplitDirection::Right => write!(f, "right"),
532        }
533    }
534}
535
536impl SplitDirection {
537    pub fn all() -> [Self; 4] {
538        [Self::Up, Self::Down, Self::Left, Self::Right]
539    }
540
541    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
542        match self {
543            Self::Up => rect.origin.y,
544            Self::Down => rect.lower_left().y,
545            Self::Left => rect.lower_left().x,
546            Self::Right => rect.lower_right().x,
547        }
548    }
549
550    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
551        match self {
552            Self::Up => Bounds {
553                origin: bounds.origin,
554                size: size(bounds.size.width, length),
555            },
556            Self::Down => Bounds {
557                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
558                size: size(bounds.size.width, length),
559            },
560            Self::Left => Bounds {
561                origin: bounds.origin,
562                size: size(length, bounds.size.height),
563            },
564            Self::Right => Bounds {
565                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
566                size: size(length, bounds.size.height),
567            },
568        }
569    }
570
571    pub fn axis(&self) -> Axis {
572        match self {
573            Self::Up | Self::Down => Axis::Vertical,
574            Self::Left | Self::Right => Axis::Horizontal,
575        }
576    }
577
578    pub fn increasing(&self) -> bool {
579        match self {
580            Self::Left | Self::Up => false,
581            Self::Down | Self::Right => true,
582        }
583    }
584}
585
586mod element {
587
588    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
589
590    use gpui::{
591        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
592        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
593        Size, Style, WeakView, WindowContext,
594    };
595    use parking_lot::Mutex;
596    use settings::Settings;
597    use smallvec::SmallVec;
598    use ui::prelude::*;
599    use util::ResultExt;
600
601    use crate::Workspace;
602
603    use crate::WorkspaceSettings;
604
605    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
606
607    const DIVIDER_SIZE: f32 = 1.0;
608
609    pub(super) fn pane_axis(
610        axis: Axis,
611        basis: usize,
612        flexes: Arc<Mutex<Vec<f32>>>,
613        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
614        workspace: WeakView<Workspace>,
615    ) -> PaneAxisElement {
616        PaneAxisElement {
617            axis,
618            basis,
619            flexes,
620            bounding_boxes,
621            children: SmallVec::new(),
622            active_pane_ix: None,
623            workspace,
624        }
625    }
626
627    pub struct PaneAxisElement {
628        axis: Axis,
629        basis: usize,
630        flexes: Arc<Mutex<Vec<f32>>>,
631        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
632        children: SmallVec<[AnyElement; 2]>,
633        active_pane_ix: Option<usize>,
634        workspace: WeakView<Workspace>,
635    }
636
637    impl PaneAxisElement {
638        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
639            self.active_pane_ix = active_pane_ix;
640            self
641        }
642
643        fn compute_resize(
644            flexes: &Arc<Mutex<Vec<f32>>>,
645            e: &MouseMoveEvent,
646            ix: usize,
647            axis: Axis,
648            child_start: Point<Pixels>,
649            container_size: Size<Pixels>,
650            workspace: WeakView<Workspace>,
651            cx: &mut WindowContext,
652        ) {
653            let min_size = match axis {
654                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
655                Axis::Vertical => px(VERTICAL_MIN_SIZE),
656            };
657            let mut flexes = flexes.lock();
658            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
659
660            let size = move |ix, flexes: &[f32]| {
661                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
662            };
663
664            // Don't allow resizing to less than the minimum size, if elements are already too small
665            if min_size - px(1.) > size(ix, flexes.as_slice()) {
666                return;
667            }
668
669            let mut proposed_current_pixel_change =
670                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
671
672            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
673                let flex_change = pixel_dx / container_size.along(axis);
674                let current_target_flex = flexes[target_ix] + flex_change;
675                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
676                (current_target_flex, next_target_flex)
677            };
678
679            let mut successors = iter::from_fn({
680                let forward = proposed_current_pixel_change > px(0.);
681                let mut ix_offset = 0;
682                let len = flexes.len();
683                move || {
684                    let result = if forward {
685                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
686                    } else {
687                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
688                    };
689
690                    ix_offset += 1;
691
692                    result
693                }
694            });
695
696            while proposed_current_pixel_change.abs() > px(0.) {
697                let Some(current_ix) = successors.next() else {
698                    break;
699                };
700
701                let next_target_size = Pixels::max(
702                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
703                    min_size,
704                );
705
706                let current_target_size = Pixels::max(
707                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
708                        - next_target_size,
709                    min_size,
710                );
711
712                let current_pixel_change =
713                    current_target_size - size(current_ix, flexes.as_slice());
714
715                let (current_target_flex, next_target_flex) =
716                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
717
718                flexes[current_ix] = current_target_flex;
719                flexes[current_ix + 1] = next_target_flex;
720
721                proposed_current_pixel_change -= current_pixel_change;
722            }
723
724            workspace
725                .update(cx, |this, cx| this.schedule_serialize(cx))
726                .log_err();
727            cx.stop_propagation();
728            cx.refresh();
729        }
730
731        fn push_handle(
732            flexes: Arc<Mutex<Vec<f32>>>,
733            dragged_handle: Rc<RefCell<Option<usize>>>,
734            axis: Axis,
735            ix: usize,
736            pane_bounds: Bounds<Pixels>,
737            axis_bounds: Bounds<Pixels>,
738            workspace: WeakView<Workspace>,
739            cx: &mut ElementContext,
740        ) {
741            let handle_bounds = Bounds {
742                origin: pane_bounds.origin.apply_along(axis, |origin| {
743                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
744                }),
745                size: pane_bounds
746                    .size
747                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
748            };
749            let divider_bounds = Bounds {
750                origin: pane_bounds
751                    .origin
752                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
753                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
754            };
755
756            cx.with_z_index(3, |cx| {
757                let interactive_handle_bounds = InteractiveBounds {
758                    bounds: handle_bounds,
759                    stacking_order: cx.stacking_order().clone(),
760                };
761                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
762                    cx.set_cursor_style(match axis {
763                        Axis::Vertical => CursorStyle::ResizeUpDown,
764                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
765                    })
766                }
767
768                cx.add_opaque_layer(handle_bounds);
769                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
770
771                cx.on_mouse_event({
772                    let dragged_handle = dragged_handle.clone();
773                    let flexes = flexes.clone();
774                    let workspace = workspace.clone();
775                    move |e: &MouseDownEvent, phase, cx| {
776                        if phase.bubble() && handle_bounds.contains(&e.position) {
777                            dragged_handle.replace(Some(ix));
778                            if e.click_count >= 2 {
779                                let mut borrow = flexes.lock();
780                                *borrow = vec![1.; borrow.len()];
781                                workspace
782                                    .update(cx, |this, cx| this.schedule_serialize(cx))
783                                    .log_err();
784
785                                cx.refresh();
786                            }
787                            cx.stop_propagation();
788                        }
789                    }
790                });
791                cx.on_mouse_event({
792                    let workspace = workspace.clone();
793                    move |e: &MouseMoveEvent, phase, cx| {
794                        let dragged_handle = dragged_handle.borrow();
795
796                        if phase.bubble() && *dragged_handle == Some(ix) {
797                            Self::compute_resize(
798                                &flexes,
799                                e,
800                                ix,
801                                axis,
802                                pane_bounds.origin,
803                                axis_bounds.size,
804                                workspace.clone(),
805                                cx,
806                            )
807                        }
808                    }
809                });
810            });
811        }
812    }
813
814    impl IntoElement for PaneAxisElement {
815        type Element = Self;
816
817        fn element_id(&self) -> Option<ui::prelude::ElementId> {
818            Some(self.basis.into())
819        }
820
821        fn into_element(self) -> Self::Element {
822            self
823        }
824    }
825
826    impl Element for PaneAxisElement {
827        type State = Rc<RefCell<Option<usize>>>;
828
829        fn request_layout(
830            &mut self,
831            state: Option<Self::State>,
832            cx: &mut ui::prelude::ElementContext,
833        ) -> (gpui::LayoutId, Self::State) {
834            let mut style = Style::default();
835            style.flex_grow = 1.;
836            style.flex_shrink = 1.;
837            style.flex_basis = relative(0.).into();
838            style.size.width = relative(1.).into();
839            style.size.height = relative(1.).into();
840            let layout_id = cx.request_layout(&style, None);
841            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
842            (layout_id, dragged_pane)
843        }
844
845        fn paint(
846            &mut self,
847            bounds: gpui::Bounds<ui::prelude::Pixels>,
848            state: &mut Self::State,
849            cx: &mut ui::prelude::ElementContext,
850        ) {
851            let flexes = self.flexes.lock().clone();
852            let len = self.children.len();
853            debug_assert!(flexes.len() == len);
854            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
855
856            let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
857            let active_pane_magnification = if magnification_value == 1. {
858                None
859            } else {
860                Some(magnification_value)
861            };
862
863            let total_flex = if let Some(flex) = active_pane_magnification {
864                self.children.len() as f32 - 1. + flex
865            } else {
866                len as f32
867            };
868
869            let mut origin = bounds.origin;
870            let space_per_flex = bounds.size.along(self.axis) / total_flex;
871
872            let mut bounding_boxes = self.bounding_boxes.lock();
873            bounding_boxes.clear();
874
875            for (ix, child) in self.children.iter_mut().enumerate() {
876                let child_flex = active_pane_magnification
877                    .map(|magnification| {
878                        if self.active_pane_ix == Some(ix) {
879                            magnification
880                        } else {
881                            1.
882                        }
883                    })
884                    .unwrap_or_else(|| flexes[ix]);
885
886                let child_size = bounds
887                    .size
888                    .apply_along(self.axis, |_| space_per_flex * child_flex);
889
890                let child_bounds = Bounds {
891                    origin,
892                    size: child_size,
893                };
894                bounding_boxes.push(Some(child_bounds));
895                cx.with_z_index(0, |cx| {
896                    child.draw(origin, child_size.into(), cx);
897                });
898
899                if active_pane_magnification.is_none() {
900                    cx.with_z_index(1, |cx| {
901                        if ix < len - 1 {
902                            Self::push_handle(
903                                self.flexes.clone(),
904                                state.clone(),
905                                self.axis,
906                                ix,
907                                child_bounds,
908                                bounds,
909                                self.workspace.clone(),
910                                cx,
911                            );
912                        }
913                    });
914                }
915
916                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
917            }
918
919            cx.with_z_index(1, |cx| {
920                cx.on_mouse_event({
921                    let state = state.clone();
922                    move |_: &MouseUpEvent, phase, _cx| {
923                        if phase.bubble() {
924                            state.replace(None);
925                        }
926                    }
927                });
928            })
929        }
930    }
931
932    impl ParentElement for PaneAxisElement {
933        fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
934            self.children.extend(elements)
935        }
936    }
937
938    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
939        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
940    }
941}