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                                .elevation_2(cx)
245                                .p_1()
246                                .z_index(1)
247                                .child(status_box)
248                                .when_some(
249                                    leader_join_data,
250                                    |this, (leader_project_id, leader_user_id)| {
251                                        this.cursor_pointer().on_mouse_down(
252                                            MouseButton::Left,
253                                            cx.listener(move |this, _, cx| {
254                                                crate::join_remote_project(
255                                                    leader_project_id,
256                                                    leader_user_id,
257                                                    this.app_state().clone(),
258                                                    cx,
259                                                )
260                                                .detach_and_log_err(cx);
261                                            }),
262                                        )
263                                    },
264                                ),
265                        )
266                    })
267                    .into_any()
268            }
269            Member::Axis(axis) => axis
270                .render(
271                    project,
272                    basis + 1,
273                    follower_states,
274                    active_call,
275                    active_pane,
276                    zoomed,
277                    app_state,
278                    cx,
279                )
280                .into_any(),
281        }
282    }
283
284    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
285        match self {
286            Member::Axis(axis) => {
287                for member in &axis.members {
288                    member.collect_panes(panes);
289                }
290            }
291            Member::Pane(pane) => panes.push(pane),
292        }
293    }
294}
295
296#[derive(Clone)]
297pub(crate) struct PaneAxis {
298    pub axis: Axis,
299    pub members: Vec<Member>,
300    pub flexes: Arc<Mutex<Vec<f32>>>,
301    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
302}
303
304impl PaneAxis {
305    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
306        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
307        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
308        Self {
309            axis,
310            members,
311            flexes,
312            bounding_boxes,
313        }
314    }
315
316    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
317        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
318        debug_assert!(members.len() == flexes.len());
319
320        let flexes = Arc::new(Mutex::new(flexes));
321        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
322        Self {
323            axis,
324            members,
325            flexes,
326            bounding_boxes,
327        }
328    }
329
330    fn split(
331        &mut self,
332        old_pane: &View<Pane>,
333        new_pane: &View<Pane>,
334        direction: SplitDirection,
335    ) -> Result<()> {
336        for (mut idx, member) in self.members.iter_mut().enumerate() {
337            match member {
338                Member::Axis(axis) => {
339                    if axis.split(old_pane, new_pane, direction).is_ok() {
340                        return Ok(());
341                    }
342                }
343                Member::Pane(pane) => {
344                    if pane == old_pane {
345                        if direction.axis() == self.axis {
346                            if direction.increasing() {
347                                idx += 1;
348                            }
349
350                            self.members.insert(idx, Member::Pane(new_pane.clone()));
351                            *self.flexes.lock() = vec![1.; self.members.len()];
352                        } else {
353                            *member =
354                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
355                        }
356                        return Ok(());
357                    }
358                }
359            }
360        }
361        Err(anyhow!("Pane not found"))
362    }
363
364    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
365        let mut found_pane = false;
366        let mut remove_member = None;
367        for (idx, member) in self.members.iter_mut().enumerate() {
368            match member {
369                Member::Axis(axis) => {
370                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
371                        if let Some(last_pane) = last_pane {
372                            *member = last_pane;
373                        }
374                        found_pane = true;
375                        break;
376                    }
377                }
378                Member::Pane(pane) => {
379                    if pane == pane_to_remove {
380                        found_pane = true;
381                        remove_member = Some(idx);
382                        break;
383                    }
384                }
385            }
386        }
387
388        if found_pane {
389            if let Some(idx) = remove_member {
390                self.members.remove(idx);
391                *self.flexes.lock() = vec![1.; self.members.len()];
392            }
393
394            if self.members.len() == 1 {
395                let result = self.members.pop();
396                *self.flexes.lock() = vec![1.; self.members.len()];
397                Ok(result)
398            } else {
399                Ok(None)
400            }
401        } else {
402            Err(anyhow!("Pane not found"))
403        }
404    }
405
406    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
407        for member in self.members.iter_mut() {
408            match member {
409                Member::Axis(axis) => axis.swap(from, to),
410                Member::Pane(pane) => {
411                    if pane == from {
412                        *member = Member::Pane(to.clone());
413                    } else if pane == to {
414                        *member = Member::Pane(from.clone())
415                    }
416                }
417            }
418        }
419    }
420
421    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
422        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
423
424        for (idx, member) in self.members.iter().enumerate() {
425            match member {
426                Member::Pane(found) => {
427                    if pane == found {
428                        return self.bounding_boxes.lock()[idx];
429                    }
430                }
431                Member::Axis(axis) => {
432                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
433                        return Some(rect);
434                    }
435                }
436            }
437        }
438        None
439    }
440
441    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
442        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
443
444        let bounding_boxes = self.bounding_boxes.lock();
445
446        for (idx, member) in self.members.iter().enumerate() {
447            if let Some(coordinates) = bounding_boxes[idx] {
448                if coordinates.contains(&coordinate) {
449                    return match member {
450                        Member::Pane(found) => Some(found),
451                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
452                    };
453                }
454            }
455        }
456        None
457    }
458
459    fn render(
460        &self,
461        project: &Model<Project>,
462        basis: usize,
463        follower_states: &HashMap<View<Pane>, FollowerState>,
464        active_call: Option<&Model<ActiveCall>>,
465        active_pane: &View<Pane>,
466        zoomed: Option<&AnyWeakView>,
467        app_state: &Arc<AppState>,
468        cx: &mut ViewContext<Workspace>,
469    ) -> gpui::AnyElement {
470        debug_assert!(self.members.len() == self.flexes.lock().len());
471        let mut active_pane_ix = None;
472
473        pane_axis(
474            self.axis,
475            basis,
476            self.flexes.clone(),
477            self.bounding_boxes.clone(),
478            cx.view().downgrade(),
479        )
480        .children(self.members.iter().enumerate().map(|(ix, member)| {
481            if member.contains(active_pane) {
482                active_pane_ix = Some(ix);
483            }
484            member
485                .render(
486                    project,
487                    (basis + ix) * 10,
488                    follower_states,
489                    active_call,
490                    active_pane,
491                    zoomed,
492                    app_state,
493                    cx,
494                )
495                .into_any_element()
496        }))
497        .with_active_pane(active_pane_ix)
498        .into_any_element()
499    }
500}
501
502#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
503pub enum SplitDirection {
504    Up,
505    Down,
506    Left,
507    Right,
508}
509
510impl SplitDirection {
511    pub fn all() -> [Self; 4] {
512        [Self::Up, Self::Down, Self::Left, Self::Right]
513    }
514
515    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
516        match self {
517            Self::Up => rect.origin.y,
518            Self::Down => rect.lower_left().y,
519            Self::Left => rect.lower_left().x,
520            Self::Right => rect.lower_right().x,
521        }
522    }
523
524    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
525        match self {
526            Self::Up => Bounds {
527                origin: bounds.origin,
528                size: size(bounds.size.width, length),
529            },
530            Self::Down => Bounds {
531                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
532                size: size(bounds.size.width, length),
533            },
534            Self::Left => Bounds {
535                origin: bounds.origin,
536                size: size(length, bounds.size.height),
537            },
538            Self::Right => Bounds {
539                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
540                size: size(length, bounds.size.height),
541            },
542        }
543    }
544
545    pub fn axis(&self) -> Axis {
546        match self {
547            Self::Up | Self::Down => Axis::Vertical,
548            Self::Left | Self::Right => Axis::Horizontal,
549        }
550    }
551
552    pub fn increasing(&self) -> bool {
553        match self {
554            Self::Left | Self::Up => false,
555            Self::Down | Self::Right => true,
556        }
557    }
558}
559
560mod element {
561
562    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
563
564    use gpui::{
565        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
566        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
567        Size, Style, WeakView, WindowContext,
568    };
569    use parking_lot::Mutex;
570    use settings::Settings;
571    use smallvec::SmallVec;
572    use ui::prelude::*;
573    use util::ResultExt;
574
575    use crate::Workspace;
576
577    use crate::WorkspaceSettings;
578
579    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
580
581    const DIVIDER_SIZE: f32 = 1.0;
582
583    pub(super) fn pane_axis(
584        axis: Axis,
585        basis: usize,
586        flexes: Arc<Mutex<Vec<f32>>>,
587        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
588        workspace: WeakView<Workspace>,
589    ) -> PaneAxisElement {
590        PaneAxisElement {
591            axis,
592            basis,
593            flexes,
594            bounding_boxes,
595            children: SmallVec::new(),
596            active_pane_ix: None,
597            workspace,
598        }
599    }
600
601    pub struct PaneAxisElement {
602        axis: Axis,
603        basis: usize,
604        flexes: Arc<Mutex<Vec<f32>>>,
605        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
606        children: SmallVec<[AnyElement; 2]>,
607        active_pane_ix: Option<usize>,
608        workspace: WeakView<Workspace>,
609    }
610
611    impl PaneAxisElement {
612        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
613            self.active_pane_ix = active_pane_ix;
614            self
615        }
616
617        fn compute_resize(
618            flexes: &Arc<Mutex<Vec<f32>>>,
619            e: &MouseMoveEvent,
620            ix: usize,
621            axis: Axis,
622            child_start: Point<Pixels>,
623            container_size: Size<Pixels>,
624            workspace: WeakView<Workspace>,
625            cx: &mut WindowContext,
626        ) {
627            let min_size = match axis {
628                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
629                Axis::Vertical => px(VERTICAL_MIN_SIZE),
630            };
631            let mut flexes = flexes.lock();
632            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
633
634            let size = move |ix, flexes: &[f32]| {
635                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
636            };
637
638            // Don't allow resizing to less than the minimum size, if elements are already too small
639            if min_size - px(1.) > size(ix, flexes.as_slice()) {
640                return;
641            }
642
643            let mut proposed_current_pixel_change =
644                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
645
646            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
647                let flex_change = pixel_dx / container_size.along(axis);
648                let current_target_flex = flexes[target_ix] + flex_change;
649                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
650                (current_target_flex, next_target_flex)
651            };
652
653            let mut successors = iter::from_fn({
654                let forward = proposed_current_pixel_change > px(0.);
655                let mut ix_offset = 0;
656                let len = flexes.len();
657                move || {
658                    let result = if forward {
659                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
660                    } else {
661                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
662                    };
663
664                    ix_offset += 1;
665
666                    result
667                }
668            });
669
670            while proposed_current_pixel_change.abs() > px(0.) {
671                let Some(current_ix) = successors.next() else {
672                    break;
673                };
674
675                let next_target_size = Pixels::max(
676                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
677                    min_size,
678                );
679
680                let current_target_size = Pixels::max(
681                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
682                        - next_target_size,
683                    min_size,
684                );
685
686                let current_pixel_change =
687                    current_target_size - size(current_ix, flexes.as_slice());
688
689                let (current_target_flex, next_target_flex) =
690                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
691
692                flexes[current_ix] = current_target_flex;
693                flexes[current_ix + 1] = next_target_flex;
694
695                proposed_current_pixel_change -= current_pixel_change;
696            }
697
698            workspace
699                .update(cx, |this, cx| this.schedule_serialize(cx))
700                .log_err();
701            cx.refresh();
702        }
703
704        fn push_handle(
705            flexes: Arc<Mutex<Vec<f32>>>,
706            dragged_handle: Rc<RefCell<Option<usize>>>,
707            axis: Axis,
708            ix: usize,
709            pane_bounds: Bounds<Pixels>,
710            axis_bounds: Bounds<Pixels>,
711            workspace: WeakView<Workspace>,
712            cx: &mut WindowContext,
713        ) {
714            let handle_bounds = Bounds {
715                origin: pane_bounds.origin.apply_along(axis, |origin| {
716                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
717                }),
718                size: pane_bounds
719                    .size
720                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
721            };
722            let divider_bounds = Bounds {
723                origin: pane_bounds
724                    .origin
725                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
726                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
727            };
728
729            cx.with_z_index(3, |cx| {
730                let interactive_handle_bounds = InteractiveBounds {
731                    bounds: handle_bounds,
732                    stacking_order: cx.stacking_order().clone(),
733                };
734                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
735                    cx.set_cursor_style(match axis {
736                        Axis::Vertical => CursorStyle::ResizeUpDown,
737                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
738                    })
739                }
740
741                cx.add_opaque_layer(handle_bounds);
742                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
743
744                cx.on_mouse_event({
745                    let dragged_handle = dragged_handle.clone();
746                    let flexes = flexes.clone();
747                    let workspace = workspace.clone();
748                    move |e: &MouseDownEvent, phase, cx| {
749                        if phase.bubble() && handle_bounds.contains(&e.position) {
750                            dragged_handle.replace(Some(ix));
751                            if e.click_count >= 2 {
752                                let mut borrow = flexes.lock();
753                                *borrow = vec![1.; borrow.len()];
754                                workspace
755                                    .update(cx, |this, cx| this.schedule_serialize(cx))
756                                    .log_err();
757                                cx.refresh();
758                            }
759                        }
760                    }
761                });
762                cx.on_mouse_event({
763                    let workspace = workspace.clone();
764                    move |e: &MouseMoveEvent, phase, cx| {
765                        let dragged_handle = dragged_handle.borrow();
766
767                        if phase.bubble() && *dragged_handle == Some(ix) {
768                            Self::compute_resize(
769                                &flexes,
770                                e,
771                                ix,
772                                axis,
773                                pane_bounds.origin,
774                                axis_bounds.size,
775                                workspace.clone(),
776                                cx,
777                            )
778                        }
779                    }
780                });
781            });
782        }
783    }
784
785    impl IntoElement for PaneAxisElement {
786        type Element = Self;
787
788        fn element_id(&self) -> Option<ui::prelude::ElementId> {
789            Some(self.basis.into())
790        }
791
792        fn into_element(self) -> Self::Element {
793            self
794        }
795    }
796
797    impl Element for PaneAxisElement {
798        type State = Rc<RefCell<Option<usize>>>;
799
800        fn request_layout(
801            &mut self,
802            state: Option<Self::State>,
803            cx: &mut ui::prelude::WindowContext,
804        ) -> (gpui::LayoutId, Self::State) {
805            let mut style = Style::default();
806            style.flex_grow = 1.;
807            style.flex_shrink = 1.;
808            style.flex_basis = relative(0.).into();
809            style.size.width = relative(1.).into();
810            style.size.height = relative(1.).into();
811            let layout_id = cx.request_layout(&style, None);
812            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
813            (layout_id, dragged_pane)
814        }
815
816        fn paint(
817            &mut self,
818            bounds: gpui::Bounds<ui::prelude::Pixels>,
819            state: &mut Self::State,
820            cx: &mut ui::prelude::WindowContext,
821        ) {
822            let flexes = self.flexes.lock().clone();
823            let len = self.children.len();
824            debug_assert!(flexes.len() == len);
825            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
826
827            let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
828            let active_pane_magnification = if magnification_value == 1. {
829                None
830            } else {
831                Some(magnification_value)
832            };
833
834            let total_flex = if let Some(flex) = active_pane_magnification {
835                self.children.len() as f32 - 1. + flex
836            } else {
837                len as f32
838            };
839
840            let mut origin = bounds.origin;
841            let space_per_flex = bounds.size.along(self.axis) / total_flex;
842
843            let mut bounding_boxes = self.bounding_boxes.lock();
844            bounding_boxes.clear();
845
846            for (ix, child) in self.children.iter_mut().enumerate() {
847                let child_flex = active_pane_magnification
848                    .map(|magnification| {
849                        if self.active_pane_ix == Some(ix) {
850                            magnification
851                        } else {
852                            1.
853                        }
854                    })
855                    .unwrap_or_else(|| flexes[ix]);
856
857                let child_size = bounds
858                    .size
859                    .apply_along(self.axis, |_| space_per_flex * child_flex);
860
861                let child_bounds = Bounds {
862                    origin,
863                    size: child_size,
864                };
865                bounding_boxes.push(Some(child_bounds));
866                cx.with_z_index(0, |cx| {
867                    child.draw(origin, child_size.into(), cx);
868                });
869
870                if active_pane_magnification.is_none() {
871                    cx.with_z_index(1, |cx| {
872                        if ix < len - 1 {
873                            Self::push_handle(
874                                self.flexes.clone(),
875                                state.clone(),
876                                self.axis,
877                                ix,
878                                child_bounds,
879                                bounds,
880                                self.workspace.clone(),
881                                cx,
882                            );
883                        }
884                    });
885                }
886
887                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
888            }
889
890            cx.with_z_index(1, |cx| {
891                cx.on_mouse_event({
892                    let state = state.clone();
893                    move |_: &MouseUpEvent, phase, _cx| {
894                        if phase.bubble() {
895                            state.replace(None);
896                        }
897                    }
898                });
899            })
900        }
901    }
902
903    impl ParentElement for PaneAxisElement {
904        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
905            &mut self.children
906        }
907    }
908
909    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
910        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
911    }
912}