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