pane_group.rs

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