Added the new elements and wired through the pointers to update the pane axis ratios

Mikayla Maki created

Change summary

crates/workspace/src/adjustable_flex.rs   |  83 -----
crates/workspace/src/pane_group.rs        | 399 +++++++++++++++++++++++-
crates/workspace/src/persistence/model.rs |   1 
crates/workspace/src/workspace.rs         |   3 
4 files changed, 376 insertions(+), 110 deletions(-)

Detailed changes

crates/workspace/src/adjustable_flex.rs 🔗

@@ -1,83 +0,0 @@
-use gpui::{Element, View, Axis, AnyElement};
-
-// Model for the center group: AdjustableGroup of AdjustableGroups
-// Implementation notes
-// - These have two representations: Exact pixel widths and ratios of elements compared to whole space
-// - We have a constraint of minimum sizes for things.
-//   - If The space is smaller than allowed, things run off the edge
-// - When doing Drag resize, we update the pixel width representation, causing a recalc of the ratios
-//   - If dragging past minimum, take space from next item, until out of space
-// - When doing a reflow (e.g. layout) we read off the ratios and calculate pixels from that
-// - When adding / removing items in an Adjustable flex, reset to default ratios (1:1)
-// - By default, every item takes up as much space as possible
-//
-
-
-struct AdjustableFlex<V: View> {
-    axis: Axis,
-    handle_size: f32,
-    items: Vec<(AnyElement<V>, f32)>
-}
-
-impl<V: View> AdjustableFlex<V> {
-    fn new(axis: Axis) -> Self {
-        AdjustableFlex {
-            axis,
-            handle_size: 2.,
-            items: Vec::new(),
-        }
-    }
-
-    fn add_item()
-}
-
-impl<V: View> Element<V> for AdjustableFlex<V> {
-    type LayoutState = ();
-
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: gpui::SizeConstraint,
-        view: &mut V,
-        cx: &mut gpui::LayoutContext<V>,
-    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-        todo!()
-    }
-
-    fn paint(
-        &mut self,
-        scene: &mut gpui::SceneBuilder,
-        bounds: gpui::geometry::rect::RectF,
-        visible_bounds: gpui::geometry::rect::RectF,
-        layout: &mut Self::LayoutState,
-        view: &mut V,
-        cx: &mut gpui::ViewContext<V>,
-    ) -> Self::PaintState {
-        todo!()
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: std::ops::Range<usize>,
-        bounds: gpui::geometry::rect::RectF,
-        visible_bounds: gpui::geometry::rect::RectF,
-        layout: &Self::LayoutState,
-        paint: &Self::PaintState,
-        view: &V,
-        cx: &gpui::ViewContext<V>,
-    ) -> Option<gpui::geometry::rect::RectF> {
-        todo!()
-    }
-
-    fn debug(
-        &self,
-        bounds: gpui::geometry::rect::RectF,
-        layout: &Self::LayoutState,
-        paint: &Self::PaintState,
-        view: &V,
-        cx: &gpui::ViewContext<V>,
-    ) -> serde_json::Value {
-        todo!()
-    }
-}

crates/workspace/src/pane_group.rs 🔗

@@ -1,6 +1,6 @@
-use std::sync::Arc;
+use std::{cell::RefCell, rc::Rc, sync::Arc};
 
-use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings};
+use crate::{AppState, FollowerStatesByLeader, Pane, Workspace};
 use anyhow::{anyhow, Result};
 use call::{ActiveCall, ParticipantLocation};
 use gpui::{
@@ -9,12 +9,13 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle,
 };
-use itertools::Itertools;
 use project::Project;
 use serde::Deserialize;
 use theme::Theme;
 
-#[derive(Clone, Debug, Eq, PartialEq)]
+use self::adjustable_group::{AdjustableGroupElement, AdjustableGroupItem};
+
+#[derive(Clone, Debug, PartialEq)]
 pub struct PaneGroup {
     pub(crate) root: Member,
 }
@@ -78,6 +79,7 @@ impl PaneGroup {
     ) -> AnyElement<Workspace> {
         self.root.render(
             project,
+            0,
             theme,
             follower_states,
             active_call,
@@ -95,7 +97,7 @@ impl PaneGroup {
     }
 }
 
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub(crate) enum Member {
     Axis(PaneAxis),
     Pane(ViewHandle<Pane>),
@@ -120,7 +122,11 @@ impl Member {
             Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
         };
 
-        Member::Axis(PaneAxis { axis, members })
+        Member::Axis(PaneAxis {
+            axis,
+            members,
+            ratios: Default::default(),
+        })
     }
 
     fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
@@ -133,6 +139,7 @@ impl Member {
     pub fn render(
         &self,
         project: &ModelHandle<Project>,
+        basis: usize,
         theme: &Theme,
         follower_states: &FollowerStatesByLeader,
         active_call: Option<&ModelHandle<ActiveCall>>,
@@ -273,6 +280,7 @@ impl Member {
             }
             Member::Axis(axis) => axis.render(
                 project,
+                basis + 1,
                 theme,
                 follower_states,
                 active_call,
@@ -296,10 +304,11 @@ impl Member {
     }
 }
 
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub(crate) struct PaneAxis {
     pub axis: Axis,
     pub members: Vec<Member>,
+    pub ratios: Rc<RefCell<Vec<f32>>>,
 }
 
 impl PaneAxis {
@@ -378,6 +387,7 @@ impl PaneAxis {
     fn render(
         &self,
         project: &ModelHandle<Project>,
+        basis: usize,
         theme: &Theme,
         follower_state: &FollowerStatesByLeader,
         active_call: Option<&ModelHandle<ActiveCall>>,
@@ -386,19 +396,29 @@ impl PaneAxis {
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
     ) -> AnyElement<Workspace> {
-        let mut flex_container = Flex::new(self.axis);
-
+        let ratios = self.ratios.clone();
+        let mut flex_container = AdjustableGroupElement::new(self.axis, 2., basis, move |new_flexes| {
+            let mut borrow = ratios.borrow_mut();
+            borrow.extend(new_flexes);
+            borrow.truncate(10);
+            dbg!(borrow);
+        });
+
+        let next_basis = basis + self.members.len();
         let mut members = self.members.iter().enumerate().peekable();
-        while let Some((ix, member)) = members.next() {
+        while let Some((_ix, member)) = members.next() {
             let last = members.peek().is_none();
 
             let mut flex = 1.0;
-            if member.contains(active_pane) {
-                flex = settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
-            }
+            // TODO: Include minimum sizes
+            // TODO: Restore this
+            // if member.contains(active_pane) {
+            // flex = settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
+            // }
 
             let mut member = member.render(
                 project,
+                next_basis,
                 theme,
                 follower_state,
                 active_call,
@@ -424,20 +444,11 @@ impl PaneAxis {
                     Axis::Vertical => HandleSide::Bottom,
                 };
 
-                member = member.contained().with_border(border)
-                    .resizable(side, 1., |workspace, size, cx| {
-                        dbg!("resize", size);
-                    })
-                    .into_any();
-
-
+                member = member.contained().with_border(border).into_any();
             }
 
-            flex_container = flex_container.with_child(
-                FlexItem::new(member)
-                    .flex(flex, true)
-                    .into_any()
-            );
+            flex_container =
+                flex_container.with_child(AdjustableGroupItem::new(member, flex).into_any());
         }
 
         flex_container.into_any()
@@ -496,3 +507,341 @@ impl SplitDirection {
         }
     }
 }
+
+mod adjustable_group {
+
+    use std::{any::Any, ops::Range, rc::Rc};
+
+    use gpui::{
+        color::Color,
+        geometry::{
+            rect::RectF,
+            vector::{vec2f, Vector2F},
+        },
+        json::{self, ToJson},
+        platform::{CursorStyle, MouseButton},
+        AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, Quad, SceneBuilder,
+        SizeConstraint, Vector2FExt, View, ViewContext,
+    };
+    use serde_json::Value;
+
+    struct AdjustableFlexData {
+        flex: f32,
+    }
+
+    pub struct AdjustableGroupElement<V: View> {
+        axis: Axis,
+        handle_size: f32,
+        basis: usize,
+        callback: Rc<dyn Fn(Vec<f32>)>,
+        children: Vec<AnyElement<V>>,
+    }
+
+    impl<V: View> AdjustableGroupElement<V> {
+        pub fn new(
+            axis: Axis,
+            handle_size: f32,
+            basis: usize,
+            callback: impl Fn(Vec<f32>) + 'static,
+        ) -> Self {
+            Self {
+                axis,
+                handle_size,
+                basis,
+                callback: Rc::new(callback),
+                children: Default::default(),
+            }
+        }
+
+        fn layout_flex_children(
+            &mut self,
+            constraint: SizeConstraint,
+            remaining_space: &mut f32,
+            remaining_flex: &mut f32,
+            cross_axis_max: &mut f32,
+            view: &mut V,
+            cx: &mut LayoutContext<V>,
+        ) {
+            let cross_axis = self.axis.invert();
+            let last_ix = self.children.len() - 1;
+            for (ix, child) in self.children.iter_mut().enumerate() {
+                let flex = child.metadata::<AdjustableFlexData>().unwrap().flex;
+
+                let handle_size = if ix == last_ix { 0. } else { self.handle_size };
+
+                let child_size = if *remaining_flex == 0.0 {
+                    *remaining_space
+                } else {
+                    let space_per_flex = *remaining_space / *remaining_flex;
+                    space_per_flex * flex
+                } - handle_size;
+
+                let child_constraint = match self.axis {
+                    Axis::Horizontal => SizeConstraint::new(
+                        vec2f(child_size, constraint.min.y()),
+                        vec2f(child_size, constraint.max.y()),
+                    ),
+                    Axis::Vertical => SizeConstraint::new(
+                        vec2f(constraint.min.x(), child_size),
+                        vec2f(constraint.max.x(), child_size),
+                    ),
+                };
+                let child_size = child.layout(child_constraint, view, cx);
+                *remaining_space -= child_size.along(self.axis) + handle_size;
+                *remaining_flex -= flex;
+                *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
+            }
+        }
+    }
+
+    impl<V: View> Extend<AnyElement<V>> for AdjustableGroupElement<V> {
+        fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
+            self.children.extend(children);
+        }
+    }
+
+    impl<V: View> Element<V> for AdjustableGroupElement<V> {
+        type LayoutState = f32;
+        type PaintState = ();
+
+        fn layout(
+            &mut self,
+            constraint: SizeConstraint,
+            view: &mut V,
+            cx: &mut LayoutContext<V>,
+        ) -> (Vector2F, Self::LayoutState) {
+            let mut remaining_flex = 0.;
+
+            let mut cross_axis_max: f32 = 0.0;
+            for child in &mut self.children {
+                let metadata = child.metadata::<AdjustableFlexData>();
+                let flex = metadata
+                    .map(|metadata| metadata.flex)
+                    .expect("All children of an adjustable flex must be AdjustableFlexItems");
+                remaining_flex += flex;
+            }
+
+            let mut remaining_space = constraint.max_along(self.axis);
+
+            if remaining_space.is_infinite() {
+                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
+            }
+
+            self.layout_flex_children(
+                constraint,
+                &mut remaining_space,
+                &mut remaining_flex,
+                &mut cross_axis_max,
+                view,
+                cx,
+            );
+
+            let mut size = match self.axis {
+                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
+                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
+            };
+
+            if constraint.min.x().is_finite() {
+                size.set_x(size.x().max(constraint.min.x()));
+            }
+            if constraint.min.y().is_finite() {
+                size.set_y(size.y().max(constraint.min.y()));
+            }
+
+            if size.x() > constraint.max.x() {
+                size.set_x(constraint.max.x());
+            }
+            if size.y() > constraint.max.y() {
+                size.set_y(constraint.max.y());
+            }
+
+            (size, remaining_space)
+        }
+
+        fn paint(
+            &mut self,
+            scene: &mut SceneBuilder,
+            bounds: RectF,
+            visible_bounds: RectF,
+            remaining_space: &mut Self::LayoutState,
+            view: &mut V,
+            cx: &mut ViewContext<V>,
+        ) -> Self::PaintState {
+            let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+            let overflowing = *remaining_space < 0.;
+            if overflowing {
+                scene.push_layer(Some(visible_bounds));
+            }
+
+            let mut child_origin = bounds.origin();
+
+            let last_ix = self.children.len() - 1;
+            for (ix, child) in self.children.iter_mut().enumerate() {
+                child.paint(scene, child_origin, visible_bounds, view, cx);
+
+                match self.axis {
+                    Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
+                    Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
+                }
+
+                if ix != last_ix {
+                    let bounds = match self.axis {
+                        Axis::Horizontal => RectF::new(
+                            child_origin,
+                            vec2f(self.handle_size, visible_bounds.height()),
+                        ),
+                        Axis::Vertical => RectF::new(
+                            child_origin,
+                            vec2f(visible_bounds.width(), self.handle_size),
+                        ),
+                    };
+
+                    scene.push_quad(Quad {
+                        bounds,
+                        background: Some(Color::red()),
+                        ..Default::default()
+                    });
+
+                    let style = match self.axis {
+                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
+                        Axis::Vertical => CursorStyle::ResizeUpDown,
+                    };
+
+                    scene.push_cursor_region(CursorRegion { bounds, style });
+
+                    enum ResizeHandle {}
+                    let callback = self.callback.clone();
+                    let axis = self.axis;
+                    let mut mouse_region =
+                        MouseRegion::new::<ResizeHandle>(cx.view_id(), self.basis + ix, bounds);
+                    mouse_region =
+                        mouse_region.on_drag(MouseButton::Left, move |drag, v: &mut V, cx| {
+                            dbg!(drag);
+                            callback({
+                                match axis {
+                                    Axis::Horizontal => vec![0., 1., 2.],
+                                    Axis::Vertical => vec![3., 2., 1.],
+                                }
+                            })
+                        });
+                    scene.push_mouse_region(mouse_region);
+
+                    match self.axis {
+                        Axis::Horizontal => child_origin += vec2f(self.handle_size, 0.0),
+                        Axis::Vertical => child_origin += vec2f(0.0, self.handle_size),
+                    }
+                }
+            }
+
+            if overflowing {
+                scene.pop_layer();
+            }
+        }
+
+        fn rect_for_text_range(
+            &self,
+            range_utf16: Range<usize>,
+            _: RectF,
+            _: RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            view: &V,
+            cx: &ViewContext<V>,
+        ) -> Option<RectF> {
+            self.children
+                .iter()
+                .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
+        }
+
+        fn debug(
+            &self,
+            bounds: RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            view: &V,
+            cx: &ViewContext<V>,
+        ) -> json::Value {
+            serde_json::json!({
+                "type": "Flex",
+                "bounds": bounds.to_json(),
+                "axis": self.axis.to_json(),
+                "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
+            })
+        }
+    }
+
+    pub struct AdjustableGroupItem<V: View> {
+        metadata: AdjustableFlexData,
+        child: AnyElement<V>,
+    }
+
+    impl<V: View> AdjustableGroupItem<V> {
+        pub fn new(child: impl Element<V>, flex: f32) -> Self {
+            Self {
+                metadata: AdjustableFlexData { flex },
+                child: child.into_any(),
+            }
+        }
+    }
+
+    impl<V: View> Element<V> for AdjustableGroupItem<V> {
+        type LayoutState = ();
+        type PaintState = ();
+
+        fn layout(
+            &mut self,
+            constraint: SizeConstraint,
+            view: &mut V,
+            cx: &mut LayoutContext<V>,
+        ) -> (Vector2F, Self::LayoutState) {
+            let size = self.child.layout(constraint, view, cx);
+            (size, ())
+        }
+
+        fn paint(
+            &mut self,
+            scene: &mut SceneBuilder,
+            bounds: RectF,
+            visible_bounds: RectF,
+            _: &mut Self::LayoutState,
+            view: &mut V,
+            cx: &mut ViewContext<V>,
+        ) -> Self::PaintState {
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx)
+        }
+
+        fn rect_for_text_range(
+            &self,
+            range_utf16: Range<usize>,
+            _: RectF,
+            _: RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            view: &V,
+            cx: &ViewContext<V>,
+        ) -> Option<RectF> {
+            self.child.rect_for_text_range(range_utf16, view, cx)
+        }
+
+        fn metadata(&self) -> Option<&dyn Any> {
+            Some(&self.metadata)
+        }
+
+        fn debug(
+            &self,
+            _: RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            view: &V,
+            cx: &ViewContext<V>,
+        ) -> Value {
+            serde_json::json!({
+                "type": "Flexible",
+                "flex": self.metadata.flex,
+                "child": self.child.debug(view, cx)
+            })
+        }
+    }
+}

crates/workspace/src/workspace.rs 🔗

@@ -4,7 +4,6 @@ pub mod notifications;
 pub mod pane;
 pub mod pane_group;
 mod persistence;
-mod adjustable_flex;
 pub mod searchable;
 pub mod shared_screen;
 mod status_bar;
@@ -2924,7 +2923,7 @@ impl Workspace {
             cx: &AppContext,
         ) -> SerializedPaneGroup {
             match pane_group {
-                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
+                Member::Axis(PaneAxis { axis, members, .. }) => SerializedPaneGroup::Group {
                     axis: *axis,
                     children: members
                         .iter()