diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index ec4c222c..b7a0862c 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -279,7 +279,7 @@ impl StoryWorkspace { cx, ), ], - vec![None, Some(px(360.))], + vec![None, Some(0.2)], &dock_area, window, cx, @@ -328,9 +328,9 @@ impl StoryWorkspace { _ = dock_area.update(cx, |view, cx| { view.set_version(MAIN_DOCK_AREA.version, window, cx); view.set_center(dock_item, window, cx); - view.set_left_dock(left_panels, Some(px(350.)), true, window, cx); - view.set_bottom_dock(bottom_panels, Some(px(200.)), true, window, cx); - view.set_right_dock(right_panels, Some(px(320.)), true, window, cx); + view.set_left_dock(left_panels, 0.3, true, window, cx); + view.set_bottom_dock(bottom_panels, 0.2, true, window, cx); + view.set_right_dock(right_panels, 0.3, true, window, cx); Self::save_state(&view.dump(cx)).unwrap(); }); diff --git a/crates/story/src/resizable_story.rs b/crates/story/src/resizable_story.rs index 644524c1..d2bbeb6c 100644 --- a/crates/story/src/resizable_story.rs +++ b/crates/story/src/resizable_story.rs @@ -53,36 +53,32 @@ impl ResizableStory { v_resizable() .group( h_resizable() - .size(px(150.)) + .ratio(0.1) .child( resizable_panel() - .size(px(300.)) + .ratio(0.3) .content(|_, cx| panel_box("Left 1 (Min 120px)", cx)), cx, ) .child( - resizable_panel() - .size(px(400.)) - .content(|_, cx| panel_box("Center 1", cx)), + resizable_panel().content(|_, cx| panel_box("Center 1", cx)), cx, ) .child( resizable_panel() - .size(px(300.)) + .ratio(0.4) .content(|_, cx| panel_box("Right (Grow)", cx)), cx, ), cx, ) .child( - resizable_panel() - .size(px(150.)) - .content(|_, cx| panel_box("Center (Grow)", cx)), + resizable_panel().content(|_, cx| panel_box("Center (Grow)", cx)), cx, ) .child( resizable_panel() - .size(px(210.)) + .ratio(0.5) .content(|_, cx| panel_box("Bottom", cx)), cx, ) @@ -92,14 +88,12 @@ impl ResizableStory { h_resizable() .child( resizable_panel() - .size(px(300.)) + .ratio(0.3) .content(|_, cx| panel_box("Left 2", cx)), cx, ) .child( - resizable_panel() - .size(px(400.)) - .content(|_, cx| panel_box("Right (Grow)", cx)), + resizable_panel().content(|_, cx| panel_box("Right (Grow)", cx)), cx, ) }); @@ -114,8 +108,9 @@ impl ResizableStory { impl Render for ResizableStory { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { v_flex() + .h_full() .gap_6() - .child(self.group1.clone()) + .child(div().h(px(800.)).child(self.group1.clone())) .child(self.group2.clone()) } } diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 5599bc9f..9ac9b3f2 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -3,9 +3,9 @@ use std::{ops::Deref, sync::Arc}; use gpui::{ - div, prelude::FluentBuilder as _, px, App, AppContext, Axis, Context, Element, Empty, Entity, - IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, - StyleRefinement, Styled as _, WeakEntity, Window, + div, prelude::FluentBuilder as _, px, Along, App, AppContext, Axis, Context, Element, Empty, + Entity, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, + Style, StyleRefinement, Styled as _, WeakEntity, Window, }; use serde::{Deserialize, Serialize}; @@ -66,8 +66,8 @@ pub struct Dock { pub(super) placement: DockPlacement, dock_area: WeakEntity, pub(crate) panel: DockItem, - /// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height. - pub(super) size: Pixels, + /// The ratio of the dock. + pub(super) ratio: f32, pub(super) open: bool, /// Whether the Dock is collapsible, default: true pub(super) collapsible: bool, @@ -104,7 +104,7 @@ impl Dock { panel, open: true, collapsible: true, - size: px(200.0), + ratio: 0.2, resizing: false, } } @@ -147,7 +147,7 @@ impl Dock { pub(super) fn from_state( dock_area: WeakEntity, placement: DockPlacement, - size: Pixels, + ratio: f32, panel: DockItem, open: bool, window: &mut Window, @@ -176,7 +176,7 @@ impl Dock { dock_area, panel, open, - size, + ratio, collapsible: true, resizing: false, } @@ -244,13 +244,13 @@ impl Dock { /// Returns the size of the Dock, the size is means the width or height of /// the Dock, if the placement is left or right, the size is width, /// otherwise the size is height. - pub fn size(&self) -> Pixels { - self.size + pub fn ratio(&self) -> f32 { + self.ratio } /// Set the size of the Dock. - pub fn set_size(&mut self, size: Pixels, _: &mut Window, cx: &mut Context) { - self.size = size.max(PANEL_MIN_SIZE); + pub fn set_ratio(&mut self, ratio: f32, _: &mut Window, cx: &mut Context) { + self.ratio = ratio; cx.notify(); } @@ -287,6 +287,16 @@ impl Dock { cx.notify(); } + fn container_size(&self, cx: &App) -> Pixels { + let dock_area = self + .dock_area + .upgrade() + .expect("DockArea is missing") + .read(cx); + let area_bounds = dock_area.bounds; + area_bounds.size.along(self.placement.axis()) + } + fn render_resize_handle(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let axis = self.placement.axis(); let view = cx.entity().clone(); @@ -301,6 +311,7 @@ impl Dock { cx.new(|_| info.deref().clone()) }) } + fn resize(&mut self, mouse_position: Point, _: &mut Window, cx: &mut Context) { if !self.resizing { return; @@ -312,28 +323,6 @@ impl Dock { .expect("DockArea is missing") .read(cx); let area_bounds = dock_area.bounds; - let mut left_dock_size = Pixels(0.0); - let mut right_dock_size = Pixels(0.0); - - // Get the size of the left dock if it's open and not the current dock - if let Some(left_dock) = &dock_area.left_dock { - if left_dock.entity_id() != cx.entity().entity_id() { - let left_dock_read = left_dock.read(cx); - if left_dock_read.is_open() { - left_dock_size = left_dock_read.size; - } - } - } - - // Get the size of the right dock if it's open and not the current dock - if let Some(right_dock) = &dock_area.right_dock { - if right_dock.entity_id() != cx.entity().entity_id() { - let right_dock_read = right_dock.read(cx); - if right_dock_read.is_open() { - right_dock_size = right_dock_read.size; - } - } - } let size = match self.placement { DockPlacement::Left => mouse_position.x - area_bounds.left(), @@ -341,21 +330,10 @@ impl Dock { DockPlacement::Bottom => area_bounds.bottom() - mouse_position.y, DockPlacement::Center => unreachable!(), }; - match self.placement { - DockPlacement::Left => { - let max_size = area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size; - self.size = size.clamp(PANEL_MIN_SIZE, max_size); - } - DockPlacement::Right => { - let max_size = area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size; - self.size = size.clamp(PANEL_MIN_SIZE, max_size); - } - DockPlacement::Bottom => { - let max_size = area_bounds.size.height - PANEL_MIN_SIZE; - self.size = size.clamp(PANEL_MIN_SIZE, max_size); - } - DockPlacement::Center => unreachable!(), - } + let container_size = self.container_size(cx); + let ratio = size / container_size; + let min_ratio = PANEL_MIN_SIZE / container_size; + self.ratio = ratio.clamp(min_ratio, 1.); cx.notify(); } @@ -372,13 +350,15 @@ impl Render for Dock { } let cache_style = StyleRefinement::default().absolute().size_full(); + let container_size = self.container_size(cx); + let dock_size = (self.ratio * container_size).max(PANEL_MIN_SIZE); div() .relative() .overflow_hidden() .map(|this| match self.placement { - DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size), - DockPlacement::Bottom => this.w_full().h(self.size), + DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(dock_size), + DockPlacement::Bottom => this.w_full().h(dock_size), DockPlacement::Center => unreachable!(), }) // Bottom Dock should keep the title bar, then user can click the Toggle button diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index d6c686a8..ce2dd9dd 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -78,7 +78,7 @@ pub enum DockItem { Split { axis: Axis, items: Vec, - sizes: Vec>, + ratios: Vec>, view: Entity, }, /// Tab layout @@ -100,12 +100,15 @@ impl std::fmt::Debug for DockItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DockItem::Split { - axis, items, sizes, .. + axis, + items, + ratios, + .. } => f .debug_struct("Split") .field("axis", axis) .field("items", &items.len()) - .field("sizes", sizes) + .field("ratios", ratios) .finish(), DockItem::Tabs { items, active_ix, .. @@ -140,7 +143,7 @@ impl DockItem { pub fn split_with_sizes( axis: Axis, items: Vec, - sizes: Vec>, + ratios: Vec>, dock_area: &WeakEntity, window: &mut Window, cx: &mut App, @@ -150,14 +153,14 @@ impl DockItem { let mut stack_panel = StackPanel::new(axis, window, cx); for (i, item) in items.iter_mut().enumerate() { let view = item.view(); - let size = sizes.get(i).copied().flatten(); - stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx) + let ratio = ratios.get(i).copied().flatten(); + stack_panel.add_panel(view.clone(), ratio, dock_area.clone(), window, cx) } for (i, item) in items.iter().enumerate() { let view = item.view(); - let size = sizes.get(i).copied().flatten(); - stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx) + let ratio = ratios.get(i).copied().flatten(); + stack_panel.add_panel(view.clone(), ratio, dock_area.clone(), window, cx) } stack_panel @@ -176,7 +179,7 @@ impl DockItem { Self::Split { axis, items, - sizes, + ratios, view: stack_panel, } } @@ -447,7 +450,7 @@ impl DockArea { let dock_item = DockItem::Split { axis: Axis::Horizontal, items: vec![], - sizes: vec![], + ratios: vec![], view: stack_panel.clone(), }; @@ -527,7 +530,7 @@ impl DockArea { pub fn set_left_dock( &mut self, panel: DockItem, - size: Option, + ratio: f32, open: bool, window: &mut Window, cx: &mut Context, @@ -536,9 +539,7 @@ impl DockArea { let weak_self = cx.entity().downgrade(); self.left_dock = Some(cx.new(|cx| { let mut dock = Dock::left(weak_self.clone(), window, cx); - if let Some(size) = size { - dock.set_size(size, window, cx); - } + dock.set_ratio(ratio, window, cx); dock.set_panel(panel, window, cx); dock.set_open(open, window, cx); dock @@ -549,7 +550,7 @@ impl DockArea { pub fn set_bottom_dock( &mut self, panel: DockItem, - size: Option, + ratio: f32, open: bool, window: &mut Window, cx: &mut Context, @@ -558,9 +559,7 @@ impl DockArea { let weak_self = cx.entity().downgrade(); self.bottom_dock = Some(cx.new(|cx| { let mut dock = Dock::bottom(weak_self.clone(), window, cx); - if let Some(size) = size { - dock.set_size(size, window, cx); - } + dock.set_ratio(ratio, window, cx); dock.set_panel(panel, window, cx); dock.set_open(open, window, cx); dock @@ -571,7 +570,7 @@ impl DockArea { pub fn set_right_dock( &mut self, panel: DockItem, - size: Option, + ratio: f32, open: bool, window: &mut Window, cx: &mut Context, @@ -580,9 +579,7 @@ impl DockArea { let weak_self = cx.entity().downgrade(); self.right_dock = Some(cx.new(|cx| { let mut dock = Dock::right(weak_self.clone(), window, cx); - if let Some(size) = size { - dock.set_size(size, window, cx); - } + dock.set_ratio(ratio, window, cx); dock.set_panel(panel, window, cx); dock.set_open(open, window, cx); dock @@ -726,7 +723,7 @@ impl DockArea { } else { self.set_left_dock( DockItem::tabs(vec![panel], None, &weak_self, window, cx), - None, + 0.2, true, window, cx, @@ -739,7 +736,7 @@ impl DockArea { } else { self.set_bottom_dock( DockItem::tabs(vec![panel], None, &weak_self, window, cx), - None, + 0.2, true, window, cx, @@ -752,7 +749,7 @@ impl DockArea { } else { self.set_right_dock( DockItem::tabs(vec![panel], None, &weak_self, window, cx), - None, + 0.2, true, window, cx, diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 25eb2aa7..d194ad04 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -13,8 +13,8 @@ use crate::{ use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel}; use gpui::{ prelude::FluentBuilder as _, App, AppContext, Axis, Context, DismissEvent, Entity, - EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled, - Subscription, WeakEntity, Window, + EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, Styled, Subscription, + WeakEntity, Window, }; use smallvec::SmallVec; @@ -41,11 +41,11 @@ impl Panel for StackPanel { } } fn dump(&self, cx: &App) -> PanelState { - let sizes = self.panel_group.read(cx).sizes(); + let flexes = self.panel_group.read(cx).ratios(); let mut state = PanelState::new(self); for panel in &self.panels { state.add_child(panel.dump(cx)); - state.info = PanelInfo::stack(sizes.clone(), self.axis); + state.info = PanelInfo::stack(flexes.clone(), self.axis); } state @@ -111,19 +111,19 @@ impl StackPanel { pub fn add_panel( &mut self, panel: Arc, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { - self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx); + self.insert_panel(panel, self.panels.len(), ratio, dock_area, window, cx); } pub fn add_panel_at( &mut self, panel: Arc, placement: Placement, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, @@ -132,7 +132,7 @@ impl StackPanel { panel, self.panels_len(), placement, - size, + ratio, dock_area, window, cx, @@ -145,17 +145,17 @@ impl StackPanel { panel: Arc, ix: usize, placement: Placement, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { match placement { Placement::Top | Placement::Left => { - self.insert_panel_before(panel, ix, size, dock_area, window, cx) + self.insert_panel_before(panel, ix, ratio, dock_area, window, cx) } Placement::Right | Placement::Bottom => { - self.insert_panel_after(panel, ix, size, dock_area, window, cx) + self.insert_panel_after(panel, ix, ratio, dock_area, window, cx) } } } @@ -165,12 +165,12 @@ impl StackPanel { &mut self, panel: Arc, ix: usize, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { - self.insert_panel(panel, ix, size, dock_area, window, cx); + self.insert_panel(panel, ix, ratio, dock_area, window, cx); } /// Insert a panel after the index. @@ -178,26 +178,26 @@ impl StackPanel { &mut self, panel: Arc, ix: usize, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, ) { - self.insert_panel(panel, ix + 1, size, dock_area, window, cx); + self.insert_panel(panel, ix + 1, ratio, dock_area, window, cx); } - fn new_resizable_panel(panel: Arc, size: Option) -> ResizablePanel { + fn new_resizable_panel(panel: Arc, ratio: Option) -> ResizablePanel { resizable_panel() .content_view(panel.view()) .content_visible(move |_, cx| panel.visible(cx)) - .when_some(size, |this, size| this.size(size)) + .when_some(ratio, |this, ratio| this.ratio(ratio)) } fn insert_panel( &mut self, panel: Arc, ix: usize, - size: Option, + ratio: Option, dock_area: WeakEntity, window: &mut Window, cx: &mut Context, @@ -241,7 +241,7 @@ impl StackPanel { self.panels.insert(ix, panel.clone()); self.panel_group.update(cx, |view, cx| { view.insert_child( - Self::new_resizable_panel(panel.clone(), size), + Self::new_resizable_panel(panel.clone(), ratio), ix, window, cx, diff --git a/crates/ui/src/dock/state.rs b/crates/ui/src/dock/state.rs index e8259160..c4e6c17b 100644 --- a/crates/ui/src/dock/state.rs +++ b/crates/ui/src/dock/state.rs @@ -26,17 +26,22 @@ pub struct DockAreaState { pub struct DockState { panel: PanelState, placement: DockPlacement, - size: Pixels, + #[serde(default = "default_ratio")] + ratio: f32, open: bool, } +fn default_ratio() -> f32 { + 0.2 +} + impl DockState { pub fn new(dock: Entity, cx: &App) -> Self { let dock = dock.read(cx); Self { placement: dock.placement, - size: dock.size, + ratio: dock.ratio, open: dock.open, panel: dock.panel.view().dump(cx), } @@ -54,7 +59,7 @@ impl DockState { Dock::from_state( dock_area.clone(), self.placement, - self.size, + self.ratio, item, self.open, window, @@ -100,7 +105,8 @@ impl From> for TileMeta { pub enum PanelInfo { #[serde(rename = "stack")] Stack { - sizes: Vec, + #[serde(default)] + ratios: Vec, axis: usize, // 0 for horizontal, 1 for vertical }, #[serde(rename = "tabs")] @@ -112,9 +118,9 @@ pub enum PanelInfo { } impl PanelInfo { - pub fn stack(sizes: Vec, axis: Axis) -> Self { + pub fn stack(ratios: Vec, axis: Axis) -> Self { Self::Stack { - sizes, + ratios, axis: if axis == Axis::Horizontal { 0 } else { 1 }, } } @@ -142,9 +148,9 @@ impl PanelInfo { } } - pub fn sizes(&self) -> Option<&Vec> { + pub fn ratios(&self) -> Option<&Vec> { match self { - Self::Stack { sizes, .. } => Some(sizes), + Self::Stack { ratios, .. } => Some(ratios), _ => None, } } @@ -194,14 +200,14 @@ impl PanelState { .collect(); match info { - PanelInfo::Stack { sizes, axis } => { + PanelInfo::Stack { ratios, axis } => { let axis = if axis == 0 { Axis::Horizontal } else { Axis::Vertical }; - let sizes = sizes.iter().map(|s| Some(*s)).collect_vec(); - DockItem::split_with_sizes(axis, items, sizes, &dock_area, window, cx) + let ratios = ratios.iter().map(|&ratio| Some(ratio)).collect(); + DockItem::split_with_sizes(axis, items, ratios, &dock_area, window, cx) } PanelInfo::Tabs { active_index } => { if items.len() == 1 { @@ -239,8 +245,6 @@ impl PanelState { #[cfg(test)] mod tests { - use gpui::px; - use super::*; #[test] fn test_deserialize_item_state() { @@ -259,7 +263,7 @@ mod tests { let left_dock = state.left_dock.unwrap(); assert_eq!(left_dock.open, true); - assert_eq!(left_dock.size, px(350.0)); + assert_eq!(left_dock.ratio, 0.2); assert_eq!(left_dock.placement, DockPlacement::Left); assert_eq!(left_dock.panel.panel_name, "TabPanel"); assert_eq!(left_dock.panel.children.len(), 1); @@ -267,14 +271,14 @@ mod tests { let bottom_dock = state.bottom_dock.unwrap(); assert_eq!(bottom_dock.open, true); - assert_eq!(bottom_dock.size, px(200.0)); + assert_eq!(bottom_dock.ratio, 0.32); assert_eq!(bottom_dock.panel.panel_name, "TabPanel"); assert_eq!(bottom_dock.panel.children.len(), 2); assert_eq!(bottom_dock.panel.children[0].panel_name, "StoryContainer"); let right_dock = state.right_dock.unwrap(); assert_eq!(right_dock.open, true); - assert_eq!(right_dock.size, px(320.0)); + assert_eq!(right_dock.ratio, 0.2); assert_eq!(right_dock.panel.panel_name, "TabPanel"); assert_eq!(right_dock.panel.children.len(), 1); assert_eq!(right_dock.panel.children[0].panel_name, "StoryContainer"); diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 6ffca86c..bdcb2931 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use gpui::{ div, prelude::FluentBuilder, px, rems, App, AppContext, Context, Corner, DefiniteLength, DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, - InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, ScrollHandle, - SharedString, StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window, + InteractiveElement as _, IntoElement, ParentElement, Render, ScrollHandle, SharedString, + StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window, }; use rust_i18n::t; @@ -264,7 +264,7 @@ impl TabPanel { &mut self, panel: Arc, placement: Placement, - size: Option, + ratio: Option, window: &mut Window, cx: &mut Context, ) { @@ -272,7 +272,7 @@ impl TabPanel { cx.update(|window, cx| { view.update(cx, |view, cx| { view.will_split_placement = Some(placement); - view.split_panel(panel, placement, size, window, cx) + view.split_panel(panel, placement, ratio, window, cx) }) .ok() }) @@ -930,7 +930,7 @@ impl TabPanel { &self, panel: Arc, placement: Placement, - size: Option, + ratio: Option, window: &mut Window, cx: &mut Context, ) { @@ -959,7 +959,7 @@ impl TabPanel { Arc::new(new_tab_panel), ix, placement, - size, + ratio, dock_area.clone(), window, cx, @@ -971,7 +971,7 @@ impl TabPanel { Arc::new(new_tab_panel), ix, placement, - size, + ratio, dock_area.clone(), window, cx, @@ -1001,7 +1001,13 @@ impl TabPanel { new_stack_panel.update(cx, |view, cx| match placement { Placement::Left | Placement::Top => { - view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx); + view.add_panel( + Arc::new(new_tab_panel), + ratio, + dock_area.clone(), + window, + cx, + ); view.add_panel( Arc::new(tab_panel.clone()), None, @@ -1018,7 +1024,13 @@ impl TabPanel { window, cx, ); - view.add_panel(Arc::new(new_tab_panel), size, dock_area.clone(), window, cx); + view.add_panel( + Arc::new(new_tab_panel), + ratio, + dock_area.clone(), + window, + cx, + ); } }); diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index 4c35a07f..a2378226 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -2,7 +2,7 @@ use std::{ops::Deref, rc::Rc}; use gpui::{ canvas, div, prelude::FluentBuilder, px, relative, Along, AnyElement, AnyView, App, AppContext, - Axis, Bounds, Context, Element, Empty, Entity, EntityId, EventEmitter, IntoElement, IsZero, + Axis, Bounds, Context, Element, Empty, Entity, EntityId, EventEmitter, IntoElement, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, Style, Styled, WeakEntity, Window, }; @@ -28,9 +28,9 @@ impl Render for DragPanel { #[derive(Clone)] pub struct ResizablePanelGroup { panels: Vec>, - sizes: Vec, + ratios: Vec, axis: Axis, - size: Option, + ratio: Option, bounds: Bounds, resizing_panel_ix: Option, } @@ -39,16 +39,16 @@ impl ResizablePanelGroup { pub(super) fn new() -> Self { Self { axis: Axis::Horizontal, - sizes: Vec::new(), + ratios: Vec::new(), panels: Vec::new(), - size: None, + ratio: None, bounds: Bounds::default(), resizing_panel_ix: None, } } - pub fn load(&mut self, sizes: Vec, panels: Vec>) { - self.sizes = sizes; + pub fn load(&mut self, ratios: Vec, panels: Vec>) { + self.ratios = ratios; self.panels = panels; } @@ -72,10 +72,10 @@ impl ResizablePanelGroup { /// Add a ResizablePanelGroup as a child to the group. pub fn group(self, group: ResizablePanelGroup, cx: &mut Context) -> Self { let group: ResizablePanelGroup = group; - let size = group.size; + let ratio = group.ratio; let panel = ResizablePanel::new() .content_view(cx.new(|_| group).into()) - .when_some(size, |this, size| this.size(size)); + .when_some(ratio, |this, ratio| this.ratio(ratio)); self.child(panel, cx) } @@ -83,26 +83,31 @@ impl ResizablePanelGroup { /// /// - When the axis is horizontal, the size is the height of the group. /// - When the axis is vertical, the size is the width of the group. - pub fn size(mut self, size: Pixels) -> Self { - self.size = Some(size); + pub fn ratio(mut self, ratio: f32) -> Self { + self.ratio = Some(ratio); self } - /// Returns the sizes of the resizable panels. - pub(crate) fn sizes(&self) -> Vec { - self.sizes.clone() + /// Returns the ratios of the panels. + pub(crate) fn ratios(&self) -> &Vec { + &self.ratios } /// Calculates the sum of all panel sizes within the group. pub fn total_size(&self) -> Pixels { - self.sizes.iter().fold(px(0.0), |acc, &size| acc + size) + self.bounds.size.along(self.axis) } pub fn add_child(&mut self, panel: ResizablePanel, cx: &mut Context) { let mut panel = panel; panel.axis = self.axis; panel.group = Some(cx.entity().downgrade()); - self.sizes.push(panel.initial_size.unwrap_or_default()); + let ratio = match panel.ratio { + Some(ratio) => ratio, + None => panel.initial_radio.unwrap_or_default(), + }; + + self.ratios.push(ratio); self.panels.push(cx.new(|_| panel)); } @@ -116,9 +121,11 @@ impl ResizablePanelGroup { let mut panel = panel; panel.axis = self.axis; panel.group = Some(cx.entity().downgrade()); - - self.sizes - .insert(ix, panel.initial_size.unwrap_or_default()); + let ratio = match panel.ratio { + Some(ratio) => ratio, + None => panel.initial_radio.unwrap_or_default(), + }; + self.ratios.insert(ix, ratio); self.panels.insert(ix, cx.new(|_| panel)); cx.notify() } @@ -134,26 +141,32 @@ impl ResizablePanelGroup { let mut panel = panel; let old_panel = self.panels[ix].clone(); - let old_panel_initial_size = old_panel.read(cx).initial_size; - let old_panel_size_ratio = old_panel.read(cx).size_ratio; + let old_panel_initial_ratio = old_panel.read(cx).initial_radio; + let old_panel_ratio = old_panel.read(cx).ratio; - panel.initial_size = old_panel_initial_size; - panel.size_ratio = old_panel_size_ratio; + panel.initial_radio = old_panel_initial_ratio; + panel.ratio = old_panel_ratio; panel.axis = self.axis; panel.group = Some(cx.entity().downgrade()); - self.sizes[ix] = panel.initial_size.unwrap_or_default(); + + let ratio = match panel.ratio { + Some(ratio) => ratio, + None => panel.initial_radio.unwrap_or_default(), + }; + + self.ratios[ix] = ratio; self.panels[ix] = cx.new(|_| panel); cx.notify() } pub fn remove_child(&mut self, ix: usize, _: &mut Window, cx: &mut Context) { - self.sizes.remove(ix); + self.ratios.remove(ix); self.panels.remove(ix); cx.notify() } pub(crate) fn remove_all_children(&mut self, _: &mut Window, cx: &mut Context) { - self.sizes.clear(); + self.ratios.clear(); self.panels.clear(); cx.notify() } @@ -183,9 +196,10 @@ impl ResizablePanelGroup { self.resizing_panel_ix = None; } - fn sync_real_panel_sizes(&mut self, _: &Window, cx: &App) { + fn sync_real_panel_sizes(&mut self, _: &Window, cx: &mut App) { + let total_size = self.total_size(); for (i, panel) in self.panels.iter().enumerate() { - self.sizes[i] = panel.read(cx).bounds.size.along(self.axis) + self.ratios[i] = panel.read(cx).bounds.size.along(self.axis) / total_size; } } @@ -208,53 +222,54 @@ impl ResizablePanelGroup { self.sync_real_panel_sizes(window, cx); - let mut changed = size - self.sizes[ix]; - let is_expand = changed > px(0.); + let new_ratio = size / container_size; + let mut changed = new_ratio - self.ratios[ix]; + let is_expand = changed > 0.; let main_ix = ix; - let mut new_sizes = self.sizes.clone(); + let mut new_ratios = self.ratios.clone(); + let min_ratio = PANEL_MIN_SIZE / container_size; if is_expand { - new_sizes[ix] = size; + new_ratios[ix] = new_ratio; // Now to expand logic is correct. - while changed > px(0.) && ix < self.panels.len() - 1 { + while changed > 0. && ix < self.panels.len() - 1 { ix += 1; - let available_size = (new_sizes[ix] - PANEL_MIN_SIZE).max(px(0.)); - let to_reduce = changed.min(available_size); - new_sizes[ix] -= to_reduce; + let available_ratio = (new_ratios[ix] - min_ratio).max(0.); + let to_reduce = changed.min(available_ratio); + new_ratios[ix] -= to_reduce; changed -= to_reduce; } } else { - let new_size = size.max(PANEL_MIN_SIZE); - new_sizes[ix] = new_size; - changed = size - PANEL_MIN_SIZE; - new_sizes[ix + 1] += self.sizes[ix] - new_size; + let new_size = new_ratio.max(min_ratio); + new_ratios[ix] = new_size; + changed = new_ratio - min_ratio; + new_ratios[ix + 1] += self.ratios[ix] - new_size; - while changed < px(0.) && ix > 0 { + while changed < 0. && ix > 0 { ix -= 1; - let available_size = self.sizes[ix] - PANEL_MIN_SIZE; - let to_increase = (changed).min(available_size); - new_sizes[ix] += to_increase; + let available_ratio = self.ratios[ix] - min_ratio; + let to_increase = (changed).min(available_ratio); + new_ratios[ix] += to_increase; changed += to_increase; } } // If total size exceeds container size, adjust the main panel - let total_size: Pixels = new_sizes.iter().map(|s| s.0).sum::().into(); - if total_size > container_size { - let overflow = total_size - container_size; - new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(PANEL_MIN_SIZE); + let total_ratio: f32 = new_ratios.iter().sum(); + if total_ratio > 1. { + let overflow = 1.0 - total_ratio; + new_ratios[main_ix] = (new_ratios[main_ix] - overflow).max(min_ratio); } - let total_size = new_sizes.iter().fold(px(0.0), |acc, &size| acc + size); - self.sizes = new_sizes; + self.ratios = new_ratios; for (i, panel) in self.panels.iter().enumerate() { - let size = self.sizes[i]; - if size > px(0.) { + let ratio = self.ratios[i]; + if ratio > 0. { panel.update(cx, |this, _| { - this.size = Some(size); - this.size_ratio = Some(size / total_size); + this.size = Some(container_size * ratio); + this.ratio = Some(ratio); }); } } @@ -300,11 +315,11 @@ impl Render for ResizablePanelGroup { pub struct ResizablePanel { group: Option>, /// Initial size is the size that the panel has when it is created. - initial_size: Option, + initial_radio: Option, /// size is the size that the panel has when it is resized or adjusted by flex layout. size: Option, /// the size ratio that the panel has relative to its group - size_ratio: Option, + ratio: Option, axis: Axis, content_builder: Option AnyElement>>, content_view: Option, @@ -318,9 +333,9 @@ impl ResizablePanel { pub(super) fn new() -> Self { Self { group: None, - initial_size: None, + initial_radio: None, size: None, - size_ratio: None, + ratio: None, axis: Axis::Horizontal, content_builder: None, content_view: None, @@ -351,28 +366,36 @@ impl ResizablePanel { self } - /// Set the initial size of the panel. - pub fn size(mut self, size: Pixels) -> Self { - self.initial_size = Some(size); + // /// Set the initial size of the panel. + // pub fn size(mut self, size: Pixels) -> Self { + // self.initial_size = Some(size); + // self + // } + + /// Set the flex ratio of the panel, the ratio is relative to the total size of the group. + /// + /// The `ratio` is 0.0 to 1.0. + pub fn ratio(mut self, ratio: f32) -> Self { + self.initial_radio = Some(ratio); + self.ratio = Some(ratio); self } /// Save the real panel size, and update group sizes - fn update_size(&mut self, bounds: Bounds, _: &mut Window, cx: &mut Context) { + fn update_size(&mut self, bounds: Bounds, window: &mut Window, cx: &mut Context) { let new_size = bounds.size.along(self.axis); self.bounds = bounds; - self.size_ratio = None; + self.ratio = None; self.size = Some(new_size); + cx.notify(); - let entity_id = cx.entity().entity_id(); - if let Some(group) = self.group.as_ref() { - _ = group.update(cx, |view, _| { - if let Some(ix) = view.panels.iter().position(|v| v.entity_id() == entity_id) { - view.sizes[ix] = new_size; - } + if let Some(group) = self.group.clone() { + window.defer(cx, move |window, cx| { + _ = group.update(cx, |view, cx| { + view.sync_real_panel_sizes(window, cx); + }); }); } - cx.notify(); } } @@ -382,7 +405,7 @@ impl Render for ResizablePanel { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { if !(self.content_visible)(window, cx) { // To keep size as initial size, to make sure the size will not be changed. - self.initial_size = self.size; + self.initial_radio = self.ratio; self.size = None; return div(); } @@ -399,23 +422,23 @@ impl Render for ResizablePanel { .flex_grow() .size_full() .relative() - .when(self.initial_size.is_none(), |this| this.flex_shrink()) + .when(self.initial_radio.is_none(), |this| this.flex_shrink()) .when(self.axis.is_vertical(), |this| this.min_h(PANEL_MIN_SIZE)) .when(self.axis.is_horizontal(), |this| this.min_w(PANEL_MIN_SIZE)) - .when_some(self.initial_size, |this, size| { - if size.is_zero() { + .when_some(self.initial_radio, |this, radio| { + if radio == 0. { this } else { // The `self.size` is None, that mean the initial size for the panel, so we need set flex_shrink_0 // To let it keep the initial size. - this.when(self.size.is_none() && size > px(0.), |this| { + this.when(self.size.is_none() && radio > 0., |this| { this.flex_shrink_0() }) - .flex_basis(size) + .flex_basis(relative(radio)) } }) - .map(|this| match (self.size_ratio, self.size, total_size) { - (Some(size_ratio), _, _) => this.flex_basis(relative(size_ratio)), + .map(|this| match (self.ratio, self.size, total_size) { + (Some(ratio), _, _) => this.flex_basis(relative(ratio)), (None, Some(size), Some(total_size)) => { this.flex_basis(relative(size / total_size)) } diff --git a/crates/ui/tests/fixtures/layout.json b/crates/ui/tests/fixtures/layout.json index f1205509..a8224510 100644 --- a/crates/ui/tests/fixtures/layout.json +++ b/crates/ui/tests/fixtures/layout.json @@ -255,6 +255,7 @@ }, "placement": "bottom", "size": 200.0, + "ratio": 0.32, "open": true, "resizeable": true }