diff --git a/crates/workspace/src/adjustable_flex.rs b/crates/workspace/src/adjustable_flex.rs deleted file mode 100644 index 4ea1b719f1e08a6893448c0c08a57a421f53e818..0000000000000000000000000000000000000000 --- a/crates/workspace/src/adjustable_flex.rs +++ /dev/null @@ -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 { - axis: Axis, - handle_size: f32, - items: Vec<(AnyElement, f32)> -} - -impl AdjustableFlex { - fn new(axis: Axis) -> Self { - AdjustableFlex { - axis, - handle_size: 2., - items: Vec::new(), - } - } - - fn add_item() -} - -impl Element for AdjustableFlex { - type LayoutState = (); - - type PaintState = (); - - fn layout( - &mut self, - constraint: gpui::SizeConstraint, - view: &mut V, - cx: &mut gpui::LayoutContext, - ) -> (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, - ) -> Self::PaintState { - todo!() - } - - fn rect_for_text_range( - &self, - range_utf16: std::ops::Range, - bounds: gpui::geometry::rect::RectF, - visible_bounds: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> Option { - todo!() - } - - fn debug( - &self, - bounds: gpui::geometry::rect::RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - view: &V, - cx: &gpui::ViewContext, - ) -> serde_json::Value { - todo!() - } -} diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 8160a770a3759bddeefd1bf8a6a124bcd652bdcd..7198dff3bfe9362ad0c172cdc982044cea12d88a 100644 --- a/crates/workspace/src/pane_group.rs +++ b/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 { 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), @@ -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) -> bool { @@ -133,6 +139,7 @@ impl Member { pub fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -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, + pub ratios: Rc>>, } impl PaneAxis { @@ -378,6 +387,7 @@ impl PaneAxis { fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -386,19 +396,29 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - 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::(cx).active_pane_magnification; - } + // TODO: Include minimum sizes + // TODO: Restore this + // if member.contains(active_pane) { + // flex = settings::get::(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 { + axis: Axis, + handle_size: f32, + basis: usize, + callback: Rc)>, + children: Vec>, + } + + impl AdjustableGroupElement { + pub fn new( + axis: Axis, + handle_size: f32, + basis: usize, + callback: impl Fn(Vec) + '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, + ) { + 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::().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 Extend> for AdjustableGroupElement { + fn extend>>(&mut self, children: T) { + self.children.extend(children); + } + } + + impl Element for AdjustableGroupElement { + type LayoutState = f32; + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut V, + cx: &mut LayoutContext, + ) -> (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::(); + 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, + ) -> 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::(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, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Option { + 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, + ) -> 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::>() + }) + } + } + + pub struct AdjustableGroupItem { + metadata: AdjustableFlexData, + child: AnyElement, + } + + impl AdjustableGroupItem { + pub fn new(child: impl Element, flex: f32) -> Self { + Self { + metadata: AdjustableFlexData { flex }, + child: child.into_any(), + } + } + } + + impl Element for AdjustableGroupItem { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut V, + cx: &mut LayoutContext, + ) -> (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, + ) -> Self::PaintState { + self.child + .paint(scene, bounds.origin(), visible_bounds, view, cx) + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Option { + 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, + ) -> Value { + serde_json::json!({ + "type": "Flexible", + "flex": self.metadata.flex, + "child": self.child.debug(view, cx) + }) + } + } +} diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 10750618530642465d73eb3d4016e1d42095c85a..c159ff0f427c573ca83733ea582cd60f62b84854 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -187,6 +187,7 @@ impl SerializedPaneGroup { Member::Axis(PaneAxis { axis: *axis, members, + ratios: Default::default() }), current_active_pane, items, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cafcd191a3c476cb1f532b2ffbb07e8bb1292616..f91204c51a4eaa4a7c82488b84607046af4f0a73 100644 --- a/crates/workspace/src/workspace.rs +++ b/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()