Begin porting the PaneAxis element

Mikayla created

Change summary

crates/gpui2/src/executor.rs               |   6 
crates/gpui2/src/geometry.rs               |  87 ++
crates/gpui2/src/taffy.rs                  |   9 
crates/util/src/util.rs                    |  31 
crates/workspace2/src/dock.rs              |   4 
crates/workspace2/src/pane_group.rs        | 930 ++++++++++++-----------
crates/workspace2/src/persistence.rs       |   6 
crates/workspace2/src/persistence/model.rs |   6 
8 files changed, 618 insertions(+), 461 deletions(-)

Detailed changes

crates/gpui2/src/executor.rs 🔗

@@ -57,8 +57,12 @@ where
     T: 'static,
     E: 'static + Debug,
 {
+    #[track_caller]
     pub fn detach_and_log_err(self, cx: &mut AppContext) {
-        cx.foreground_executor().spawn(self.log_err()).detach();
+        let location = core::panic::Location::caller();
+        cx.foreground_executor()
+            .spawn(self.log_tracked_err(*location))
+            .detach();
     }
 }
 

crates/gpui2/src/geometry.rs 🔗

@@ -8,6 +8,54 @@ use std::{
     ops::{Add, Div, Mul, MulAssign, Sub},
 };
 
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Axis {
+    Vertical,
+    Horizontal,
+}
+
+impl Axis {
+    pub fn invert(&self) -> Self {
+        match self {
+            Axis::Vertical => Axis::Horizontal,
+            Axis::Horizontal => Axis::Vertical,
+        }
+    }
+}
+
+impl sqlez::bindable::StaticColumnCount for Axis {}
+impl sqlez::bindable::Bind for Axis {
+    fn bind(
+        &self,
+        statement: &sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<i32> {
+        match self {
+            Axis::Horizontal => "Horizontal",
+            Axis::Vertical => "Vertical",
+        }
+        .bind(statement, start_index)
+    }
+}
+
+impl sqlez::bindable::Column for Axis {
+    fn column(
+        statement: &mut sqlez::statement::Statement,
+        start_index: i32,
+    ) -> anyhow::Result<(Self, i32)> {
+        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+            Ok((
+                match axis_text.as_str() {
+                    "Horizontal" => Axis::Horizontal,
+                    "Vertical" => Axis::Vertical,
+                    _ => anyhow::bail!("Stored serialized item kind is incorrect"),
+                },
+                next_index,
+            ))
+        })
+    }
+}
+
 /// Describes a location in a 2D cartesian coordinate space.
 ///
 /// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
@@ -94,6 +142,19 @@ impl<T: Clone + Debug + Default> Point<T> {
             y: f(self.y.clone()),
         }
     }
+
+    pub fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point<T> {
+        match axis {
+            Axis::Horizontal => Point {
+                x: f(self.x.clone()),
+                y: self.y.clone(),
+            },
+            Axis::Vertical => Point {
+                x: self.x.clone(),
+                y: f(self.y.clone()),
+            },
+        }
+    }
 }
 
 impl Point<Pixels> {
@@ -373,6 +434,32 @@ impl Size<Pixels> {
     }
 }
 
+impl<T> Size<T>
+where
+    T: Clone + Default + Debug,
+{
+    pub fn along(&self, axis: Axis) -> T {
+        match axis {
+            Axis::Horizontal => self.width.clone(),
+            Axis::Vertical => self.height.clone(),
+        }
+    }
+
+    /// Returns the value of this size along the given axis.
+    pub fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self {
+        match axis {
+            Axis::Horizontal => Size {
+                width: f(self.width.clone()),
+                height: self.height.clone(),
+            },
+            Axis::Vertical => Size {
+                width: self.width.clone(),
+                height: f(self.height.clone()),
+            },
+        }
+    }
+}
+
 impl<T> Size<T>
 where
     T: PartialOrd + Clone + Default + Debug,

crates/gpui2/src/taffy.rs 🔗

@@ -477,3 +477,12 @@ impl From<Pixels> for AvailableSpace {
         AvailableSpace::Definite(pixels)
     }
 }
+
+impl From<Size<Pixels>> for Size<AvailableSpace> {
+    fn from(size: Size<Pixels>) -> Self {
+        Size {
+            width: AvailableSpace::Definite(size.width),
+            height: AvailableSpace::Definite(size.height),
+        }
+    }
+}

crates/util/src/util.rs 🔗

@@ -184,6 +184,11 @@ pub trait TryFutureExt {
     fn log_err(self) -> LogErrorFuture<Self>
     where
         Self: Sized;
+
+    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
+    where
+        Self: Sized;
+
     fn warn_on_err(self) -> LogErrorFuture<Self>
     where
         Self: Sized;
@@ -197,18 +202,29 @@ where
     F: Future<Output = Result<T, E>>,
     E: std::fmt::Debug,
 {
+    #[track_caller]
     fn log_err(self) -> LogErrorFuture<Self>
     where
         Self: Sized,
     {
-        LogErrorFuture(self, log::Level::Error)
+        let location = Location::caller();
+        LogErrorFuture(self, log::Level::Error, *location)
     }
 
+    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
+    where
+        Self: Sized,
+    {
+        LogErrorFuture(self, log::Level::Error, location)
+    }
+
+    #[track_caller]
     fn warn_on_err(self) -> LogErrorFuture<Self>
     where
         Self: Sized,
     {
-        LogErrorFuture(self, log::Level::Warn)
+        let location = Location::caller();
+        LogErrorFuture(self, log::Level::Warn, *location)
     }
 
     fn unwrap(self) -> UnwrapFuture<Self>
@@ -219,7 +235,7 @@ where
     }
 }
 
-pub struct LogErrorFuture<F>(F, log::Level);
+pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
 
 impl<F, T, E> Future for LogErrorFuture<F>
 where
@@ -230,12 +246,19 @@ where
 
     fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
         let level = self.1;
+        let location = self.2;
         let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
         match inner.poll(cx) {
             Poll::Ready(output) => Poll::Ready(match output {
                 Ok(output) => Some(output),
                 Err(error) => {
-                    log::log!(level, "{:?}", error);
+                    log::log!(
+                        level,
+                        "{}:{}: {:?}",
+                        location.file(),
+                        location.line(),
+                        error
+                    );
                     None
                 }
             }),

crates/workspace2/src/dock.rs 🔗

@@ -1,6 +1,6 @@
-use crate::{status_bar::StatusItemView, Axis, Workspace};
+use crate::{status_bar::StatusItemView, Workspace};
 use gpui::{
-    div, px, Action, AnchorCorner, AnyView, AppContext, Div, Entity, EntityId, EventEmitter,
+    div, px, Action, AnchorCorner, AnyView, AppContext, Axis, Div, Entity, EntityId, EventEmitter,
     FocusHandle, FocusableView, IntoElement, ParentElement, Render, SharedString, Styled,
     Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
 };

crates/workspace2/src/pane_group.rs 🔗

@@ -1,13 +1,9 @@
-use crate::{AppState, FollowerState, Pane, Workspace};
-use anyhow::{anyhow, bail, Result};
+use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
+use anyhow::{anyhow, Result};
 use call::{ActiveCall, ParticipantLocation};
 use collections::HashMap;
-use db::sqlez::{
-    bindable::{Bind, Column, StaticColumnCount},
-    statement::Statement,
-};
 use gpui::{
-    point, size, AnyWeakView, Bounds, Div, Entity as _, IntoElement, Model, Pixels, Point, View,
+    point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
     ViewContext,
 };
 use parking_lot::Mutex;
@@ -20,38 +16,6 @@ const HANDLE_HITBOX_SIZE: f32 = 4.0;
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum Axis {
-    Vertical,
-    Horizontal,
-}
-
-impl StaticColumnCount for Axis {}
-impl Bind for Axis {
-    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
-        match self {
-            Axis::Horizontal => "Horizontal",
-            Axis::Vertical => "Vertical",
-        }
-        .bind(statement, start_index)
-    }
-}
-
-impl Column for Axis {
-    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
-        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
-            Ok((
-                match axis_text.as_str() {
-                    "Horizontal" => Axis::Horizontal,
-                    "Vertical" => Axis::Vertical,
-                    _ => bail!("Stored serialized item kind is incorrect"),
-                },
-                next_index,
-            ))
-        })
-    }
-}
-
 #[derive(Clone, PartialEq)]
 pub struct PaneGroup {
     pub(crate) root: Member,
@@ -632,16 +596,10 @@ impl PaneAxis {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> Div {
+    ) -> gpui::AnyElement {
         debug_assert!(self.members.len() == self.flexes.lock().len());
 
-        div()
-            .flex()
-            .flex_auto()
-            .map(|s| match self.axis {
-                Axis::Vertical => s.flex_col(),
-                Axis::Horizontal => s.flex_row(),
-            })
+        pane_axis(self.axis, basis, self.flexes.clone())
             .children(self.members.iter().enumerate().map(|(ix, member)| {
                 match member {
                     Member::Axis(axis) => axis
@@ -658,6 +616,7 @@ impl PaneAxis {
                     Member::Pane(pane) => pane.clone().into_any_element(),
                 }
             }))
+            .into_any_element()
 
         // let mut pane_axis = PaneAxisElement::new(
         //     self.axis,
@@ -767,403 +726,480 @@ impl SplitDirection {
     }
 }
 
-// mod element {
-//     // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
-
-//     // use gpui::{
-//     //     geometry::{
-//     //         rect::Bounds<Pixels>,
-//     //         vector::{vec2f, Vector2F},
-//     //     },
-//     //     json::{self, ToJson},
-//     //     platform::{CursorStyle, MouseButton},
-//     //     scene::MouseDrag,
-//     //     AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
-//     //     SizeConstraint, Vector2FExt, ViewContext,
-//     // };
-
-//     use crate::{
-//         pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
-//         Workspace, WorkspaceSettings,
-//     };
-
-//     pub struct PaneAxisElement {
-//         axis: Axis,
-//         basis: usize,
-//         active_pane_ix: Option<usize>,
-//         flexes: Rc<RefCell<Vec<f32>>>,
-//         children: Vec<AnyElement<Workspace>>,
-//         bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
-//     }
-
-//     impl PaneAxisElement {
-//         pub fn new(
-//             axis: Axis,
-//             basis: usize,
-//             flexes: Rc<RefCell<Vec<f32>>>,
-//             bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
-//         ) -> Self {
-//             Self {
-//                 axis,
-//                 basis,
-//                 flexes,
-//                 bounding_boxes,
-//                 active_pane_ix: None,
-//                 children: Default::default(),
-//             }
-//         }
-
-//         pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
-//             self.active_pane_ix = active_pane_ix;
-//         }
-
-//         fn layout_children(
-//             &mut self,
-//             active_pane_magnification: f32,
-//             constraint: SizeConstraint,
-//             remaining_space: &mut f32,
-//             remaining_flex: &mut f32,
-//             cross_axis_max: &mut f32,
-//             view: &mut Workspace,
-//             cx: &mut ViewContext<Workspace>,
-//         ) {
-//             let flexes = self.flexes.borrow();
-//             let cross_axis = self.axis.invert();
-//             for (ix, child) in self.children.iter_mut().enumerate() {
-//                 let flex = if active_pane_magnification != 1. {
-//                     if let Some(active_pane_ix) = self.active_pane_ix {
-//                         if ix == active_pane_ix {
-//                             active_pane_magnification
-//                         } else {
-//                             1.
-//                         }
-//                     } else {
-//                         1.
-//                     }
-//                 } else {
-//                     flexes[ix]
-//                 };
-
-//                 let child_size = if *remaining_flex == 0.0 {
-//                     *remaining_space
-//                 } else {
-//                     let space_per_flex = *remaining_space / *remaining_flex;
-//                     space_per_flex * flex
-//                 };
-
-//                 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);
-//                 *remaining_flex -= flex;
-//                 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
-//             }
-//         }
-
-//         fn handle_resize(
-//             flexes: Rc<RefCell<Vec<f32>>>,
-//             axis: Axis,
-//             preceding_ix: usize,
-//             child_start: Vector2F,
-//             drag_bounds: Bounds<Pixels>,
-//         ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
-//             let size = move |ix, flexes: &[f32]| {
-//                 drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
-//             };
-
-//             move |drag, workspace: &mut Workspace, cx| {
-//                 if drag.end {
-//                     // TODO: Clear cascading resize state
-//                     return;
-//                 }
-//                 let min_size = match axis {
-//                     Axis::Horizontal => HORIZONTAL_MIN_SIZE,
-//                     Axis::Vertical => VERTICAL_MIN_SIZE,
-//                 };
-//                 let mut flexes = flexes.borrow_mut();
-
-//                 // Don't allow resizing to less than the minimum size, if elements are already too small
-//                 if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
-//                     return;
-//                 }
-
-//                 let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
-//                     - size(preceding_ix, flexes.as_slice());
-
-//                 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-//                     let flex_change = pixel_dx / drag_bounds.length_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 mut successors = from_fn({
-//                     let forward = proposed_current_pixel_change > 0.;
-//                     let mut ix_offset = 0;
-//                     let len = flexes.len();
-//                     move || {
-//                         let result = if forward {
-//                             (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
-//                         } else {
-//                             (preceding_ix as isize - ix_offset as isize >= 0)
-//                                 .then(|| preceding_ix - ix_offset)
-//                         };
-
-//                         ix_offset += 1;
-
-//                         result
-//                     }
-//                 });
-
-//                 while proposed_current_pixel_change.abs() > 0. {
-//                     let Some(current_ix) = successors.next() else {
-//                         break;
-//                     };
-
-//                     let next_target_size = f32::max(
-//                         size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
-//                         min_size,
-//                     );
-
-//                     let current_target_size = f32::max(
-//                         size(current_ix, flexes.as_slice())
-//                             + size(current_ix + 1, flexes.as_slice())
-//                             - next_target_size,
-//                         min_size,
-//                     );
-
-//                     let current_pixel_change =
-//                         current_target_size - size(current_ix, flexes.as_slice());
-
-//                     let (current_target_flex, next_target_flex) =
-//                         flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
-
-//                     flexes[current_ix] = current_target_flex;
-//                     flexes[current_ix + 1] = next_target_flex;
-
-//                     proposed_current_pixel_change -= current_pixel_change;
-//                 }
-
-//                 workspace.schedule_serialize(cx);
-//                 cx.notify();
-//             }
-//         }
-//     }
-
-//     impl Extend<AnyElement<Workspace>> for PaneAxisElement {
-//         fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
-//             self.children.extend(children);
-//         }
-//     }
-
-//     impl Element<Workspace> for PaneAxisElement {
-//         type LayoutState = f32;
-//         type PaintState = ();
-
-//         fn layout(
-//             &mut self,
-//             constraint: SizeConstraint,
-//             view: &mut Workspace,
-//             cx: &mut ViewContext<Workspace>,
-//         ) -> (Vector2F, Self::LayoutState) {
-//             debug_assert!(self.children.len() == self.flexes.borrow().len());
-
-//             let active_pane_magnification =
-//                 settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
-
-//             let mut remaining_flex = 0.;
-
-//             if active_pane_magnification != 1. {
-//                 let active_pane_flex = self
-//                     .active_pane_ix
-//                     .map(|_| active_pane_magnification)
-//                     .unwrap_or(1.);
-//                 remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
-//             } else {
-//                 for flex in self.flexes.borrow().iter() {
-//                     remaining_flex += flex;
-//                 }
-//             }
-
-//             let mut cross_axis_max: f32 = 0.0;
-//             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_children(
-//                 active_pane_magnification,
-//                 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,
-//             bounds: Bounds<Pixels>,
-//             visible_bounds: Bounds<Pixels>,
-//             remaining_space: &mut Self::LayoutState,
-//             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();
-
-//             let overflowing = *remaining_space < 0.;
-//             if overflowing {
-//                 cx.scene().push_layer(Some(visible_bounds));
-//             }
-
-//             let mut child_origin = bounds.origin();
-
-//             let mut bounding_boxes = self.bounding_boxes.borrow_mut();
-//             bounding_boxes.clear();
-
-//             let mut children_iter = self.children.iter_mut().enumerate().peekable();
-//             while let Some((ix, child)) = children_iter.next() {
-//                 let child_start = child_origin.clone();
-//                 child.paint(child_origin, visible_bounds, view, cx);
-
-//                 bounding_boxes.push(Some(Bounds<Pixels>::new(child_origin, child.size())));
-
-//                 match self.axis {
-//                     Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
-//                     Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
-//                 }
-
-//                 if can_resize && children_iter.peek().is_some() {
-//                     cx.scene().push_stacking_context(None, None);
-
-//                     let handle_origin = match self.axis {
-//                         Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
-//                         Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
-//                     };
-
-//                     let handle_bounds = match self.axis {
-//                         Axis::Horizontal => Bounds<Pixels>::new(
-//                             handle_origin,
-//                             vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
-//                         ),
-//                         Axis::Vertical => Bounds<Pixels>::new(
-//                             handle_origin,
-//                             vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
-//                         ),
-//                     };
-
-//                     let style = match self.axis {
-//                         Axis::Horizontal => CursorStyle::ResizeLeftRight,
-//                         Axis::Vertical => CursorStyle::ResizeUpDown,
-//                     };
-
-//                     cx.scene().push_cursor_region(CursorRegion {
-//                         bounds: handle_bounds,
-//                         style,
-//                     });
-
-//                     enum ResizeHandle {}
-//                     let mut mouse_region = MouseRegion::new::<ResizeHandle>(
-//                         cx.view_id(),
-//                         self.basis + ix,
-//                         handle_bounds,
-//                     );
-//                     mouse_region = mouse_region
-//                         .on_drag(
-//                             MouseButton::Left,
-//                             Self::handle_resize(
-//                                 self.flexes.clone(),
-//                                 self.axis,
-//                                 ix,
-//                                 child_start,
-//                                 visible_bounds.clone(),
-//                             ),
-//                         )
-//                         .on_click(MouseButton::Left, {
-//                             let flexes = self.flexes.clone();
-//                             move |e, v: &mut Workspace, cx| {
-//                                 if e.click_count >= 2 {
-//                                     let mut borrow = flexes.borrow_mut();
-//                                     *borrow = vec![1.; borrow.len()];
-//                                     v.schedule_serialize(cx);
-//                                     cx.notify();
-//                                 }
-//                             }
-//                         });
-//                     cx.scene().push_mouse_region(mouse_region);
-
-//                     cx.scene().pop_stacking_context();
-//                 }
-//             }
-
-//             if overflowing {
-//                 cx.scene().pop_layer();
-//             }
-//         }
-
-//         fn rect_for_text_range(
-//             &self,
-//             range_utf16: Range<usize>,
-//             _: Bounds<Pixels>,
-//             _: Bounds<Pixels>,
-//             _: &Self::LayoutState,
-//             _: &Self::PaintState,
-//             view: &Workspace,
-//             cx: &ViewContext<Workspace>,
-//         ) -> Option<Bounds<Pixels>> {
-//             self.children
-//                 .iter()
-//                 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
-//         }
-
-//         fn debug(
-//             &self,
-//             bounds: Bounds<Pixels>,
-//             _: &Self::LayoutState,
-//             _: &Self::PaintState,
-//             view: &Workspace,
-//             cx: &ViewContext<Workspace>,
-//         ) -> json::Value {
-//             serde_json::json!({
-//                 "type": "PaneAxis",
-//                 "bounds": bounds.to_json(),
-//                 "axis": self.axis.to_json(),
-//                 "flexes": *self.flexes.borrow(),
-//                 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-//             })
-//         }
-//     }
-// }
+mod element {
+    use std::sync::Arc;
+
+    use gpui::{relative, AnyElement, Axis, Element, IntoElement, ParentElement, Style};
+    use parking_lot::Mutex;
+    use smallvec::SmallVec;
+
+    pub fn pane_axis(axis: Axis, basis: usize, flexes: Arc<Mutex<Vec<f32>>>) -> PaneAxisElement {
+        PaneAxisElement {
+            axis,
+            basis,
+            flexes,
+            children: SmallVec::new(),
+        }
+    }
+
+    pub struct PaneAxisElement {
+        axis: Axis,
+        basis: usize,
+        flexes: Arc<Mutex<Vec<f32>>>,
+        children: SmallVec<[AnyElement; 2]>,
+    }
+
+    impl IntoElement for PaneAxisElement {
+        type Element = Self;
+
+        fn element_id(&self) -> Option<ui::prelude::ElementId> {
+            Some("pane axis".into())
+        }
+
+        fn into_element(self) -> Self::Element {
+            self
+        }
+    }
+
+    impl Element for PaneAxisElement {
+        type State = ();
+
+        fn layout(
+            &mut self,
+            state: Option<Self::State>,
+            cx: &mut ui::prelude::WindowContext,
+        ) -> (gpui::LayoutId, Self::State) {
+            let mut style = Style::default();
+            style.size.width = relative(1.).into();
+            style.size.height = relative(1.).into();
+            let layout_id = cx.request_layout(&style, None);
+
+            (layout_id, ())
+        }
+
+        fn paint(
+            self,
+            bounds: gpui::Bounds<ui::prelude::Pixels>,
+            state: &mut Self::State,
+            cx: &mut ui::prelude::WindowContext,
+        ) {
+            let flexes = self.flexes.lock().clone();
+            debug_assert!(flexes.len() == self.children.len());
+
+            let origin = bounds.origin;
+            let size = bounds.size;
+            let len = self.children.len();
+            let child_size = size.apply_along(self.axis, |val| val / len as f32);
+            for (ix, child) in self.children.into_iter().enumerate() {
+                let origin =
+                    origin.apply_along(self.axis, |val| val + child_size.along(self.axis) * ix);
+                child.draw(origin, child_size.into(), cx);
+            }
+        }
+    }
+
+    impl ParentElement for PaneAxisElement {
+        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
+            &mut self.children
+        }
+    }
+
+    //     // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
+
+    //     // use gpui::{
+    //     //     geometry::{
+    //     //         rect::Bounds<Pixels>,
+    //     //         vector::{vec2f, Vector2F},
+    //     //     },
+    //     //     json::{self, ToJson},
+    //     //     platform::{CursorStyle, MouseButton},
+    //     //     scene::MouseDrag,
+    //     //     AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
+    //     //     SizeConstraint, Vector2FExt, ViewContext,
+    //     // };
+
+    //     use crate::{
+    //         pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
+    //         Workspace, WorkspaceSettings,
+    //     };
+
+    //     pub struct PaneAxisElement {
+    //         axis: Axis,
+    //         basis: usize,
+    //         active_pane_ix: Option<usize>,
+    //         flexes: Rc<RefCell<Vec<f32>>>,
+    //         children: Vec<AnyElement<Workspace>>,
+    //         bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
+    //     }
+
+    //     impl PaneAxisElement {
+    //         pub fn new(
+    //             axis: Axis,
+    //             basis: usize,
+    //             flexes: Rc<RefCell<Vec<f32>>>,
+    //             bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
+    //         ) -> Self {
+    //             Self {
+    //                 axis,
+    //                 basis,
+    //                 flexes,
+    //                 bounding_boxes,
+    //                 active_pane_ix: None,
+    //                 children: Default::default(),
+    //             }
+    //         }
+
+    //         pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
+    //             self.active_pane_ix = active_pane_ix;
+    //         }
+
+    //         fn layout_children(
+    //             &mut self,
+    //             active_pane_magnification: f32,
+    //             constraint: SizeConstraint,
+    //             remaining_space: &mut f32,
+    //             remaining_flex: &mut f32,
+    //             cross_axis_max: &mut f32,
+    //             view: &mut Workspace,
+    //             cx: &mut ViewContext<Workspace>,
+    //         ) {
+    //             let flexes = self.flexes.borrow();
+    //             let cross_axis = self.axis.invert();
+    //             for (ix, child) in self.children.iter_mut().enumerate() {
+    //                 let flex = if active_pane_magnification != 1. {
+    //                     if let Some(active_pane_ix) = self.active_pane_ix {
+    //                         if ix == active_pane_ix {
+    //                             active_pane_magnification
+    //                         } else {
+    //                             1.
+    //                         }
+    //                     } else {
+    //                         1.
+    //                     }
+    //                 } else {
+    //                     flexes[ix]
+    //                 };
+
+    //                 let child_size = if *remaining_flex == 0.0 {
+    //                     *remaining_space
+    //                 } else {
+    //                     let space_per_flex = *remaining_space / *remaining_flex;
+    //                     space_per_flex * flex
+    //                 };
+
+    //                 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);
+    //                 *remaining_flex -= flex;
+    //                 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
+    //             }
+    //         }
+
+    //         fn handle_resize(
+    //             flexes: Rc<RefCell<Vec<f32>>>,
+    //             axis: Axis,
+    //             preceding_ix: usize,
+    //             child_start: Vector2F,
+    //             drag_bounds: Bounds<Pixels>,
+    //         ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
+    //             let size = move |ix, flexes: &[f32]| {
+    //                 drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
+    //             };
+
+    //             move |drag, workspace: &mut Workspace, cx| {
+    //                 if drag.end {
+    //                     // TODO: Clear cascading resize state
+    //                     return;
+    //                 }
+    //                 let min_size = match axis {
+    //                     Axis::Horizontal => HORIZONTAL_MIN_SIZE,
+    //                     Axis::Vertical => VERTICAL_MIN_SIZE,
+    //                 };
+    //                 let mut flexes = flexes.borrow_mut();
+
+    //                 // Don't allow resizing to less than the minimum size, if elements are already too small
+    //                 if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
+    //                     return;
+    //                 }
+
+    //                 let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
+    //                     - size(preceding_ix, flexes.as_slice());
+
+    //                 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
+    //                     let flex_change = pixel_dx / drag_bounds.length_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 mut successors = from_fn({
+    //                     let forward = proposed_current_pixel_change > 0.;
+    //                     let mut ix_offset = 0;
+    //                     let len = flexes.len();
+    //                     move || {
+    //                         let result = if forward {
+    //                             (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
+    //                         } else {
+    //                             (preceding_ix as isize - ix_offset as isize >= 0)
+    //                                 .then(|| preceding_ix - ix_offset)
+    //                         };
+
+    //                         ix_offset += 1;
+
+    //                         result
+    //                     }
+    //                 });
+
+    //                 while proposed_current_pixel_change.abs() > 0. {
+    //                     let Some(current_ix) = successors.next() else {
+    //                         break;
+    //                     };
+
+    //                     let next_target_size = f32::max(
+    //                         size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
+    //                         min_size,
+    //                     );
+
+    //                     let current_target_size = f32::max(
+    //                         size(current_ix, flexes.as_slice())
+    //                             + size(current_ix + 1, flexes.as_slice())
+    //                             - next_target_size,
+    //                         min_size,
+    //                     );
+
+    //                     let current_pixel_change =
+    //                         current_target_size - size(current_ix, flexes.as_slice());
+
+    //                     let (current_target_flex, next_target_flex) =
+    //                         flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
+
+    //                     flexes[current_ix] = current_target_flex;
+    //                     flexes[current_ix + 1] = next_target_flex;
+
+    //                     proposed_current_pixel_change -= current_pixel_change;
+    //                 }
+
+    //                 workspace.schedule_serialize(cx);
+    //                 cx.notify();
+    //             }
+    //         }
+    //     }
+
+    //     impl Extend<AnyElement<Workspace>> for PaneAxisElement {
+    //         fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
+    //             self.children.extend(children);
+    //         }
+    //     }
+
+    //     impl Element<Workspace> for PaneAxisElement {
+    //         type LayoutState = f32;
+    //         type PaintState = ();
+
+    //         fn layout(
+    //             &mut self,
+    //             constraint: SizeConstraint,
+    //             view: &mut Workspace,
+    //             cx: &mut ViewContext<Workspace>,
+    //         ) -> (Vector2F, Self::LayoutState) {
+    //             debug_assert!(self.children.len() == self.flexes.borrow().len());
+
+    //             let active_pane_magnification =
+    //                 settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
+
+    //             let mut remaining_flex = 0.;
+
+    //             if active_pane_magnification != 1. {
+    //                 let active_pane_flex = self
+    //                     .active_pane_ix
+    //                     .map(|_| active_pane_magnification)
+    //                     .unwrap_or(1.);
+    //                 remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
+    //             } else {
+    //                 for flex in self.flexes.borrow().iter() {
+    //                     remaining_flex += flex;
+    //                 }
+    //             }
+
+    //             let mut cross_axis_max: f32 = 0.0;
+    //             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_children(
+    //                 active_pane_magnification,
+    //                 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,
+    //             bounds: Bounds<Pixels>,
+    //             visible_bounds: Bounds<Pixels>,
+    //             remaining_space: &mut Self::LayoutState,
+    //             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();
+
+    //             let overflowing = *remaining_space < 0.;
+    //             if overflowing {
+    //                 cx.scene().push_layer(Some(visible_bounds));
+    //             }
+
+    //             let mut child_origin = bounds.origin();
+
+    //             let mut bounding_boxes = self.bounding_boxes.borrow_mut();
+    //             bounding_boxes.clear();
+
+    //             let mut children_iter = self.children.iter_mut().enumerate().peekable();
+    //             while let Some((ix, child)) = children_iter.next() {
+    //                 let child_start = child_origin.clone();
+    //                 child.paint(child_origin, visible_bounds, view, cx);
+
+    //                 bounding_boxes.push(Some(Bounds<Pixels>::new(child_origin, child.size())));
+
+    //                 match self.axis {
+    //                     Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
+    //                     Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
+    //                 }
+
+    //                 if can_resize && children_iter.peek().is_some() {
+    //                     cx.scene().push_stacking_context(None, None);
+
+    //                     let handle_origin = match self.axis {
+    //                         Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
+    //                         Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
+    //                     };
+
+    //                     let handle_bounds = match self.axis {
+    //                         Axis::Horizontal => Bounds<Pixels>::new(
+    //                             handle_origin,
+    //                             vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
+    //                         ),
+    //                         Axis::Vertical => Bounds<Pixels>::new(
+    //                             handle_origin,
+    //                             vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
+    //                         ),
+    //                     };
+
+    //                     let style = match self.axis {
+    //                         Axis::Horizontal => CursorStyle::ResizeLeftRight,
+    //                         Axis::Vertical => CursorStyle::ResizeUpDown,
+    //                     };
+
+    //                     cx.scene().push_cursor_region(CursorRegion {
+    //                         bounds: handle_bounds,
+    //                         style,
+    //                     });
+
+    //                     enum ResizeHandle {}
+    //                     let mut mouse_region = MouseRegion::new::<ResizeHandle>(
+    //                         cx.view_id(),
+    //                         self.basis + ix,
+    //                         handle_bounds,
+    //                     );
+    //                     mouse_region = mouse_region
+    //                         .on_drag(
+    //                             MouseButton::Left,
+    //                             Self::handle_resize(
+    //                                 self.flexes.clone(),
+    //                                 self.axis,
+    //                                 ix,
+    //                                 child_start,
+    //                                 visible_bounds.clone(),
+    //                             ),
+    //                         )
+    //                         .on_click(MouseButton::Left, {
+    //                             let flexes = self.flexes.clone();
+    //                             move |e, v: &mut Workspace, cx| {
+    //                                 if e.click_count >= 2 {
+    //                                     let mut borrow = flexes.borrow_mut();
+    //                                     *borrow = vec![1.; borrow.len()];
+    //                                     v.schedule_serialize(cx);
+    //                                     cx.notify();
+    //                                 }
+    //                             }
+    //                         });
+    //                     cx.scene().push_mouse_region(mouse_region);
+
+    //                     cx.scene().pop_stacking_context();
+    //                 }
+    //             }
+
+    //             if overflowing {
+    //                 cx.scene().pop_layer();
+    //             }
+    //         }
+
+    //         fn rect_for_text_range(
+    //             &self,
+    //             range_utf16: Range<usize>,
+    //             _: Bounds<Pixels>,
+    //             _: Bounds<Pixels>,
+    //             _: &Self::LayoutState,
+    //             _: &Self::PaintState,
+    //             view: &Workspace,
+    //             cx: &ViewContext<Workspace>,
+    //         ) -> Option<Bounds<Pixels>> {
+    //             self.children
+    //                 .iter()
+    //                 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
+    //         }
+
+    //         fn debug(
+    //             &self,
+    //             bounds: Bounds<Pixels>,
+    //             _: &Self::LayoutState,
+    //             _: &Self::PaintState,
+    //             view: &Workspace,
+    //             cx: &ViewContext<Workspace>,
+    //         ) -> json::Value {
+    //             serde_json::json!({
+    //                 "type": "PaneAxis",
+    //                 "bounds": bounds.to_json(),
+    //                 "axis": self.axis.to_json(),
+    //                 "flexes": *self.flexes.borrow(),
+    //                 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
+    //             })
+    //         }
+    //     }
+}

crates/workspace2/src/persistence.rs 🔗

@@ -6,12 +6,12 @@ use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
 use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
-use gpui::WindowBounds;
+use gpui::{Axis, WindowBounds};
 
 use util::{unzip_option, ResultExt};
 use uuid::Uuid;
 
-use crate::{Axis, WorkspaceId};
+use crate::WorkspaceId;
 
 use model::{
     GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
@@ -403,7 +403,7 @@ impl WorkspaceDb {
         .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))
+                    .map(|flexes: String| serde_json::from_str::<Vec<f32>>(&flexes))
                     .transpose()?;
 
                 Ok(SerializedPaneGroup::Group {

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

@@ -1,13 +1,11 @@
-use crate::{
-    item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
-};
+use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
 use anyhow::{Context, Result};
 use async_recursion::async_recursion;
 use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
+use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
 use project::Project;
 use std::{
     path::{Path, PathBuf},