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