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.stop_propagation();
702            cx.refresh();
703        }
704
705        fn push_handle(
706            flexes: Arc<Mutex<Vec<f32>>>,
707            dragged_handle: Rc<RefCell<Option<usize>>>,
708            axis: Axis,
709            ix: usize,
710            pane_bounds: Bounds<Pixels>,
711            axis_bounds: Bounds<Pixels>,
712            workspace: WeakView<Workspace>,
713            cx: &mut WindowContext,
714        ) {
715            let handle_bounds = Bounds {
716                origin: pane_bounds.origin.apply_along(axis, |origin| {
717                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
718                }),
719                size: pane_bounds
720                    .size
721                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
722            };
723            let divider_bounds = Bounds {
724                origin: pane_bounds
725                    .origin
726                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
727                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
728            };
729
730            cx.with_z_index(3, |cx| {
731                let interactive_handle_bounds = InteractiveBounds {
732                    bounds: handle_bounds,
733                    stacking_order: cx.stacking_order().clone(),
734                };
735                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
736                    cx.set_cursor_style(match axis {
737                        Axis::Vertical => CursorStyle::ResizeUpDown,
738                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
739                    })
740                }
741
742                cx.add_opaque_layer(handle_bounds);
743                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
744
745                cx.on_mouse_event({
746                    let dragged_handle = dragged_handle.clone();
747                    let flexes = flexes.clone();
748                    let workspace = workspace.clone();
749                    move |e: &MouseDownEvent, phase, cx| {
750                        if phase.bubble() && handle_bounds.contains(&e.position) {
751                            dragged_handle.replace(Some(ix));
752                            if e.click_count >= 2 {
753                                let mut borrow = flexes.lock();
754                                *borrow = vec![1.; borrow.len()];
755                                workspace
756                                    .update(cx, |this, cx| this.schedule_serialize(cx))
757                                    .log_err();
758
759                                cx.refresh();
760                            }
761                            cx.stop_propagation();
762                        }
763                    }
764                });
765                cx.on_mouse_event({
766                    let workspace = workspace.clone();
767                    move |e: &MouseMoveEvent, phase, cx| {
768                        let dragged_handle = dragged_handle.borrow();
769
770                        if phase.bubble() && *dragged_handle == Some(ix) {
771                            Self::compute_resize(
772                                &flexes,
773                                e,
774                                ix,
775                                axis,
776                                pane_bounds.origin,
777                                axis_bounds.size,
778                                workspace.clone(),
779                                cx,
780                            )
781                        }
782                    }
783                });
784            });
785        }
786    }
787
788    impl IntoElement for PaneAxisElement {
789        type Element = Self;
790
791        fn element_id(&self) -> Option<ui::prelude::ElementId> {
792            Some(self.basis.into())
793        }
794
795        fn into_element(self) -> Self::Element {
796            self
797        }
798    }
799
800    impl Element for PaneAxisElement {
801        type State = Rc<RefCell<Option<usize>>>;
802
803        fn request_layout(
804            &mut self,
805            state: Option<Self::State>,
806            cx: &mut ui::prelude::WindowContext,
807        ) -> (gpui::LayoutId, Self::State) {
808            let mut style = Style::default();
809            style.flex_grow = 1.;
810            style.flex_shrink = 1.;
811            style.flex_basis = relative(0.).into();
812            style.size.width = relative(1.).into();
813            style.size.height = relative(1.).into();
814            let layout_id = cx.request_layout(&style, None);
815            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
816            (layout_id, dragged_pane)
817        }
818
819        fn paint(
820            &mut self,
821            bounds: gpui::Bounds<ui::prelude::Pixels>,
822            state: &mut Self::State,
823            cx: &mut ui::prelude::WindowContext,
824        ) {
825            let flexes = self.flexes.lock().clone();
826            let len = self.children.len();
827            debug_assert!(flexes.len() == len);
828            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
829
830            let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
831            let active_pane_magnification = if magnification_value == 1. {
832                None
833            } else {
834                Some(magnification_value)
835            };
836
837            let total_flex = if let Some(flex) = active_pane_magnification {
838                self.children.len() as f32 - 1. + flex
839            } else {
840                len as f32
841            };
842
843            let mut origin = bounds.origin;
844            let space_per_flex = bounds.size.along(self.axis) / total_flex;
845
846            let mut bounding_boxes = self.bounding_boxes.lock();
847            bounding_boxes.clear();
848
849            for (ix, child) in self.children.iter_mut().enumerate() {
850                let child_flex = active_pane_magnification
851                    .map(|magnification| {
852                        if self.active_pane_ix == Some(ix) {
853                            magnification
854                        } else {
855                            1.
856                        }
857                    })
858                    .unwrap_or_else(|| flexes[ix]);
859
860                let child_size = bounds
861                    .size
862                    .apply_along(self.axis, |_| space_per_flex * child_flex);
863
864                let child_bounds = Bounds {
865                    origin,
866                    size: child_size,
867                };
868                bounding_boxes.push(Some(child_bounds));
869                cx.with_z_index(0, |cx| {
870                    child.draw(origin, child_size.into(), cx);
871                });
872
873                if active_pane_magnification.is_none() {
874                    cx.with_z_index(1, |cx| {
875                        if ix < len - 1 {
876                            Self::push_handle(
877                                self.flexes.clone(),
878                                state.clone(),
879                                self.axis,
880                                ix,
881                                child_bounds,
882                                bounds,
883                                self.workspace.clone(),
884                                cx,
885                            );
886                        }
887                    });
888                }
889
890                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
891            }
892
893            cx.with_z_index(1, |cx| {
894                cx.on_mouse_event({
895                    let state = state.clone();
896                    move |_: &MouseUpEvent, phase, _cx| {
897                        if phase.bubble() {
898                            state.replace(None);
899                        }
900                    }
901                });
902            })
903        }
904    }
905
906    impl ParentElement for PaneAxisElement {
907        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
908            &mut self.children
909        }
910    }
911
912    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
913        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
914    }
915}