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