pane_group.rs

  1use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace};
  2use anyhow::{anyhow, Result};
  3use call::{ActiveCall, ParticipantLocation};
  4use collections::HashMap;
  5use gpui::{
  6    elements::*,
  7    geometry::{rect::RectF, vector::Vector2F},
  8    platform::{CursorStyle, MouseButton},
  9    AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
 10};
 11use project::Project;
 12use serde::Deserialize;
 13use std::{cell::RefCell, rc::Rc, sync::Arc};
 14use theme::Theme;
 15
 16const HANDLE_HITBOX_SIZE: f32 = 4.0;
 17const HORIZONTAL_MIN_SIZE: f32 = 80.;
 18const VERTICAL_MIN_SIZE: f32 = 100.;
 19
 20#[derive(Clone, Debug, PartialEq)]
 21pub struct PaneGroup {
 22    pub(crate) root: Member,
 23}
 24
 25impl PaneGroup {
 26    pub(crate) fn with_root(root: Member) -> Self {
 27        Self { root }
 28    }
 29
 30    pub fn new(pane: ViewHandle<Pane>) -> Self {
 31        Self {
 32            root: Member::Pane(pane),
 33        }
 34    }
 35
 36    pub fn split(
 37        &mut self,
 38        old_pane: &ViewHandle<Pane>,
 39        new_pane: &ViewHandle<Pane>,
 40        direction: SplitDirection,
 41    ) -> Result<()> {
 42        match &mut self.root {
 43            Member::Pane(pane) => {
 44                if pane == old_pane {
 45                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 46                    Ok(())
 47                } else {
 48                    Err(anyhow!("Pane not found"))
 49                }
 50            }
 51            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
 52        }
 53    }
 54
 55    pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
 56        match &self.root {
 57            Member::Pane(_) => None,
 58            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
 59        }
 60    }
 61
 62    pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
 63        match &self.root {
 64            Member::Pane(pane) => Some(pane),
 65            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 66        }
 67    }
 68
 69    /// Returns:
 70    /// - Ok(true) if it found and removed a pane
 71    /// - Ok(false) if it found but did not remove the pane
 72    /// - Err(_) if it did not find the pane
 73    pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
 74        match &mut self.root {
 75            Member::Pane(_) => Ok(false),
 76            Member::Axis(axis) => {
 77                if let Some(last_pane) = axis.remove(pane)? {
 78                    self.root = last_pane;
 79                }
 80                Ok(true)
 81            }
 82        }
 83    }
 84
 85    pub fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
 86        match &mut self.root {
 87            Member::Pane(_) => {}
 88            Member::Axis(axis) => axis.swap(from, to),
 89        };
 90    }
 91
 92    pub(crate) fn render(
 93        &self,
 94        project: &ModelHandle<Project>,
 95        theme: &Theme,
 96        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
 97        active_call: Option<&ModelHandle<ActiveCall>>,
 98        active_pane: &ViewHandle<Pane>,
 99        zoomed: Option<&AnyViewHandle>,
100        app_state: &Arc<AppState>,
101        cx: &mut ViewContext<Workspace>,
102    ) -> AnyElement<Workspace> {
103        self.root.render(
104            project,
105            0,
106            theme,
107            follower_states,
108            active_call,
109            active_pane,
110            zoomed,
111            app_state,
112            cx,
113        )
114    }
115
116    pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
117        let mut panes = Vec::new();
118        self.root.collect_panes(&mut panes);
119        panes
120    }
121}
122
123#[derive(Clone, Debug, PartialEq)]
124pub(crate) enum Member {
125    Axis(PaneAxis),
126    Pane(ViewHandle<Pane>),
127}
128
129impl Member {
130    fn new_axis(
131        old_pane: ViewHandle<Pane>,
132        new_pane: ViewHandle<Pane>,
133        direction: SplitDirection,
134    ) -> Self {
135        use Axis::*;
136        use SplitDirection::*;
137
138        let axis = match direction {
139            Up | Down => Vertical,
140            Left | Right => Horizontal,
141        };
142
143        let members = match direction {
144            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
145            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
146        };
147
148        Member::Axis(PaneAxis::new(axis, members))
149    }
150
151    fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
152        match self {
153            Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
154            Member::Pane(pane) => pane == needle,
155        }
156    }
157
158    pub fn render(
159        &self,
160        project: &ModelHandle<Project>,
161        basis: usize,
162        theme: &Theme,
163        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
164        active_call: Option<&ModelHandle<ActiveCall>>,
165        active_pane: &ViewHandle<Pane>,
166        zoomed: Option<&AnyViewHandle>,
167        app_state: &Arc<AppState>,
168        cx: &mut ViewContext<Workspace>,
169    ) -> AnyElement<Workspace> {
170        enum FollowIntoExternalProject {}
171
172        match self {
173            Member::Pane(pane) => {
174                let pane_element = if Some(&**pane) == zoomed {
175                    Empty::new().into_any()
176                } else {
177                    ChildView::new(pane, cx).into_any()
178                };
179
180                let leader = follower_states.get(pane).and_then(|state| {
181                    let room = active_call?.read(cx).room()?.read(cx);
182                    room.remote_participant_for_peer_id(state.leader_id)
183                });
184
185                let mut leader_border = Border::default();
186                let mut leader_status_box = None;
187                if let Some(leader) = &leader {
188                    let leader_color = theme
189                        .editor
190                        .selection_style_for_room_participant(leader.participant_index.0)
191                        .cursor;
192                    leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
193                    leader_border
194                        .color
195                        .fade_out(1. - theme.workspace.leader_border_opacity);
196                    leader_border.overlay = true;
197
198                    leader_status_box = match leader.location {
199                        ParticipantLocation::SharedProject {
200                            project_id: leader_project_id,
201                        } => {
202                            if Some(leader_project_id) == project.read(cx).remote_id() {
203                                None
204                            } else {
205                                let leader_user = leader.user.clone();
206                                let leader_user_id = leader.user.id;
207                                Some(
208                                    MouseEventHandler::new::<FollowIntoExternalProject, _>(
209                                        pane.id(),
210                                        cx,
211                                        |_, _| {
212                                            Label::new(
213                                                format!(
214                                                    "Follow {} to their active project",
215                                                    leader_user.github_login,
216                                                ),
217                                                theme
218                                                    .workspace
219                                                    .external_location_message
220                                                    .text
221                                                    .clone(),
222                                            )
223                                            .contained()
224                                            .with_style(
225                                                theme.workspace.external_location_message.container,
226                                            )
227                                        },
228                                    )
229                                    .with_cursor_style(CursorStyle::PointingHand)
230                                    .on_click(MouseButton::Left, move |_, this, cx| {
231                                        crate::join_remote_project(
232                                            leader_project_id,
233                                            leader_user_id,
234                                            this.app_state().clone(),
235                                            cx,
236                                        )
237                                        .detach_and_log_err(cx);
238                                    })
239                                    .aligned()
240                                    .bottom()
241                                    .right()
242                                    .into_any(),
243                                )
244                            }
245                        }
246                        ParticipantLocation::UnsharedProject => Some(
247                            Label::new(
248                                format!(
249                                    "{} is viewing an unshared Zed project",
250                                    leader.user.github_login
251                                ),
252                                theme.workspace.external_location_message.text.clone(),
253                            )
254                            .contained()
255                            .with_style(theme.workspace.external_location_message.container)
256                            .aligned()
257                            .bottom()
258                            .right()
259                            .into_any(),
260                        ),
261                        ParticipantLocation::External => Some(
262                            Label::new(
263                                format!(
264                                    "{} is viewing a window outside of Zed",
265                                    leader.user.github_login
266                                ),
267                                theme.workspace.external_location_message.text.clone(),
268                            )
269                            .contained()
270                            .with_style(theme.workspace.external_location_message.container)
271                            .aligned()
272                            .bottom()
273                            .right()
274                            .into_any(),
275                        ),
276                    };
277                }
278
279                Stack::new()
280                    .with_child(pane_element.contained().with_border(leader_border))
281                    .with_children(leader_status_box)
282                    .into_any()
283            }
284            Member::Axis(axis) => axis.render(
285                project,
286                basis + 1,
287                theme,
288                follower_states,
289                active_call,
290                active_pane,
291                zoomed,
292                app_state,
293                cx,
294            ),
295        }
296    }
297
298    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
299        match self {
300            Member::Axis(axis) => {
301                for member in &axis.members {
302                    member.collect_panes(panes);
303                }
304            }
305            Member::Pane(pane) => panes.push(pane),
306        }
307    }
308}
309
310#[derive(Clone, Debug, PartialEq)]
311pub(crate) struct PaneAxis {
312    pub axis: Axis,
313    pub members: Vec<Member>,
314    pub flexes: Rc<RefCell<Vec<f32>>>,
315    pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
316}
317
318impl PaneAxis {
319    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
320        let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
321        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
322        Self {
323            axis,
324            members,
325            flexes,
326            bounding_boxes,
327        }
328    }
329
330    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
331        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
332        debug_assert!(members.len() == flexes.len());
333
334        let flexes = Rc::new(RefCell::new(flexes));
335        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
336        Self {
337            axis,
338            members,
339            flexes,
340            bounding_boxes,
341        }
342    }
343
344    fn split(
345        &mut self,
346        old_pane: &ViewHandle<Pane>,
347        new_pane: &ViewHandle<Pane>,
348        direction: SplitDirection,
349    ) -> Result<()> {
350        for (mut idx, member) in self.members.iter_mut().enumerate() {
351            match member {
352                Member::Axis(axis) => {
353                    if axis.split(old_pane, new_pane, direction).is_ok() {
354                        return Ok(());
355                    }
356                }
357                Member::Pane(pane) => {
358                    if pane == old_pane {
359                        if direction.axis() == self.axis {
360                            if direction.increasing() {
361                                idx += 1;
362                            }
363
364                            self.members.insert(idx, Member::Pane(new_pane.clone()));
365                            *self.flexes.borrow_mut() = vec![1.; self.members.len()];
366                        } else {
367                            *member =
368                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
369                        }
370                        return Ok(());
371                    }
372                }
373            }
374        }
375        Err(anyhow!("Pane not found"))
376    }
377
378    fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
379        let mut found_pane = false;
380        let mut remove_member = None;
381        for (idx, member) in self.members.iter_mut().enumerate() {
382            match member {
383                Member::Axis(axis) => {
384                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
385                        if let Some(last_pane) = last_pane {
386                            *member = last_pane;
387                        }
388                        found_pane = true;
389                        break;
390                    }
391                }
392                Member::Pane(pane) => {
393                    if pane == pane_to_remove {
394                        found_pane = true;
395                        remove_member = Some(idx);
396                        break;
397                    }
398                }
399            }
400        }
401
402        if found_pane {
403            if let Some(idx) = remove_member {
404                self.members.remove(idx);
405                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
406            }
407
408            if self.members.len() == 1 {
409                let result = self.members.pop();
410                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
411                Ok(result)
412            } else {
413                Ok(None)
414            }
415        } else {
416            Err(anyhow!("Pane not found"))
417        }
418    }
419
420    fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
421        for member in self.members.iter_mut() {
422            match member {
423                Member::Axis(axis) => axis.swap(from, to),
424                Member::Pane(pane) => {
425                    if pane == from {
426                        *member = Member::Pane(to.clone());
427                    } else if pane == to {
428                        *member = Member::Pane(from.clone())
429                    }
430                }
431            }
432        }
433    }
434
435    fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
436        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
437
438        for (idx, member) in self.members.iter().enumerate() {
439            match member {
440                Member::Pane(found) => {
441                    if pane == found {
442                        return self.bounding_boxes.borrow()[idx];
443                    }
444                }
445                Member::Axis(axis) => {
446                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
447                        return Some(rect);
448                    }
449                }
450            }
451        }
452        None
453    }
454
455    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
456        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
457
458        let bounding_boxes = self.bounding_boxes.borrow();
459
460        for (idx, member) in self.members.iter().enumerate() {
461            if let Some(coordinates) = bounding_boxes[idx] {
462                if coordinates.contains_point(coordinate) {
463                    return match member {
464                        Member::Pane(found) => Some(found),
465                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
466                    };
467                }
468            }
469        }
470        None
471    }
472
473    fn render(
474        &self,
475        project: &ModelHandle<Project>,
476        basis: usize,
477        theme: &Theme,
478        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
479        active_call: Option<&ModelHandle<ActiveCall>>,
480        active_pane: &ViewHandle<Pane>,
481        zoomed: Option<&AnyViewHandle>,
482        app_state: &Arc<AppState>,
483        cx: &mut ViewContext<Workspace>,
484    ) -> AnyElement<Workspace> {
485        debug_assert!(self.members.len() == self.flexes.borrow().len());
486
487        let mut pane_axis = PaneAxisElement::new(
488            self.axis,
489            basis,
490            self.flexes.clone(),
491            self.bounding_boxes.clone(),
492        );
493        let mut active_pane_ix = None;
494
495        let mut members = self.members.iter().enumerate().peekable();
496        while let Some((ix, member)) = members.next() {
497            let last = members.peek().is_none();
498
499            if member.contains(active_pane) {
500                active_pane_ix = Some(ix);
501            }
502
503            let mut member = member.render(
504                project,
505                (basis + ix) * 10,
506                theme,
507                follower_states,
508                active_call,
509                active_pane,
510                zoomed,
511                app_state,
512                cx,
513            );
514
515            if !last {
516                let mut border = theme.workspace.pane_divider;
517                border.left = false;
518                border.right = false;
519                border.top = false;
520                border.bottom = false;
521
522                match self.axis {
523                    Axis::Vertical => border.bottom = true,
524                    Axis::Horizontal => border.right = true,
525                }
526
527                member = member.contained().with_border(border).into_any();
528            }
529
530            pane_axis = pane_axis.with_child(member.into_any());
531        }
532        pane_axis.set_active_pane(active_pane_ix);
533        pane_axis.into_any()
534    }
535}
536
537#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
538pub enum SplitDirection {
539    Up,
540    Down,
541    Left,
542    Right,
543}
544
545impl SplitDirection {
546    pub fn all() -> [Self; 4] {
547        [Self::Up, Self::Down, Self::Left, Self::Right]
548    }
549
550    pub fn edge(&self, rect: RectF) -> f32 {
551        match self {
552            Self::Up => rect.min_y(),
553            Self::Down => rect.max_y(),
554            Self::Left => rect.min_x(),
555            Self::Right => rect.max_x(),
556        }
557    }
558
559    // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
560    pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
561        match self {
562            Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
563            Self::Down => RectF::new(
564                rect.lower_left() - Vector2F::new(0., size),
565                Vector2F::new(rect.width(), size),
566            ),
567            Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
568            Self::Right => RectF::new(
569                rect.upper_right() - Vector2F::new(size, 0.),
570                Vector2F::new(size, rect.height()),
571            ),
572        }
573    }
574
575    pub fn axis(&self) -> Axis {
576        match self {
577            Self::Up | Self::Down => Axis::Vertical,
578            Self::Left | Self::Right => Axis::Horizontal,
579        }
580    }
581
582    pub fn increasing(&self) -> bool {
583        match self {
584            Self::Left | Self::Up => false,
585            Self::Down | Self::Right => true,
586        }
587    }
588}
589
590mod element {
591    use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
592
593    use gpui::{
594        geometry::{
595            rect::RectF,
596            vector::{vec2f, Vector2F},
597        },
598        json::{self, ToJson},
599        platform::{CursorStyle, MouseButton},
600        scene::MouseDrag,
601        AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
602        SizeConstraint, Vector2FExt, ViewContext,
603    };
604
605    use crate::{
606        pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
607        Workspace, WorkspaceSettings,
608    };
609
610    pub struct PaneAxisElement {
611        axis: Axis,
612        basis: usize,
613        active_pane_ix: Option<usize>,
614        flexes: Rc<RefCell<Vec<f32>>>,
615        children: Vec<AnyElement<Workspace>>,
616        bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
617    }
618
619    impl PaneAxisElement {
620        pub fn new(
621            axis: Axis,
622            basis: usize,
623            flexes: Rc<RefCell<Vec<f32>>>,
624            bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
625        ) -> Self {
626            Self {
627                axis,
628                basis,
629                flexes,
630                bounding_boxes,
631                active_pane_ix: None,
632                children: Default::default(),
633            }
634        }
635
636        pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
637            self.active_pane_ix = active_pane_ix;
638        }
639
640        fn layout_children(
641            &mut self,
642            active_pane_magnification: f32,
643            constraint: SizeConstraint,
644            remaining_space: &mut f32,
645            remaining_flex: &mut f32,
646            cross_axis_max: &mut f32,
647            view: &mut Workspace,
648            cx: &mut ViewContext<Workspace>,
649        ) {
650            let flexes = self.flexes.borrow();
651            let cross_axis = self.axis.invert();
652            for (ix, child) in self.children.iter_mut().enumerate() {
653                let flex = if active_pane_magnification != 1. {
654                    if let Some(active_pane_ix) = self.active_pane_ix {
655                        if ix == active_pane_ix {
656                            active_pane_magnification
657                        } else {
658                            1.
659                        }
660                    } else {
661                        1.
662                    }
663                } else {
664                    flexes[ix]
665                };
666
667                let child_size = if *remaining_flex == 0.0 {
668                    *remaining_space
669                } else {
670                    let space_per_flex = *remaining_space / *remaining_flex;
671                    space_per_flex * flex
672                };
673
674                let child_constraint = match self.axis {
675                    Axis::Horizontal => SizeConstraint::new(
676                        vec2f(child_size, constraint.min.y()),
677                        vec2f(child_size, constraint.max.y()),
678                    ),
679                    Axis::Vertical => SizeConstraint::new(
680                        vec2f(constraint.min.x(), child_size),
681                        vec2f(constraint.max.x(), child_size),
682                    ),
683                };
684                let child_size = child.layout(child_constraint, view, cx);
685                *remaining_space -= child_size.along(self.axis);
686                *remaining_flex -= flex;
687                *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
688            }
689        }
690
691        fn handle_resize(
692            flexes: Rc<RefCell<Vec<f32>>>,
693            axis: Axis,
694            preceding_ix: usize,
695            child_start: Vector2F,
696            drag_bounds: RectF,
697        ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
698            let size = move |ix, flexes: &[f32]| {
699                drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
700            };
701
702            move |drag, workspace: &mut Workspace, cx| {
703                if drag.end {
704                    // TODO: Clear cascading resize state
705                    return;
706                }
707                let min_size = match axis {
708                    Axis::Horizontal => HORIZONTAL_MIN_SIZE,
709                    Axis::Vertical => VERTICAL_MIN_SIZE,
710                };
711                let mut flexes = flexes.borrow_mut();
712
713                // Don't allow resizing to less than the minimum size, if elements are already too small
714                if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
715                    return;
716                }
717
718                let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
719                    - size(preceding_ix, flexes.as_slice());
720
721                let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
722                    let flex_change = pixel_dx / drag_bounds.length_along(axis);
723                    let current_target_flex = flexes[target_ix] + flex_change;
724                    let next_target_flex =
725                        flexes[(target_ix as isize + next) as usize] - flex_change;
726                    (current_target_flex, next_target_flex)
727                };
728
729                let mut successors = from_fn({
730                    let forward = proposed_current_pixel_change > 0.;
731                    let mut ix_offset = 0;
732                    let len = flexes.len();
733                    move || {
734                        let result = if forward {
735                            (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
736                        } else {
737                            (preceding_ix as isize - ix_offset as isize >= 0)
738                                .then(|| preceding_ix - ix_offset)
739                        };
740
741                        ix_offset += 1;
742
743                        result
744                    }
745                });
746
747                while proposed_current_pixel_change.abs() > 0. {
748                    let Some(current_ix) = successors.next() else {
749                        break;
750                    };
751
752                    let next_target_size = f32::max(
753                        size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
754                        min_size,
755                    );
756
757                    let current_target_size = f32::max(
758                        size(current_ix, flexes.as_slice())
759                            + size(current_ix + 1, flexes.as_slice())
760                            - next_target_size,
761                        min_size,
762                    );
763
764                    let current_pixel_change =
765                        current_target_size - size(current_ix, flexes.as_slice());
766
767                    let (current_target_flex, next_target_flex) =
768                        flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
769
770                    flexes[current_ix] = current_target_flex;
771                    flexes[current_ix + 1] = next_target_flex;
772
773                    proposed_current_pixel_change -= current_pixel_change;
774                }
775
776                workspace.schedule_serialize(cx);
777                cx.notify();
778            }
779        }
780    }
781
782    impl Extend<AnyElement<Workspace>> for PaneAxisElement {
783        fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
784            self.children.extend(children);
785        }
786    }
787
788    impl Element<Workspace> for PaneAxisElement {
789        type LayoutState = f32;
790        type PaintState = ();
791
792        fn layout(
793            &mut self,
794            constraint: SizeConstraint,
795            view: &mut Workspace,
796            cx: &mut ViewContext<Workspace>,
797        ) -> (Vector2F, Self::LayoutState) {
798            debug_assert!(self.children.len() == self.flexes.borrow().len());
799
800            let active_pane_magnification =
801                settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
802
803            let mut remaining_flex = 0.;
804
805            if active_pane_magnification != 1. {
806                let active_pane_flex = self
807                    .active_pane_ix
808                    .map(|_| active_pane_magnification)
809                    .unwrap_or(1.);
810                remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
811            } else {
812                for flex in self.flexes.borrow().iter() {
813                    remaining_flex += flex;
814                }
815            }
816
817            let mut cross_axis_max: f32 = 0.0;
818            let mut remaining_space = constraint.max_along(self.axis);
819
820            if remaining_space.is_infinite() {
821                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
822            }
823
824            self.layout_children(
825                active_pane_magnification,
826                constraint,
827                &mut remaining_space,
828                &mut remaining_flex,
829                &mut cross_axis_max,
830                view,
831                cx,
832            );
833
834            let mut size = match self.axis {
835                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
836                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
837            };
838
839            if constraint.min.x().is_finite() {
840                size.set_x(size.x().max(constraint.min.x()));
841            }
842            if constraint.min.y().is_finite() {
843                size.set_y(size.y().max(constraint.min.y()));
844            }
845
846            if size.x() > constraint.max.x() {
847                size.set_x(constraint.max.x());
848            }
849            if size.y() > constraint.max.y() {
850                size.set_y(constraint.max.y());
851            }
852
853            (size, remaining_space)
854        }
855
856        fn paint(
857            &mut self,
858            bounds: RectF,
859            visible_bounds: RectF,
860            remaining_space: &mut Self::LayoutState,
861            view: &mut Workspace,
862            cx: &mut ViewContext<Workspace>,
863        ) -> Self::PaintState {
864            let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
865            let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
866
867            let overflowing = *remaining_space < 0.;
868            if overflowing {
869                cx.scene().push_layer(Some(visible_bounds));
870            }
871
872            let mut child_origin = bounds.origin();
873
874            let mut bounding_boxes = self.bounding_boxes.borrow_mut();
875            bounding_boxes.clear();
876
877            let mut children_iter = self.children.iter_mut().enumerate().peekable();
878            while let Some((ix, child)) = children_iter.next() {
879                let child_start = child_origin.clone();
880                child.paint(child_origin, visible_bounds, view, cx);
881
882                bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
883
884                match self.axis {
885                    Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
886                    Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
887                }
888
889                if can_resize && children_iter.peek().is_some() {
890                    cx.scene().push_stacking_context(None, None);
891
892                    let handle_origin = match self.axis {
893                        Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
894                        Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
895                    };
896
897                    let handle_bounds = match self.axis {
898                        Axis::Horizontal => RectF::new(
899                            handle_origin,
900                            vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
901                        ),
902                        Axis::Vertical => RectF::new(
903                            handle_origin,
904                            vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
905                        ),
906                    };
907
908                    let style = match self.axis {
909                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
910                        Axis::Vertical => CursorStyle::ResizeUpDown,
911                    };
912
913                    cx.scene().push_cursor_region(CursorRegion {
914                        bounds: handle_bounds,
915                        style,
916                    });
917
918                    enum ResizeHandle {}
919                    let mut mouse_region = MouseRegion::new::<ResizeHandle>(
920                        cx.view_id(),
921                        self.basis + ix,
922                        handle_bounds,
923                    );
924                    mouse_region = mouse_region
925                        .on_drag(
926                            MouseButton::Left,
927                            Self::handle_resize(
928                                self.flexes.clone(),
929                                self.axis,
930                                ix,
931                                child_start,
932                                visible_bounds.clone(),
933                            ),
934                        )
935                        .on_click(MouseButton::Left, {
936                            let flexes = self.flexes.clone();
937                            move |e, v: &mut Workspace, cx| {
938                                if e.click_count >= 2 {
939                                    let mut borrow = flexes.borrow_mut();
940                                    *borrow = vec![1.; borrow.len()];
941                                    v.schedule_serialize(cx);
942                                    cx.notify();
943                                }
944                            }
945                        });
946                    cx.scene().push_mouse_region(mouse_region);
947
948                    cx.scene().pop_stacking_context();
949                }
950            }
951
952            if overflowing {
953                cx.scene().pop_layer();
954            }
955        }
956
957        fn rect_for_text_range(
958            &self,
959            range_utf16: Range<usize>,
960            _: RectF,
961            _: RectF,
962            _: &Self::LayoutState,
963            _: &Self::PaintState,
964            view: &Workspace,
965            cx: &ViewContext<Workspace>,
966        ) -> Option<RectF> {
967            self.children
968                .iter()
969                .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
970        }
971
972        fn debug(
973            &self,
974            bounds: RectF,
975            _: &Self::LayoutState,
976            _: &Self::PaintState,
977            view: &Workspace,
978            cx: &ViewContext<Workspace>,
979        ) -> json::Value {
980            serde_json::json!({
981                "type": "PaneAxis",
982                "bounds": bounds.to_json(),
983                "axis": self.axis.to_json(),
984                "flexes": *self.flexes.borrow(),
985                "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
986            })
987        }
988    }
989}