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, GlobalElementId, IntoElement,
601        MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
602        WeakView, 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.serialize_workspace(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 WindowContext,
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 RequestLayoutState = ();
796        type PrepaintState = PaneAxisLayout;
797
798        fn id(&self) -> Option<ElementId> {
799            Some(self.basis.into())
800        }
801
802        fn request_layout(
803            &mut self,
804            _global_id: Option<&GlobalElementId>,
805            cx: &mut ui::prelude::WindowContext,
806        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
807            let mut style = Style::default();
808            style.flex_grow = 1.;
809            style.flex_shrink = 1.;
810            style.flex_basis = relative(0.).into();
811            style.size.width = relative(1.).into();
812            style.size.height = relative(1.).into();
813            (cx.request_layout(style, None), ())
814        }
815
816        fn prepaint(
817            &mut self,
818            global_id: Option<&GlobalElementId>,
819            bounds: Bounds<Pixels>,
820            _state: &mut Self::RequestLayoutState,
821            cx: &mut WindowContext,
822        ) -> PaneAxisLayout {
823            let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
824                global_id.unwrap(),
825                |state, _cx| {
826                    let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
827                    (state.clone(), state)
828                },
829            );
830            let flexes = self.flexes.lock().clone();
831            let len = self.children.len();
832            debug_assert!(flexes.len() == len);
833            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
834
835            let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
836            let active_pane_magnification = if magnification_value == 1. {
837                None
838            } else {
839                Some(magnification_value)
840            };
841
842            let total_flex = if let Some(flex) = active_pane_magnification {
843                self.children.len() as f32 - 1. + flex
844            } else {
845                len as f32
846            };
847
848            let mut origin = bounds.origin;
849            let space_per_flex = bounds.size.along(self.axis) / total_flex;
850
851            let mut bounding_boxes = self.bounding_boxes.lock();
852            bounding_boxes.clear();
853
854            let mut layout = PaneAxisLayout {
855                dragged_handle: dragged_handle.clone(),
856                children: Vec::new(),
857            };
858            for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
859                let child_flex = active_pane_magnification
860                    .map(|magnification| {
861                        if self.active_pane_ix == Some(ix) {
862                            magnification
863                        } else {
864                            1.
865                        }
866                    })
867                    .unwrap_or_else(|| flexes[ix]);
868
869                let child_size = bounds
870                    .size
871                    .apply_along(self.axis, |_| space_per_flex * child_flex)
872                    .map(|d| d.round());
873
874                let child_bounds = Bounds {
875                    origin,
876                    size: child_size,
877                };
878                bounding_boxes.push(Some(child_bounds));
879                child.layout_as_root(child_size.into(), cx);
880                child.prepaint_at(origin, cx);
881
882                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
883                layout.children.push(PaneAxisChildLayout {
884                    bounds: child_bounds,
885                    element: child,
886                    handle: None,
887                })
888            }
889
890            for (ix, child_layout) in layout.children.iter_mut().enumerate() {
891                if active_pane_magnification.is_none() {
892                    if ix < len - 1 {
893                        child_layout.handle =
894                            Some(Self::layout_handle(self.axis, child_layout.bounds, cx));
895                    }
896                }
897            }
898
899            layout
900        }
901
902        fn paint(
903            &mut self,
904            _id: Option<&GlobalElementId>,
905            bounds: gpui::Bounds<ui::prelude::Pixels>,
906            _: &mut Self::RequestLayoutState,
907            layout: &mut Self::PrepaintState,
908            cx: &mut ui::prelude::WindowContext,
909        ) {
910            for child in &mut layout.children {
911                child.element.paint(cx);
912            }
913
914            for (ix, child) in &mut layout.children.iter_mut().enumerate() {
915                if let Some(handle) = child.handle.as_mut() {
916                    let cursor_style = match self.axis {
917                        Axis::Vertical => CursorStyle::ResizeRow,
918                        Axis::Horizontal => CursorStyle::ResizeColumn,
919                    };
920                    cx.set_cursor_style(cursor_style, &handle.hitbox);
921                    cx.paint_quad(gpui::fill(
922                        handle.divider_bounds,
923                        cx.theme().colors().pane_group_border,
924                    ));
925
926                    cx.on_mouse_event({
927                        let dragged_handle = layout.dragged_handle.clone();
928                        let flexes = self.flexes.clone();
929                        let workspace = self.workspace.clone();
930                        let handle_hitbox = handle.hitbox.clone();
931                        move |e: &MouseDownEvent, phase, cx| {
932                            if phase.bubble() && handle_hitbox.is_hovered(cx) {
933                                dragged_handle.replace(Some(ix));
934                                if e.click_count >= 2 {
935                                    let mut borrow = flexes.lock();
936                                    *borrow = vec![1.; borrow.len()];
937                                    workspace
938                                        .update(cx, |this, cx| this.serialize_workspace(cx))
939                                        .log_err();
940
941                                    cx.refresh();
942                                }
943                                cx.stop_propagation();
944                            }
945                        }
946                    });
947                    cx.on_mouse_event({
948                        let workspace = self.workspace.clone();
949                        let dragged_handle = layout.dragged_handle.clone();
950                        let flexes = self.flexes.clone();
951                        let child_bounds = child.bounds;
952                        let axis = self.axis;
953                        move |e: &MouseMoveEvent, phase, cx| {
954                            let dragged_handle = dragged_handle.borrow();
955                            if phase.bubble() {
956                                if *dragged_handle == Some(ix) {
957                                    Self::compute_resize(
958                                        &flexes,
959                                        e,
960                                        ix,
961                                        axis,
962                                        child_bounds.origin,
963                                        bounds.size,
964                                        workspace.clone(),
965                                        cx,
966                                    )
967                                }
968                            }
969                        }
970                    });
971                }
972            }
973
974            cx.on_mouse_event({
975                let dragged_handle = layout.dragged_handle.clone();
976                move |_: &MouseUpEvent, phase, _cx| {
977                    if phase.bubble() {
978                        dragged_handle.replace(None);
979                    }
980                }
981            });
982        }
983    }
984
985    impl ParentElement for PaneAxisElement {
986        fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
987            self.children.extend(elements)
988        }
989    }
990
991    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
992        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
993    }
994}