Implement rendering of panels' flex content

Max Brunsfeld and Eric Holk created

Co-authored-by: Eric Holk <eric@zed.dev>

Change summary

crates/agent_ui/src/agent_panel.rs         |   6 
crates/debugger_ui/src/session/running.rs  |   2 
crates/terminal_view/src/terminal_panel.rs |   2 
crates/workspace/src/dock.rs               |  32 
crates/workspace/src/pane_group.rs         | 410 +++++++++++++++++------
crates/workspace/src/workspace.rs          | 104 +----
6 files changed, 358 insertions(+), 198 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -3186,7 +3186,7 @@ impl Panel for AgentPanel {
         cx.notify();
     }
 
-    fn render_center_element(
+    fn render_flex_content(
         &mut self,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -3194,11 +3194,11 @@ impl Panel for AgentPanel {
         Some(self.render_content(window, cx).into_any_element())
     }
 
-    fn has_center_element(&self, _window: &Window, _: &App) -> bool {
+    fn has_flex_content(&self, _window: &Window, _: &App) -> bool {
         true
     }
 
-    fn has_main_element(&self, window: &Window, cx: &App) -> bool {
+    fn has_panel_content(&self, window: &Window, cx: &App) -> bool {
         sidebar_is_open(window, cx)
     }
 }

crates/debugger_ui/src/session/running.rs 🔗

@@ -115,6 +115,8 @@ impl Render for RunningState {
         } else if let Some(active) = active {
             self.panes
                 .render(
+                    None,
+                    None,
                     None,
                     &ActivePaneDecorator::new(active, &self.workspace),
                     window,

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1362,6 +1362,8 @@ impl Render for TerminalPanel {
             .update(cx, |workspace, cx| {
                 registrar.size_full().child(self.center.render(
                     workspace.zoomed_item(),
+                    None,
+                    None,
                     &workspace::PaneRenderContext {
                         follower_states: &HashMap::default(),
                         active_call: workspace.active_call(),

crates/workspace/src/dock.rs 🔗

@@ -59,17 +59,17 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
     fn enabled(&self, _cx: &App) -> bool {
         true
     }
-    fn render_center_element(
+    fn render_flex_content(
         &mut self,
         _window: &mut Window,
         _cx: &mut Context<Self>,
     ) -> Option<AnyElement> {
         None
     }
-    fn has_center_element(&self, _window: &Window, _cx: &App) -> bool {
+    fn has_flex_content(&self, _window: &Window, _cx: &App) -> bool {
         false
     }
-    fn has_main_element(&self, _window: &Window, _cx: &App) -> bool {
+    fn has_panel_content(&self, _window: &Window, _cx: &App) -> bool {
         true
     }
 }
@@ -96,9 +96,9 @@ pub trait PanelHandle: Send + Sync {
     fn to_any(&self) -> AnyView;
     fn activation_priority(&self, cx: &App) -> u32;
     fn enabled(&self, cx: &App) -> bool;
-    fn center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView>;
-    fn has_center_element(&self, window: &Window, cx: &App) -> bool;
-    fn has_main_element(&self, window: &Window, cx: &App) -> bool;
+    fn flex_content(&self, window: &Window, cx: &mut App) -> Option<AnyView>;
+    fn has_flex_content(&self, window: &Window, cx: &App) -> bool;
+    fn has_panel_content(&self, window: &Window, cx: &App) -> bool;
     fn move_to_next_position(&self, window: &mut Window, cx: &mut App) {
         let current_position = self.position(window, cx);
         let next_position = [
@@ -204,19 +204,19 @@ where
         self.read(cx).enabled(cx)
     }
 
-    fn center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
-        if !self.read(cx).has_center_element(window, cx) {
+    fn flex_content(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
+        if !self.read(cx).has_flex_content(window, cx) {
             return None;
         }
         Some(cx.new(|_| PanelCenterView(self.clone())).into())
     }
 
-    fn has_center_element(&self, window: &Window, cx: &App) -> bool {
-        self.read(cx).has_center_element(window, cx)
+    fn has_flex_content(&self, window: &Window, cx: &App) -> bool {
+        self.read(cx).has_flex_content(window, cx)
     }
 
-    fn has_main_element(&self, window: &Window, cx: &App) -> bool {
-        self.read(cx).has_main_element(window, cx)
+    fn has_panel_content(&self, window: &Window, cx: &App) -> bool {
+        self.read(cx).has_panel_content(window, cx)
     }
 }
 
@@ -225,7 +225,7 @@ struct PanelCenterView<T: Panel>(Entity<T>);
 impl<T: Panel> Render for PanelCenterView<T> {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         self.0.update(cx, |this, cx| {
-            this.render_center_element(window, cx)
+            this.render_flex_content(window, cx)
                 .unwrap_or_else(|| gpui::Empty.into_any_element())
         })
     }
@@ -732,9 +732,9 @@ impl Dock {
         Some(&entry.panel)
     }
 
-    pub fn visible_panel_center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
+    pub fn visible_panel_flex_element(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
         let entry = self.visible_entry()?;
-        entry.panel.center_element(window, cx)
+        entry.panel.flex_content(window, cx)
     }
 
     pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
@@ -832,7 +832,7 @@ impl Render for Dock {
         let dispatch_context = Self::dispatch_context();
         if let Some(entry) = self
             .visible_entry()
-            .filter(|entry| entry.panel.has_main_element(window, cx))
+            .filter(|entry| entry.panel.has_panel_content(window, cx))
         {
             let size = entry.panel.size(window, cx);
 

crates/workspace/src/pane_group.rs 🔗

@@ -27,6 +27,7 @@ const VERTICAL_MIN_SIZE: f32 = 100.;
 #[derive(Clone)]
 pub struct PaneGroup {
     pub root: Member,
+    pub state: PaneGroupState,
     pub is_center: bool,
 }
 
@@ -39,15 +40,13 @@ impl PaneGroup {
     pub fn with_root(root: Member) -> Self {
         Self {
             root,
+            state: PaneGroupState::default(),
             is_center: false,
         }
     }
 
     pub fn new(pane: Entity<Pane>) -> Self {
-        Self {
-            root: Member::Pane(pane),
-            is_center: false,
-        }
+        Self::with_root(Member::Pane(pane))
     }
 
     pub fn set_is_center(&mut self, is_center: bool) {
@@ -65,6 +64,7 @@ impl PaneGroup {
             Member::Pane(pane) => {
                 if pane == old_pane {
                     self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
+                    self.state.reset_flexes();
                     true
                 } else {
                     false
@@ -174,6 +174,7 @@ impl PaneGroup {
             Member::Axis(axis) => {
                 if let Some(last_pane) = axis.remove(pane)? {
                     self.root = last_pane;
+                    self.state.reset_flexes();
                 }
                 Ok(true)
             }
@@ -222,11 +223,46 @@ impl PaneGroup {
     pub fn render(
         &self,
         zoomed: Option<&AnyWeakView>,
+        left_content: Option<AnyView>,
+        right_content: Option<AnyView>,
         render_cx: &dyn PaneLeaderDecorator,
         window: &mut Window,
         cx: &mut App,
     ) -> impl IntoElement {
-        self.root.render(0, zoomed, render_cx, window, cx).element
+        let mut state = self.state.0.borrow_mut();
+        if left_content.is_some() {
+            state.left_entry_is_active = true;
+            state.left_entry.get_or_insert(PaneAxisStateEntry {
+                flex: 1.0,
+                bounding_box: None,
+            });
+        } else {
+            state.left_entry_is_active = false;
+        }
+
+        if right_content.is_some() {
+            state.right_entry_is_active = true;
+            state.right_entry.get_or_insert(PaneAxisStateEntry {
+                flex: 1.0,
+                bounding_box: None,
+            });
+        } else {
+            state.right_entry_is_active = false;
+        }
+        drop(state);
+
+        self.root
+            .render(
+                0,
+                zoomed,
+                left_content,
+                right_content,
+                Some(self.state.clone()),
+                render_cx,
+                window,
+                cx,
+            )
+            .element
     }
 
     pub fn panes(&self) -> Vec<&Entity<Pane>> {
@@ -510,10 +546,66 @@ impl Member {
         &self,
         basis: usize,
         zoomed: Option<&AnyWeakView>,
+        left_content: Option<AnyView>,
+        right_content: Option<AnyView>,
+        pane_group_state: Option<PaneGroupState>,
         render_cx: &dyn PaneLeaderDecorator,
         window: &mut Window,
         cx: &mut App,
     ) -> PaneRenderResult {
+        if let Some(pane_group_state) = pane_group_state
+            && (left_content.is_some() || right_content.is_some())
+        {
+            return match self {
+                Member::Axis(axis) if axis.axis == Axis::Horizontal => axis.render(
+                    basis + 1,
+                    zoomed,
+                    Some(pane_group_state),
+                    left_content,
+                    right_content,
+                    render_cx,
+                    window,
+                    cx,
+                ),
+                _ => {
+                    let mut active_pane_ix = 0;
+                    if left_content.is_some() {
+                        active_pane_ix += 1;
+                    }
+                    if right_content.is_some() {
+                        active_pane_ix += 1;
+                    }
+
+                    let inner = self.render(0, zoomed, None, None, None, render_cx, window, cx);
+
+                    let mut children = Vec::new();
+                    children.extend(left_content.map(|content| content.into_any_element()));
+                    children.push(inner.element);
+                    children.extend(right_content.map(|content| content.into_any_element()));
+
+                    let element = pane_axis(
+                        Axis::Horizontal,
+                        0,
+                        PaneAxisState::with_flexes(vec![
+                            children.len() as f32
+                                - pane_group_state.total_flex();
+                            1
+                        ]),
+                        Some(pane_group_state),
+                        render_cx.workspace().clone(),
+                    )
+                    .with_active_pane(Some(active_pane_ix))
+                    .with_is_leaf_pane_mask(vec![true, matches!(self, Member::Pane(_)), true])
+                    .children(children)
+                    .into_any_element();
+                    PaneRenderResult {
+                        element,
+                        contains_active_pane: inner.contains_active_pane,
+                    }
+                }
+            };
+        }
+
         match self {
             Member::Pane(pane) => {
                 if zoomed == Some(&pane.downgrade().into()) {
@@ -551,7 +643,9 @@ impl Member {
                     contains_active_pane: is_active,
                 }
             }
-            Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx),
+            Member::Axis(axis) => {
+                axis.render(basis + 1, zoomed, None, None, None, render_cx, window, cx)
+            }
         }
     }
 
@@ -582,56 +676,116 @@ impl Member {
 #[derive(Debug, Clone)]
 pub struct PaneAxisState(Rc<RefCell<PaneAxisStateInner>>);
 
+#[derive(Default, Debug, Clone)]
+pub struct PaneGroupState(Rc<RefCell<PaneGroupStateInner>>);
+
 #[derive(Debug)]
 struct PaneAxisStateInner {
-    flexes: Vec<f32>,
-    bounding_boxes: Vec<Option<Bounds<Pixels>>>,
+    entries: Vec<PaneAxisStateEntry>,
+}
+
+#[derive(Default, Debug)]
+struct PaneGroupStateInner {
+    left_entry: Option<PaneAxisStateEntry>,
+    left_entry_is_active: bool,
+    right_entry: Option<PaneAxisStateEntry>,
+    right_entry_is_active: bool,
+}
+
+#[derive(Clone, Copy, Debug)]
+struct PaneAxisStateEntry {
+    flex: f32,
+    bounding_box: Option<Bounds<Pixels>>,
+}
+
+impl PaneGroupState {
+    fn total_flex(&self) -> f32 {
+        let state = self.0.borrow();
+        state.left_entry.as_ref().map_or(0., |e| e.flex)
+            + state.right_entry.as_ref().map_or(0., |e| e.flex)
+    }
+
+    fn reset_flexes(&self) {
+        let mut state = self.0.borrow_mut();
+        if let Some(left_entry) = state.left_entry.as_mut() {
+            left_entry.flex = 1.0;
+            left_entry.bounding_box = None;
+        }
+        if let Some(right_entry) = state.right_entry.as_mut() {
+            right_entry.flex = 1.0;
+            right_entry.bounding_box = None;
+        }
+    }
 }
 
 impl PaneAxisState {
     pub fn new(member_count: usize) -> Self {
         Self(Rc::new(RefCell::new(PaneAxisStateInner {
-            flexes: vec![1.; member_count],
-            bounding_boxes: vec![None; member_count],
+            entries: vec![
+                PaneAxisStateEntry {
+                    flex: 1.,
+                    bounding_box: None
+                };
+                member_count
+            ],
         })))
     }
 
     fn with_flexes(flexes: Vec<f32>) -> Self {
-        let member_count = flexes.len();
         Self(Rc::new(RefCell::new(PaneAxisStateInner {
-            flexes,
-            bounding_boxes: vec![None; member_count],
+            entries: flexes
+                .into_iter()
+                .map(|flex| PaneAxisStateEntry {
+                    flex,
+                    bounding_box: None,
+                })
+                .collect(),
         })))
     }
 
     pub fn flexes(&self) -> Vec<f32> {
-        self.0.borrow().flexes.clone()
+        self.0.borrow().entries.iter().map(|e| e.flex).collect()
     }
 
     pub fn len(&self) -> usize {
-        self.0.borrow().flexes.len()
+        self.0.borrow().entries.len()
     }
 
     fn resize(&self, len: usize) {
         let mut inner = self.0.borrow_mut();
-        inner.flexes.clear();
-        inner.flexes.resize(len, 1.);
-        inner.bounding_boxes.clear();
-        inner.bounding_boxes.resize(len, None);
+        inner.entries.clear();
+        inner.entries.resize(
+            len,
+            PaneAxisStateEntry {
+                flex: 1.,
+                bounding_box: None,
+            },
+        );
     }
 
     fn reset_flexes(&self) {
         let mut inner = self.0.borrow_mut();
-        inner.flexes.iter_mut().for_each(|f| *f = 1.);
-        inner.bounding_boxes.iter_mut().for_each(|f| *f = None);
+        inner.entries.iter_mut().for_each(|e| {
+            e.flex = 1.;
+            e.bounding_box = None;
+        });
     }
 
-    fn bounding_boxes(&self) -> Vec<Option<Bounds<Pixels>>> {
-        self.0.borrow().bounding_boxes.clone()
+    fn bounding_box_at(&self, index: usize) -> Option<Bounds<Pixels>> {
+        self.0
+            .borrow()
+            .entries
+            .get(index)
+            .and_then(|e| e.bounding_box)
     }
 
-    fn bounding_box_at(&self, index: usize) -> Option<Bounds<Pixels>> {
-        self.0.borrow().bounding_boxes[index]
+    fn bounds(&self) -> Option<Bounds<Pixels>> {
+        self.0
+            .borrow()
+            .entries
+            .iter()
+            .filter_map(|e| e.bounding_box)
+            .reduce(|acc, e| acc.union(&e))
     }
 }
 
@@ -778,14 +932,7 @@ impl PaneAxis {
         amount: Pixels,
         bounds: &Bounds<Pixels>,
     ) -> Option<bool> {
-        let container_size = self
-            .state
-            .bounding_boxes()
-            .iter()
-            .filter_map(|e| *e)
-            .reduce(|acc, e| acc.union(&e))
-            .unwrap_or(*bounds)
-            .size;
+        let container_size = self.state.bounds().unwrap_or(*bounds).size;
 
         let found_pane = self
             .members
@@ -818,7 +965,6 @@ impl PaneAxis {
             Axis::Vertical => px(VERTICAL_MIN_SIZE),
         };
         let mut state = self.state.0.borrow_mut();
-        let flexes = &mut state.flexes;
 
         let ix = if found_pane {
             self.members.iter().position(|m| {
@@ -838,46 +984,48 @@ impl PaneAxis {
 
         let ix = ix.unwrap_or(0);
 
-        let size = move |ix, flexes: &[f32]| {
-            container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
+        let size = move |ix: usize, state: &PaneAxisStateInner| {
+            container_size.along(axis) * (state.entries[ix].flex / state.entries.len() as f32)
         };
 
         // Don't allow resizing to less than the minimum size, if elements are already too small
-        if min_size - px(1.) > size(ix, flexes.as_slice()) {
+        if min_size - px(1.) > size(ix, &state) {
             return Some(true);
         }
 
-        let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-            let flex_change = flexes.len() as f32 * pixel_dx / container_size.along(axis);
-            let current_target_flex = flexes[target_ix] + flex_change;
-            let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
+        let flex_changes = |pixel_dx, target_ix: usize, next: isize, state: &PaneAxisStateInner| {
+            let flex_change = state.entries.len() as f32 * pixel_dx / container_size.along(axis);
+            let current_target_flex = state.entries[target_ix].flex + flex_change;
+            let next_target_flex =
+                state.entries[(target_ix as isize + next) as usize].flex - flex_change;
             (current_target_flex, next_target_flex)
         };
 
-        let apply_changes =
-            |current_ix: usize, proposed_current_pixel_change: Pixels, flexes: &mut [f32]| {
-                let next_target_size = Pixels::max(
-                    size(current_ix + 1, flexes) - proposed_current_pixel_change,
-                    min_size,
-                );
-                let current_target_size = Pixels::max(
-                    size(current_ix, flexes) + size(current_ix + 1, flexes) - next_target_size,
-                    min_size,
-                );
+        let apply_changes = |current_ix: usize,
+                             proposed_current_pixel_change: Pixels,
+                             state: &mut PaneAxisStateInner| {
+            let next_target_size = Pixels::max(
+                size(current_ix + 1, state) - proposed_current_pixel_change,
+                min_size,
+            );
+            let current_target_size = Pixels::max(
+                size(current_ix, state) + size(current_ix + 1, state) - next_target_size,
+                min_size,
+            );
 
-                let current_pixel_change = current_target_size - size(current_ix, flexes);
+            let current_pixel_change = current_target_size - size(current_ix, state);
 
-                let (current_target_flex, next_target_flex) =
-                    flex_changes(current_pixel_change, current_ix, 1, flexes);
+            let (current_target_flex, next_target_flex) =
+                flex_changes(current_pixel_change, current_ix, 1, state);
 
-                flexes[current_ix] = current_target_flex;
-                flexes[current_ix + 1] = next_target_flex;
-            };
+            state.entries[current_ix].flex = current_target_flex;
+            state.entries[current_ix + 1].flex = next_target_flex;
+        };
 
-        if ix + 1 == flexes.len() {
-            apply_changes(ix - 1, -1.0 * amount, flexes.as_mut_slice());
+        if ix + 1 == state.entries.len() {
+            apply_changes(ix - 1, -1.0 * amount, &mut *state);
         } else {
-            apply_changes(ix, amount, flexes.as_mut_slice());
+            apply_changes(ix, amount, &mut *state);
         }
         Some(true)
     }
@@ -920,10 +1068,8 @@ impl PaneAxis {
     fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
         debug_assert!(self.members.len() == self.state.len());
 
-        let bounding_boxes = self.state.bounding_boxes();
-
         for (idx, member) in self.members.iter().enumerate() {
-            if let Some(coordinates) = bounding_boxes[idx]
+            if let Some(coordinates) = self.state.bounding_box_at(idx)
                 && coordinates.contains(&coordinate)
             {
                 return match member {
@@ -939,6 +1085,9 @@ impl PaneAxis {
         &self,
         basis: usize,
         zoomed: Option<&AnyWeakView>,
+        pane_group_state: Option<PaneGroupState>,
+        left_content: Option<AnyView>,
+        right_content: Option<AnyView>,
         render_cx: &dyn PaneLeaderDecorator,
         window: &mut Window,
         cx: &mut App,
@@ -948,11 +1097,10 @@ impl PaneAxis {
         let mut contains_active_pane = false;
         let mut is_leaf_pane = vec![false; self.members.len()];
 
-        let rendered_children = self
-            .members
-            .iter()
-            .enumerate()
-            .map(|(ix, member)| {
+        let rendered_children = left_content
+            .map(|view| view.into_any_element())
+            .into_iter()
+            .chain(self.members.iter().enumerate().map(|(ix, member)| {
                 match member {
                     Member::Pane(pane) => {
                         is_leaf_pane[ix] = true;
@@ -966,18 +1114,29 @@ impl PaneAxis {
                     }
                 }
 
-                let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx);
+                let result = member.render(
+                    (basis + ix) * 10,
+                    zoomed,
+                    None,
+                    None,
+                    None,
+                    render_cx,
+                    window,
+                    cx,
+                );
                 if result.contains_active_pane {
                     contains_active_pane = true;
                 }
                 result.element.into_any_element()
-            })
+            }))
+            .chain(right_content.map(|view| view.into_any_element()))
             .collect::<Vec<_>>();
 
         let element = pane_axis(
             self.axis,
             basis,
             self.state.clone(),
+            pane_group_state,
             render_cx.workspace().clone(),
         )
         .with_is_leaf_pane_mask(is_leaf_pane)
@@ -992,6 +1151,35 @@ impl PaneAxis {
     }
 }
 
+impl PaneAxisStateInner {
+    pub fn entries<'a>(
+        &'a mut self,
+        pane_group_state: Option<&'a mut PaneGroupStateInner>,
+    ) -> Vec<&'a mut PaneAxisStateEntry> {
+        let mut entries = Vec::new();
+        if let Some(pane_group_state) = pane_group_state {
+            if let Some(left) = pane_group_state
+                .left_entry
+                .as_mut()
+                .filter(|_| pane_group_state.left_entry_is_active)
+            {
+                entries.push(left);
+            }
+            entries.extend(self.entries.iter_mut());
+            if let Some(right) = pane_group_state
+                .right_entry
+                .as_mut()
+                .filter(|_| pane_group_state.right_entry_is_active)
+            {
+                entries.push(right);
+            }
+        } else {
+            entries.extend(self.entries.iter_mut());
+        }
+        entries
+    }
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum SplitDirection {
@@ -1103,6 +1291,7 @@ pub mod element {
     use crate::Workspace;
 
     use crate::WorkspaceSettings;
+    use crate::pane_group::{PaneAxisStateEntry, PaneGroupState};
 
     use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, PaneAxisState, VERTICAL_MIN_SIZE};
 
@@ -1112,12 +1301,14 @@ pub mod element {
         axis: Axis,
         basis: usize,
         state: PaneAxisState,
+        pane_group_state: Option<PaneGroupState>,
         workspace: WeakEntity<Workspace>,
     ) -> PaneAxisElement {
         PaneAxisElement {
             axis,
             basis,
             state,
+            pane_group_state,
             children: SmallVec::new(),
             active_pane_ix: None,
             workspace,
@@ -1129,6 +1320,7 @@ pub mod element {
         axis: Axis,
         basis: usize,
         state: PaneAxisState,
+        pane_group_state: Option<PaneGroupState>,
         children: SmallVec<[AnyElement; 2]>,
         active_pane_ix: Option<usize>,
         workspace: WeakEntity<Workspace>,
@@ -1165,6 +1357,7 @@ pub mod element {
         }
 
         fn compute_resize(
+            pane_group_state: Option<&PaneGroupState>,
             state: &PaneAxisState,
             e: &MouseMoveEvent,
             ix: usize,
@@ -1175,37 +1368,42 @@ pub mod element {
             window: &mut Window,
             cx: &mut App,
         ) {
+            let mut state = state.0.borrow_mut();
+            let mut group_state = pane_group_state.as_ref().map(|state| state.0.borrow_mut());
+            let group_state = group_state.as_deref_mut();
+            let mut entries = state.entries(group_state);
+
             let min_size = match axis {
                 Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
                 Axis::Vertical => px(VERTICAL_MIN_SIZE),
             };
-            let mut inner = state.0.borrow_mut();
-            let flexes = &mut inner.flexes;
-            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
+            debug_assert!(flex_values_in_bounds(&entries));
 
             // Math to convert a flex value to a pixel value
-            let size = move |ix, flexes: &[f32]| {
-                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
+            let size = move |ix: usize, state: &[&mut PaneAxisStateEntry]| {
+                container_size.along(axis) * (state[ix].flex / state.len() as f32)
             };
 
             // Don't allow resizing to less than the minimum size, if elements are already too small
-            if min_size - px(1.) > size(ix, flexes.as_slice()) {
+            if min_size - px(1.) > size(ix, &entries) {
                 return;
             }
 
             // This is basically a "bucket" of pixel changes that need to be applied in response to this
             // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels
             let mut proposed_current_pixel_change =
-                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
+                (e.position - child_start).along(axis) - size(ix, &entries);
 
             // This takes a pixel change, and computes the flex changes that correspond to this pixel change
             // as well as the next one, for some reason
-            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-                let flex_change = pixel_dx / container_size.along(axis);
-                let current_target_flex = flexes[target_ix] + flex_change;
-                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
-                (current_target_flex, next_target_flex)
-            };
+            let flex_changes =
+                |pixel_dx, target_ix: usize, next: isize, entries: &[&mut PaneAxisStateEntry]| {
+                    let flex_change = pixel_dx / container_size.along(axis);
+                    let current_target_flex = entries[target_ix].flex + flex_change;
+                    let next_target_flex =
+                        entries[(target_ix as isize + next) as usize].flex - flex_change;
+                    (current_target_flex, next_target_flex)
+                };
 
             // Generate the list of flex successors, from the current index.
             // If you're dragging column 3 forward, out of 6 columns, then this code will produce [4, 5, 6]
@@ -1213,7 +1411,7 @@ pub mod element {
             let mut successors = iter::from_fn({
                 let forward = proposed_current_pixel_change > px(0.);
                 let mut ix_offset = 0;
-                let len = flexes.len();
+                let len = entries.len();
                 move || {
                     let result = if forward {
                         (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
@@ -1234,24 +1432,22 @@ pub mod element {
                 };
 
                 let next_target_size = Pixels::max(
-                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
+                    size(current_ix + 1, &entries) - proposed_current_pixel_change,
                     min_size,
                 );
 
                 let current_target_size = Pixels::max(
-                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
-                        - next_target_size,
+                    size(current_ix, &entries) + size(current_ix + 1, &entries) - next_target_size,
                     min_size,
                 );
 
-                let current_pixel_change =
-                    current_target_size - size(current_ix, flexes.as_slice());
+                let current_pixel_change = current_target_size - size(current_ix, &entries);
 
                 let (current_target_flex, next_target_flex) =
-                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
+                    flex_changes(current_pixel_change, current_ix, 1, &entries);
 
-                flexes[current_ix] = current_target_flex;
-                flexes[current_ix + 1] = next_target_flex;
+                entries[current_ix].flex = current_target_flex;
+                entries[current_ix + 1].flex = next_target_flex;
 
                 proposed_current_pixel_change -= current_pixel_change;
             }
@@ -1344,24 +1540,28 @@ pub mod element {
                     (state.clone(), state)
                 },
             );
-            let flexes = self.state.flexes();
+            let mut state = self.state.0.borrow_mut();
+            let mut group_state = self.pane_group_state.as_ref().map(|s| s.0.borrow_mut());
+            let group_state = group_state.as_deref_mut();
+            let mut entries = state.entries(group_state);
+
             let len = self.children.len();
-            debug_assert!(flexes.len() == len);
-            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
+            debug_assert!(entries.len() == len);
+            debug_assert!(flex_values_in_bounds(&entries));
 
             let total_flex = len as f32;
 
             let mut origin = bounds.origin;
             let space_per_flex = bounds.size.along(self.axis) / total_flex;
 
-            self.state.0.borrow_mut().bounding_boxes.clear();
+            // self.state.0.borrow_mut().bounding_boxes.clear();
 
             let mut layout = PaneAxisLayout {
                 dragged_handle,
                 children: Vec::new(),
             };
             for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
-                let child_flex = flexes[ix];
+                let child_flex = entries[ix].flex;
 
                 let child_size = bounds
                     .size
@@ -1373,11 +1573,7 @@ pub mod element {
                     size: child_size,
                 };
 
-                self.state
-                    .0
-                    .borrow_mut()
-                    .bounding_boxes
-                    .push(Some(child_bounds));
+                entries[ix].bounding_box = Some(child_bounds);
                 child.layout_as_root(child_size.into(), window, cx);
                 child.prepaint_at(origin, window, cx);
 
@@ -1498,6 +1694,7 @@ pub mod element {
                     window.on_mouse_event({
                         let dragged_handle = layout.dragged_handle.clone();
                         let state = self.state.clone();
+                        let group_state = self.pane_group_state.clone();
                         let workspace = self.workspace.clone();
                         let handle_hitbox = handle.hitbox.clone();
                         move |e: &MouseDownEvent, phase, window, cx| {
@@ -1505,6 +1702,9 @@ pub mod element {
                                 dragged_handle.replace(Some(ix));
                                 if e.click_count >= 2 {
                                     state.reset_flexes();
+                                    if let Some(group_state) = group_state.as_ref() {
+                                        group_state.reset_flexes();
+                                    }
                                     workspace
                                         .update(cx, |this, cx| this.serialize_workspace(window, cx))
                                         .log_err();
@@ -1519,12 +1719,14 @@ pub mod element {
                         let workspace = self.workspace.clone();
                         let dragged_handle = layout.dragged_handle.clone();
                         let state = self.state.clone();
+                        let group_state = self.pane_group_state.clone();
                         let child_bounds = child.bounds;
                         let axis = self.axis;
                         move |e: &MouseMoveEvent, phase, window, cx| {
                             let dragged_handle = dragged_handle.borrow();
                             if phase.bubble() && *dragged_handle == Some(ix) {
                                 Self::compute_resize(
+                                    group_state.as_ref(),
                                     &state,
                                     e,
                                     ix,
@@ -1558,7 +1760,7 @@ pub mod element {
         }
     }
 
-    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
-        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
+    fn flex_values_in_bounds(inner: &[&mut PaneAxisStateEntry]) -> bool {
+        (inner.iter().map(|e| e.flex).sum::<f32>() - inner.len() as f32).abs() < 0.001
     }
 }

crates/workspace/src/workspace.rs 🔗

@@ -51,12 +51,12 @@ use futures::{
     future::{Shared, try_join_all},
 };
 use gpui::{
-    Action, AnyElement, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Axis,
-    Bounds, Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter,
-    FocusHandle, Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView,
-    MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful,
-    Subscription, SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle,
-    WindowId, WindowOptions, actions, canvas, point, relative, size, transparent_black,
+    Action, AnyElement, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds,
+    Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
+    Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
+    PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription,
+    SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId,
+    WindowOptions, actions, canvas, point, relative, size, transparent_black,
 };
 pub use history_manager::*;
 pub use item::{
@@ -1336,7 +1336,6 @@ pub struct Workspace {
     last_open_dock_positions: Vec<DockPosition>,
     removing: bool,
     _panels_task: Option<Task<Result<()>>>,
-    center_element_state: PaneAxisState,
 }
 
 impl EventEmitter<Event> for Workspace {}
@@ -1742,7 +1741,6 @@ impl Workspace {
             scheduled_tasks: Vec::new(),
             last_open_dock_positions: Vec::new(),
             removing: false,
-            center_element_state: PaneAxisState::new(0),
         }
     }
 
@@ -7003,27 +7001,33 @@ impl Workspace {
         window: &mut Window,
         cx: &mut App,
     ) -> AnyElement {
-        let left_visible_panel = if self.zoomed_position != Some(DockPosition::Left) {
-            self.left_dock.read(cx).visible_panel().cloned()
-        } else {
-            None
-        };
-        let left_center_element =
-            left_visible_panel.and_then(|panel| panel.center_element(window, cx));
-
-        let right_visible_panel = if self.zoomed_position != Some(DockPosition::Right) {
-            self.right_dock.read(cx).visible_panel().cloned()
-        } else {
-            None
-        };
-        let right_center_element =
-            right_visible_panel.and_then(|panel| panel.center_element(window, cx));
+        let left_panel_flex_content = (self.zoomed_position != Some(DockPosition::Left))
+            .then(|| {
+                self.left_dock
+                    .read(cx)
+                    .visible_panel()
+                    .cloned()
+                    .and_then(|panel| panel.flex_content(window, cx))
+            })
+            .flatten();
+        let right_panel_flex_content = (self.zoomed_position != Some(DockPosition::Right))
+            .then(|| {
+                self.right_dock
+                    .read(cx)
+                    .visible_panel()
+                    .cloned()
+                    .and_then(|panel| panel.flex_content(window, cx))
+            })
+            .flatten();
 
         let center_pane_group = h_flex()
+            .size_full()
             .flex_1()
             .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
             .child(self.center.render(
                 self.zoomed.as_ref(),
+                left_panel_flex_content,
+                right_panel_flex_content,
                 &PaneRenderContext {
                     follower_states: &self.follower_states,
                     active_call: self.active_call(),
@@ -7038,59 +7042,9 @@ impl Workspace {
             .when_some(paddings.1, |this, p| this.child(p.border_l_1()))
             .into_any_element();
 
-        if left_center_element.is_none() && right_center_element.is_none() {
-            return h_flex()
-                .flex_1()
-                .child(center_pane_group)
-                .into_any_element();
-        }
-
-        let member_count =
-            1 + left_center_element.is_some() as usize + right_center_element.is_some() as usize;
-
-        if self.center_element_state.len() != member_count {
-            self.center_element_state = PaneAxisState::new(member_count);
-        }
-
-        let axis_element = pane_axis(
-            Axis::Horizontal,
-            0,
-            self.center_element_state.clone(),
-            self.weak_self.clone(),
-        );
-
-        let axis_element = if let Some(left_element) = left_center_element {
-            axis_element.child(
-                div()
-                    .flex_1()
-                    .size_full()
-                    .overflow_hidden()
-                    .child(left_element),
-            )
-        } else {
-            axis_element
-        };
-
-        let axis_element = axis_element.child(center_pane_group);
-
-        let axis_element = if let Some(right_element) = right_center_element {
-            axis_element.child(
-                div()
-                    .flex_1()
-                    .size_full()
-                    .overflow_hidden()
-                    .child(right_element),
-            )
-        } else {
-            axis_element
-        };
-
-        div()
-            .flex()
-            .flex_row()
+        h_flex()
             .flex_1()
-            .size_full()
-            .child(axis_element)
+            .child(center_pane_group)
             .into_any_element()
     }