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, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
  7    ViewContext,
  8};
  9use parking_lot::Mutex;
 10use project::Project;
 11use serde::Deserialize;
 12use std::sync::Arc;
 13use ui::{prelude::*, Button};
 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                if let Some(leader) = &leader {
187                    let mut leader_color = cx
188                        .theme()
189                        .players()
190                        .color_for_participant(leader.participant_index.0)
191                        .cursor;
192                    leader_color.fade_out(0.3);
193                    leader_border = Some(leader_color);
194
195                    leader_status_box = match leader.location {
196                        ParticipantLocation::SharedProject {
197                            project_id: leader_project_id,
198                        } => {
199                            if Some(leader_project_id) == project.read(cx).remote_id() {
200                                None
201                            } else {
202                                let leader_user = leader.user.clone();
203                                let leader_user_id = leader.user.id;
204                                Some(
205                                    Button::new(
206                                        ("leader-status", pane.entity_id()),
207                                        format!(
208                                            "Follow {} to their active project",
209                                            leader_user.github_login,
210                                        ),
211                                    )
212                                    .on_click(cx.listener(
213                                        move |this, _, cx| {
214                                            crate::join_remote_project(
215                                                leader_project_id,
216                                                leader_user_id,
217                                                this.app_state().clone(),
218                                                cx,
219                                            )
220                                            .detach_and_log_err(cx);
221                                        },
222                                    )),
223                                )
224                            }
225                        }
226                        ParticipantLocation::UnsharedProject => Some(Button::new(
227                            ("leader-status", pane.entity_id()),
228                            format!(
229                                "{} is viewing an unshared Zed project",
230                                leader.user.github_login
231                            ),
232                        )),
233                        ParticipantLocation::External => Some(Button::new(
234                            ("leader-status", pane.entity_id()),
235                            format!(
236                                "{} is viewing a window outside of Zed",
237                                leader.user.github_login
238                            ),
239                        )),
240                    };
241                }
242
243                div()
244                    .relative()
245                    .flex_1()
246                    .size_full()
247                    .child(pane.clone())
248                    .when_some(leader_border, |this, color| {
249                        this.child(
250                            div()
251                                .absolute()
252                                .size_full()
253                                .left_0()
254                                .top_0()
255                                .border_2()
256                                .border_color(color),
257                        )
258                    })
259                    .when_some(leader_status_box, |this, status_box| {
260                        this.child(
261                            div()
262                                .absolute()
263                                .w_96()
264                                .bottom_3()
265                                .right_3()
266                                .z_index(1)
267                                .child(status_box),
268                        )
269                    })
270                    .into_any()
271            }
272            Member::Axis(axis) => axis
273                .render(
274                    project,
275                    basis + 1,
276                    follower_states,
277                    active_call,
278                    active_pane,
279                    zoomed,
280                    app_state,
281                    cx,
282                )
283                .into_any(),
284        }
285    }
286
287    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
288        match self {
289            Member::Axis(axis) => {
290                for member in &axis.members {
291                    member.collect_panes(panes);
292                }
293            }
294            Member::Pane(pane) => panes.push(pane),
295        }
296    }
297}
298
299#[derive(Clone)]
300pub(crate) struct PaneAxis {
301    pub axis: Axis,
302    pub members: Vec<Member>,
303    pub flexes: Arc<Mutex<Vec<f32>>>,
304    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
305}
306
307impl PaneAxis {
308    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
309        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
310        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
311        Self {
312            axis,
313            members,
314            flexes,
315            bounding_boxes,
316        }
317    }
318
319    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
320        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
321        debug_assert!(members.len() == flexes.len());
322
323        let flexes = Arc::new(Mutex::new(flexes));
324        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
325        Self {
326            axis,
327            members,
328            flexes,
329            bounding_boxes,
330        }
331    }
332
333    fn split(
334        &mut self,
335        old_pane: &View<Pane>,
336        new_pane: &View<Pane>,
337        direction: SplitDirection,
338    ) -> Result<()> {
339        for (mut idx, member) in self.members.iter_mut().enumerate() {
340            match member {
341                Member::Axis(axis) => {
342                    if axis.split(old_pane, new_pane, direction).is_ok() {
343                        return Ok(());
344                    }
345                }
346                Member::Pane(pane) => {
347                    if pane == old_pane {
348                        if direction.axis() == self.axis {
349                            if direction.increasing() {
350                                idx += 1;
351                            }
352
353                            self.members.insert(idx, Member::Pane(new_pane.clone()));
354                            *self.flexes.lock() = vec![1.; self.members.len()];
355                        } else {
356                            *member =
357                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
358                        }
359                        return Ok(());
360                    }
361                }
362            }
363        }
364        Err(anyhow!("Pane not found"))
365    }
366
367    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
368        let mut found_pane = false;
369        let mut remove_member = None;
370        for (idx, member) in self.members.iter_mut().enumerate() {
371            match member {
372                Member::Axis(axis) => {
373                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
374                        if let Some(last_pane) = last_pane {
375                            *member = last_pane;
376                        }
377                        found_pane = true;
378                        break;
379                    }
380                }
381                Member::Pane(pane) => {
382                    if pane == pane_to_remove {
383                        found_pane = true;
384                        remove_member = Some(idx);
385                        break;
386                    }
387                }
388            }
389        }
390
391        if found_pane {
392            if let Some(idx) = remove_member {
393                self.members.remove(idx);
394                *self.flexes.lock() = vec![1.; self.members.len()];
395            }
396
397            if self.members.len() == 1 {
398                let result = self.members.pop();
399                *self.flexes.lock() = vec![1.; self.members.len()];
400                Ok(result)
401            } else {
402                Ok(None)
403            }
404        } else {
405            Err(anyhow!("Pane not found"))
406        }
407    }
408
409    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
410        for member in self.members.iter_mut() {
411            match member {
412                Member::Axis(axis) => axis.swap(from, to),
413                Member::Pane(pane) => {
414                    if pane == from {
415                        *member = Member::Pane(to.clone());
416                    } else if pane == to {
417                        *member = Member::Pane(from.clone())
418                    }
419                }
420            }
421        }
422    }
423
424    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
425        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
426
427        for (idx, member) in self.members.iter().enumerate() {
428            match member {
429                Member::Pane(found) => {
430                    if pane == found {
431                        return self.bounding_boxes.lock()[idx];
432                    }
433                }
434                Member::Axis(axis) => {
435                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
436                        return Some(rect);
437                    }
438                }
439            }
440        }
441        None
442    }
443
444    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
445        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
446
447        let bounding_boxes = self.bounding_boxes.lock();
448
449        for (idx, member) in self.members.iter().enumerate() {
450            if let Some(coordinates) = bounding_boxes[idx] {
451                if coordinates.contains(&coordinate) {
452                    return match member {
453                        Member::Pane(found) => Some(found),
454                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
455                    };
456                }
457            }
458        }
459        None
460    }
461
462    fn render(
463        &self,
464        project: &Model<Project>,
465        basis: usize,
466        follower_states: &HashMap<View<Pane>, FollowerState>,
467        active_call: Option<&Model<ActiveCall>>,
468        active_pane: &View<Pane>,
469        zoomed: Option<&AnyWeakView>,
470        app_state: &Arc<AppState>,
471        cx: &mut ViewContext<Workspace>,
472    ) -> gpui::AnyElement {
473        debug_assert!(self.members.len() == self.flexes.lock().len());
474        let mut active_pane_ix = None;
475
476        pane_axis(
477            self.axis,
478            basis,
479            self.flexes.clone(),
480            self.bounding_boxes.clone(),
481            cx.view().downgrade(),
482        )
483        .children(self.members.iter().enumerate().map(|(ix, member)| {
484            if member.contains(active_pane) {
485                active_pane_ix = Some(ix);
486            }
487            member
488                .render(
489                    project,
490                    (basis + ix) * 10,
491                    follower_states,
492                    active_call,
493                    active_pane,
494                    zoomed,
495                    app_state,
496                    cx,
497                )
498                .into_any_element()
499        }))
500        .with_active_pane(active_pane_ix)
501        .into_any_element()
502    }
503}
504
505#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
506pub enum SplitDirection {
507    Up,
508    Down,
509    Left,
510    Right,
511}
512
513impl SplitDirection {
514    pub fn all() -> [Self; 4] {
515        [Self::Up, Self::Down, Self::Left, Self::Right]
516    }
517
518    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
519        match self {
520            Self::Up => rect.origin.y,
521            Self::Down => rect.lower_left().y,
522            Self::Left => rect.lower_left().x,
523            Self::Right => rect.lower_right().x,
524        }
525    }
526
527    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
528        match self {
529            Self::Up => Bounds {
530                origin: bounds.origin,
531                size: size(bounds.size.width, length),
532            },
533            Self::Down => Bounds {
534                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
535                size: size(bounds.size.width, length),
536            },
537            Self::Left => Bounds {
538                origin: bounds.origin,
539                size: size(length, bounds.size.height),
540            },
541            Self::Right => Bounds {
542                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
543                size: size(length, bounds.size.height),
544            },
545        }
546    }
547
548    pub fn axis(&self) -> Axis {
549        match self {
550            Self::Up | Self::Down => Axis::Vertical,
551            Self::Left | Self::Right => Axis::Horizontal,
552        }
553    }
554
555    pub fn increasing(&self) -> bool {
556        match self {
557            Self::Left | Self::Up => false,
558            Self::Down | Self::Right => true,
559        }
560    }
561}
562
563mod element {
564
565    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
566
567    use gpui::{
568        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
569        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
570        Size, Style, WeakView, WindowContext,
571    };
572    use parking_lot::Mutex;
573    use settings::Settings;
574    use smallvec::SmallVec;
575    use ui::prelude::*;
576    use util::ResultExt;
577
578    use crate::Workspace;
579
580    use crate::WorkspaceSettings;
581
582    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
583
584    const DIVIDER_SIZE: f32 = 1.0;
585
586    pub(super) fn pane_axis(
587        axis: Axis,
588        basis: usize,
589        flexes: Arc<Mutex<Vec<f32>>>,
590        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
591        workspace: WeakView<Workspace>,
592    ) -> PaneAxisElement {
593        PaneAxisElement {
594            axis,
595            basis,
596            flexes,
597            bounding_boxes,
598            children: SmallVec::new(),
599            active_pane_ix: None,
600            workspace,
601        }
602    }
603
604    pub struct PaneAxisElement {
605        axis: Axis,
606        basis: usize,
607        flexes: Arc<Mutex<Vec<f32>>>,
608        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
609        children: SmallVec<[AnyElement; 2]>,
610        active_pane_ix: Option<usize>,
611        workspace: WeakView<Workspace>,
612    }
613
614    impl PaneAxisElement {
615        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
616            self.active_pane_ix = active_pane_ix;
617            self
618        }
619
620        fn compute_resize(
621            flexes: &Arc<Mutex<Vec<f32>>>,
622            e: &MouseMoveEvent,
623            ix: usize,
624            axis: Axis,
625            child_start: Point<Pixels>,
626            container_size: Size<Pixels>,
627            workspace: WeakView<Workspace>,
628            cx: &mut WindowContext,
629        ) {
630            let min_size = match axis {
631                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
632                Axis::Vertical => px(VERTICAL_MIN_SIZE),
633            };
634            let mut flexes = flexes.lock();
635            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
636
637            let size = move |ix, flexes: &[f32]| {
638                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
639            };
640
641            // Don't allow resizing to less than the minimum size, if elements are already too small
642            if min_size - px(1.) > size(ix, flexes.as_slice()) {
643                return;
644            }
645
646            let mut proposed_current_pixel_change =
647                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
648
649            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
650                let flex_change = pixel_dx / container_size.along(axis);
651                let current_target_flex = flexes[target_ix] + flex_change;
652                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
653                (current_target_flex, next_target_flex)
654            };
655
656            let mut successors = iter::from_fn({
657                let forward = proposed_current_pixel_change > px(0.);
658                let mut ix_offset = 0;
659                let len = flexes.len();
660                move || {
661                    let result = if forward {
662                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
663                    } else {
664                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
665                    };
666
667                    ix_offset += 1;
668
669                    result
670                }
671            });
672
673            while proposed_current_pixel_change.abs() > px(0.) {
674                let Some(current_ix) = successors.next() else {
675                    break;
676                };
677
678                let next_target_size = Pixels::max(
679                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
680                    min_size,
681                );
682
683                let current_target_size = Pixels::max(
684                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
685                        - next_target_size,
686                    min_size,
687                );
688
689                let current_pixel_change =
690                    current_target_size - size(current_ix, flexes.as_slice());
691
692                let (current_target_flex, next_target_flex) =
693                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
694
695                flexes[current_ix] = current_target_flex;
696                flexes[current_ix + 1] = next_target_flex;
697
698                proposed_current_pixel_change -= current_pixel_change;
699            }
700
701            workspace
702                .update(cx, |this, cx| this.schedule_serialize(cx))
703                .log_err();
704            cx.notify();
705        }
706
707        fn push_handle(
708            flexes: Arc<Mutex<Vec<f32>>>,
709            dragged_handle: Rc<RefCell<Option<usize>>>,
710            axis: Axis,
711            ix: usize,
712            pane_bounds: Bounds<Pixels>,
713            axis_bounds: Bounds<Pixels>,
714            workspace: WeakView<Workspace>,
715            cx: &mut WindowContext,
716        ) {
717            let handle_bounds = Bounds {
718                origin: pane_bounds.origin.apply_along(axis, |origin| {
719                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
720                }),
721                size: pane_bounds
722                    .size
723                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
724            };
725            let divider_bounds = Bounds {
726                origin: pane_bounds
727                    .origin
728                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
729                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
730            };
731
732            cx.with_z_index(3, |cx| {
733                let interactive_handle_bounds = InteractiveBounds {
734                    bounds: handle_bounds,
735                    stacking_order: cx.stacking_order().clone(),
736                };
737                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
738                    cx.set_cursor_style(match axis {
739                        Axis::Vertical => CursorStyle::ResizeUpDown,
740                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
741                    })
742                }
743
744                cx.add_opaque_layer(handle_bounds);
745                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
746
747                cx.on_mouse_event({
748                    let dragged_handle = dragged_handle.clone();
749                    let flexes = flexes.clone();
750                    let workspace = workspace.clone();
751                    move |e: &MouseDownEvent, phase, cx| {
752                        if phase.bubble() && handle_bounds.contains(&e.position) {
753                            dragged_handle.replace(Some(ix));
754                            if e.click_count >= 2 {
755                                let mut borrow = flexes.lock();
756                                *borrow = vec![1.; borrow.len()];
757                                workspace
758                                    .update(cx, |this, cx| this.schedule_serialize(cx))
759                                    .log_err();
760                                cx.notify();
761                            }
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}