pane_group.rs

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