Add resising serialization

Mikayla Maki created

Change summary

crates/workspace/src/pane_group.rs        |  67 ++++++----
crates/workspace/src/persistence.rs       | 152 ++++++++++++++++--------
crates/workspace/src/persistence/model.rs |   9 
crates/workspace/src/workspace.rs         |  16 ++
4 files changed, 159 insertions(+), 85 deletions(-)

Detailed changes

crates/workspace/src/pane_group.rs 🔗

@@ -308,7 +308,7 @@ impl Member {
 pub(crate) struct PaneAxis {
     pub axis: Axis,
     pub members: Vec<Member>,
-    flexes: Rc<RefCell<Vec<f32>>>,
+    pub flexes: Rc<RefCell<Vec<f32>>>,
 }
 
 impl PaneAxis {
@@ -321,6 +321,18 @@ impl PaneAxis {
         }
     }
 
+    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
+        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
+        debug_assert!(members.len() == flexes.len());
+
+        let flexes = Rc::new(RefCell::new(flexes));
+        Self {
+            axis,
+            members,
+            flexes,
+        }
+    }
+
     fn split(
         &mut self,
         old_pane: &ViewHandle<Pane>,
@@ -519,23 +531,23 @@ mod element {
         json::{self, ToJson},
         platform::{CursorStyle, MouseButton},
         AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt,
-        SceneBuilder, SizeConstraint, Vector2FExt, View, ViewContext,
+        SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
     };
 
     use crate::{
         pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
-        WorkspaceSettings,
+        WorkspaceSettings, Workspace,
     };
 
-    pub struct PaneAxisElement<V: View> {
+    pub struct PaneAxisElement {
         axis: Axis,
         basis: usize,
         active_pane_ix: Option<usize>,
         flexes: Rc<RefCell<Vec<f32>>>,
-        children: Vec<AnyElement<V>>,
+        children: Vec<AnyElement<Workspace>>,
     }
 
-    impl<V: View> PaneAxisElement<V> {
+    impl  PaneAxisElement {
         pub fn new(axis: Axis, basis: usize, flexes: Rc<RefCell<Vec<f32>>>) -> Self {
             Self {
                 axis,
@@ -557,8 +569,8 @@ mod element {
             remaining_space: &mut f32,
             remaining_flex: &mut f32,
             cross_axis_max: &mut f32,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
+            view: &mut Workspace,
+            cx: &mut LayoutContext<Workspace>,
         ) {
             let flexes = self.flexes.borrow();
             let cross_axis = self.axis.invert();
@@ -602,21 +614,21 @@ mod element {
         }
     }
 
-    impl<V: View> Extend<AnyElement<V>> for PaneAxisElement<V> {
-        fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
+    impl Extend<AnyElement<Workspace>> for PaneAxisElement {
+        fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
             self.children.extend(children);
         }
     }
 
-    impl<V: View> Element<V> for PaneAxisElement<V> {
+    impl Element<Workspace> for PaneAxisElement {
         type LayoutState = f32;
         type PaintState = ();
 
         fn layout(
             &mut self,
             constraint: SizeConstraint,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
+            view: &mut Workspace,
+            cx: &mut LayoutContext<Workspace>,
         ) -> (Vector2F, Self::LayoutState) {
             debug_assert!(self.children.len() == self.flexes.borrow().len());
 
@@ -682,8 +694,8 @@ mod element {
             bounds: RectF,
             visible_bounds: RectF,
             remaining_space: &mut Self::LayoutState,
-            view: &mut V,
-            cx: &mut ViewContext<V>,
+            view: &mut Workspace,
+            cx: &mut ViewContext<Workspace>,
         ) -> Self::PaintState {
             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -750,7 +762,7 @@ mod element {
                         handle_bounds,
                     );
                     mouse_region =
-                        mouse_region.on_drag(MouseButton::Left, move |drag, _: &mut V, cx| {
+                        mouse_region.on_drag(MouseButton::Left, move |drag, workspace: &mut Workspace, cx| {
                             let min_size = match axis {
                                 Axis::Horizontal => HORIZONTAL_MIN_SIZE,
                                 Axis::Vertical => VERTICAL_MIN_SIZE,
@@ -768,13 +780,15 @@ mod element {
                                 current_target_size - child_size.along(axis);
 
                             if proposed_current_pixel_change < 0. {
-                                current_target_size = current_target_size.max(min_size);
+                                current_target_size = f32::max(current_target_size, min_size);
                             } else if proposed_current_pixel_change > 0. {
-                                // TODO: cascade this size change down, collect all changes into a vec
-                                let next_target_size = (next_child_size.along(axis)
-                                    - proposed_current_pixel_change)
-                                    .max(min_size);
-                                current_target_size = current_target_size.min(
+                                // TODO: cascade this change to other children if current item is at min size
+                                let next_target_size = f32::max(
+                                    next_child_size.along(axis) - proposed_current_pixel_change,
+                                    min_size,
+                                );
+                                current_target_size = f32::min(
+                                    current_target_size,
                                     child_size.along(axis) + next_child_size.along(axis)
                                         - next_target_size,
                                 );
@@ -789,6 +803,7 @@ mod element {
                             *borrow.get_mut(ix).unwrap() = current_target_flex;
                             *borrow.get_mut(next_ix).unwrap() = next_target_flex;
 
+                            workspace.schedule_serialize(cx);
                             cx.notify();
                         });
                     scene.push_mouse_region(mouse_region);
@@ -809,8 +824,8 @@ mod element {
             _: RectF,
             _: &Self::LayoutState,
             _: &Self::PaintState,
-            view: &V,
-            cx: &ViewContext<V>,
+            view: &Workspace,
+            cx: &ViewContext<Workspace>,
         ) -> Option<RectF> {
             self.children
                 .iter()
@@ -822,8 +837,8 @@ mod element {
             bounds: RectF,
             _: &Self::LayoutState,
             _: &Self::PaintState,
-            view: &V,
-            cx: &ViewContext<V>,
+            view: &Workspace,
+            cx: &ViewContext<Workspace>,
         ) -> json::Value {
             serde_json::json!({
                 "type": "PaneAxis",

crates/workspace/src/persistence.rs 🔗

@@ -45,6 +45,7 @@ define_connection! {
     //   parent_group_id: Option<usize>, // None indicates that this is the root node
     //   position: Optiopn<usize>, // None indicates that this is the root node
     //   axis: Option<Axis>, // 'Vertical', 'Horizontal'
+    //   flexes: Option<Vec<f32>>, // A JSON array of floats
     // )
     //
     // panes(
@@ -168,7 +169,12 @@ define_connection! {
         ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
         ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
         ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
-    )];
+    ),
+    // Add pane group flex data
+    sql!(
+        ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
+    )
+    ];
 }
 
 impl WorkspaceDb {
@@ -359,38 +365,51 @@ impl WorkspaceDb {
         group_id: Option<GroupId>,
     ) -> Result<Vec<SerializedPaneGroup>> {
         type GroupKey = (Option<GroupId>, WorkspaceId);
-        type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
+        type GroupOrPane = (
+            Option<GroupId>,
+            Option<Axis>,
+            Option<PaneId>,
+            Option<bool>,
+            Option<String>,
+        );
         self.select_bound::<GroupKey, GroupOrPane>(sql!(
-            SELECT group_id, axis, pane_id, active
+            SELECT group_id, axis, pane_id, active, flexes
                 FROM (SELECT
-                    group_id,
-                    axis,
-                    NULL as pane_id,
-                    NULL as active,
-                    position,
-                    parent_group_id,
-                    workspace_id
-                    FROM pane_groups
+                        group_id,
+                        axis,
+                        NULL as pane_id,
+                        NULL as active,
+                        position,
+                        parent_group_id,
+                        workspace_id,
+                        flexes
+                      FROM pane_groups
                     UNION
-                    SELECT
-                    NULL,
-                    NULL,
-                    center_panes.pane_id,
-                    panes.active as active,
-                    position,
-                    parent_group_id,
-                    panes.workspace_id as workspace_id
-                        FROM center_panes
-                        JOIN panes ON center_panes.pane_id = panes.pane_id)
+                      SELECT
+                        NULL,
+                        NULL,
+                        center_panes.pane_id,
+                        panes.active as active,
+                        position,
+                        parent_group_id,
+                        panes.workspace_id as workspace_id,
+                        NULL
+                      FROM center_panes
+                      JOIN panes ON center_panes.pane_id = panes.pane_id)
                 WHERE parent_group_id IS ? AND workspace_id = ?
                 ORDER BY position
         ))?((group_id, workspace_id))?
         .into_iter()
-        .map(|(group_id, axis, pane_id, active)| {
+        .map(|(group_id, axis, pane_id, active, flexes)| {
             if let Some((group_id, axis)) = group_id.zip(axis) {
+                let flexes = flexes
+                    .map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
+                    .transpose()?;
+
                 Ok(SerializedPaneGroup::Group {
                     axis,
                     children: self.get_pane_group(workspace_id, Some(group_id))?,
+                    flexes,
                 })
             } else if let Some((pane_id, active)) = pane_id.zip(active) {
                 Ok(SerializedPaneGroup::Pane(SerializedPane::new(
@@ -417,14 +436,31 @@ impl WorkspaceDb {
         parent: Option<(GroupId, usize)>,
     ) -> Result<()> {
         match pane_group {
-            SerializedPaneGroup::Group { axis, children } => {
+            SerializedPaneGroup::Group {
+                axis,
+                children,
+                flexes,
+            } => {
                 let (parent_id, position) = unzip_option(parent);
 
+                let flex_string = serde_json::json!(flexes).to_string();
                 let group_id = conn.select_row_bound::<_, i64>(sql!(
-                    INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis)
-                    VALUES (?, ?, ?, ?)
+                    INSERT INTO pane_groups(
+                        workspace_id,
+                        parent_group_id,
+                        position,
+                        axis,
+                        flexes
+                    )
+                    VALUES (?, ?, ?, ?, ?)
                     RETURNING group_id
-                ))?((workspace_id, parent_id, position, *axis))?
+                ))?((
+                    workspace_id,
+                    parent_id,
+                    position,
+                    *axis,
+                    flex_string,
+                ))?
                 .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
 
                 for (position, group) in children.iter().enumerate() {
@@ -641,6 +677,14 @@ mod tests {
         assert_eq!(test_text_1, "test-text-1");
     }
 
+    fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
+        SerializedPaneGroup::Group {
+            axis,
+            flexes: None,
+            children,
+        }
+    }
+
     #[gpui::test]
     async fn test_full_workspace_serialization() {
         env_logger::try_init().ok();
@@ -652,12 +696,12 @@ mod tests {
         //  | - - - |       |
         //  | 3,4   |       |
         //  -----------------
-        let center_group = SerializedPaneGroup::Group {
-            axis: gpui::Axis::Horizontal,
-            children: vec![
-                SerializedPaneGroup::Group {
-                    axis: gpui::Axis::Vertical,
-                    children: vec![
+        let center_group = group(
+            gpui::Axis::Horizontal,
+            vec![
+                group(
+                    gpui::Axis::Vertical,
+                    vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
                                 SerializedItem::new("Terminal", 5, false),
@@ -673,7 +717,7 @@ mod tests {
                             false,
                         )),
                     ],
-                },
+                ),
                 SerializedPaneGroup::Pane(SerializedPane::new(
                     vec![
                         SerializedItem::new("Terminal", 9, false),
@@ -682,7 +726,7 @@ mod tests {
                     false,
                 )),
             ],
-        };
+        );
 
         let workspace = SerializedWorkspace {
             id: 5,
@@ -811,12 +855,12 @@ mod tests {
         //  | - - - |       |
         //  | 3,4   |       |
         //  -----------------
-        let center_pane = SerializedPaneGroup::Group {
-            axis: gpui::Axis::Horizontal,
-            children: vec![
-                SerializedPaneGroup::Group {
-                    axis: gpui::Axis::Vertical,
-                    children: vec![
+        let center_pane = group(
+            gpui::Axis::Horizontal,
+            vec![
+                group(
+                    gpui::Axis::Vertical,
+                    vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
                                 SerializedItem::new("Terminal", 1, false),
@@ -832,7 +876,7 @@ mod tests {
                             true,
                         )),
                     ],
-                },
+                ),
                 SerializedPaneGroup::Pane(SerializedPane::new(
                     vec![
                         SerializedItem::new("Terminal", 5, true),
@@ -841,7 +885,7 @@ mod tests {
                     false,
                 )),
             ],
-        };
+        );
 
         let workspace = default_workspace(&["/tmp"], &center_pane);
 
@@ -858,12 +902,12 @@ mod tests {
 
         let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
 
-        let center_pane = SerializedPaneGroup::Group {
-            axis: gpui::Axis::Horizontal,
-            children: vec![
-                SerializedPaneGroup::Group {
-                    axis: gpui::Axis::Vertical,
-                    children: vec![
+        let center_pane = group(
+            gpui::Axis::Horizontal,
+            vec![
+                group(
+                    gpui::Axis::Vertical,
+                    vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
                                 SerializedItem::new("Terminal", 1, false),
@@ -879,7 +923,7 @@ mod tests {
                             true,
                         )),
                     ],
-                },
+                ),
                 SerializedPaneGroup::Pane(SerializedPane::new(
                     vec![
                         SerializedItem::new("Terminal", 5, false),
@@ -888,7 +932,7 @@ mod tests {
                     false,
                 )),
             ],
-        };
+        );
 
         let id = &["/tmp"];
 
@@ -896,9 +940,9 @@ mod tests {
 
         db.save_workspace(workspace.clone()).await;
 
-        workspace.center_group = SerializedPaneGroup::Group {
-            axis: gpui::Axis::Vertical,
-            children: vec![
+        workspace.center_group = group(
+            gpui::Axis::Vertical,
+            vec![
                 SerializedPaneGroup::Pane(SerializedPane::new(
                     vec![
                         SerializedItem::new("Terminal", 1, false),
@@ -914,7 +958,7 @@ mod tests {
                     true,
                 )),
             ],
-        };
+        );
 
         db.save_workspace(workspace.clone()).await;
 

crates/workspace/src/persistence/model.rs 🔗

@@ -127,10 +127,11 @@ impl Bind for DockData {
     }
 }
 
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, PartialEq, Clone)]
 pub enum SerializedPaneGroup {
     Group {
         axis: Axis,
+        flexes: Option<Vec<f32>>,
         children: Vec<SerializedPaneGroup>,
     },
     Pane(SerializedPane),
@@ -149,7 +150,7 @@ impl Default for SerializedPaneGroup {
 impl SerializedPaneGroup {
     #[async_recursion(?Send)]
     pub(crate) async fn deserialize(
-        &self,
+        self,
         project: &ModelHandle<Project>,
         workspace_id: WorkspaceId,
         workspace: &WeakViewHandle<Workspace>,
@@ -160,7 +161,7 @@ impl SerializedPaneGroup {
         Vec<Option<Box<dyn ItemHandle>>>,
     )> {
         match self {
-            SerializedPaneGroup::Group { axis, children } => {
+            SerializedPaneGroup::Group { axis, children, flexes } => {
                 let mut current_active_pane = None;
                 let mut members = Vec::new();
                 let mut items = Vec::new();
@@ -184,7 +185,7 @@ impl SerializedPaneGroup {
                 }
 
                 Some((
-                    Member::Axis(PaneAxis::new(*axis, members)),
+                    Member::Axis(PaneAxis::load(axis, members, flexes)),
                     current_active_pane,
                     items,
                 ))

crates/workspace/src/workspace.rs 🔗

@@ -504,6 +504,7 @@ pub struct Workspace {
     subscriptions: Vec<Subscription>,
     _apply_leader_updates: Task<Result<()>>,
     _observe_current_user: Task<Result<()>>,
+    _schedule_serialize: Option<Task<()>>,
     pane_history_timestamp: Arc<AtomicUsize>,
 }
 
@@ -718,6 +719,7 @@ impl Workspace {
             app_state,
             _observe_current_user,
             _apply_leader_updates,
+            _schedule_serialize: None,
             leader_updates_tx,
             subscriptions,
             pane_history_timestamp,
@@ -2893,6 +2895,13 @@ impl Workspace {
         cx.notify();
     }
 
+    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
+        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
+            cx.background().timer(Duration::from_millis(100)).await;
+            this.read_with(&cx, |this, cx| this.serialize_workspace(cx)).ok();
+        }));
+    }
+
     fn serialize_workspace(&self, cx: &ViewContext<Self>) {
         fn serialize_pane_handle(
             pane_handle: &ViewHandle<Pane>,
@@ -2923,12 +2932,17 @@ impl Workspace {
             cx: &AppContext,
         ) -> SerializedPaneGroup {
             match pane_group {
-                Member::Axis(PaneAxis { axis, members, .. }) => SerializedPaneGroup::Group {
+                Member::Axis(PaneAxis {
+                    axis,
+                    members,
+                    flexes,
+                }) => SerializedPaneGroup::Group {
                     axis: *axis,
                     children: members
                         .iter()
                         .map(|member| build_serialized_pane_group(member, cx))
                         .collect::<Vec<_>>(),
+                    flexes: Some(flexes.borrow().clone()),
                 },
                 Member::Pane(pane_handle) => {
                     SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))