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