From df59b28aaf216eaed46bf2fbe790914aefd050b8 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 12 Sep 2022 18:32:15 -0700 Subject: [PATCH] Extract panel resize code from sidebar into Resizable element Make resizable work in vertical axis Make dock resizable Have dock preserve size based on Anchor position Make pane buttons work more correctly in pathological cases Sync status bar dock button with dock visibility/anchor position Co-Authored-By Mikayla Maki --- crates/gpui/src/elements/resizable.rs | 53 ++++++++++++++------------- crates/settings/src/settings.rs | 2 +- crates/theme/src/theme.rs | 8 +++- crates/workspace/src/dock.rs | 52 ++++++++++++++++++++------ crates/workspace/src/pane.rs | 11 ++++-- crates/workspace/src/sidebar.rs | 6 ++- crates/workspace/src/workspace.rs | 29 +++++++++++++-- styles/src/styleTree/workspace.ts | 15 +++++--- 8 files changed, 120 insertions(+), 56 deletions(-) diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 814353ce85fd8c44b95dbccad746a960cd8238ba..802452790300cf94f41b39f4e103382c25a3ea86 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -8,7 +8,7 @@ use crate::{ ElementStateHandle, MouseButton, MouseRegion, RenderContext, View, }; -use super::{ConstrainedBox, Flex, Hook, ParentElement}; +use super::{ConstrainedBox, Hook}; #[derive(Copy, Clone, Debug)] pub enum Side { @@ -53,8 +53,12 @@ impl Side { fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { match self { Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), - Side::Bottom => RectF::new(bounds.lower_left(), vec2f(bounds.width(), handle_size)), Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())), + Side::Bottom => { + let mut origin = bounds.lower_left(); + origin.set_y(origin.y() - handle_size); + RectF::new(origin, vec2f(bounds.width(), handle_size)) + } Side::Right => { let mut origin = bounds.upper_right(); origin.set_x(origin.x() - handle_size); @@ -96,27 +100,21 @@ impl Resizable { let state = state_handle.read(cx).clone(); - let mut flex = Flex::new(side.axis()); - - flex.add_child( - Hook::new({ - let constrained = ConstrainedBox::new(child); - match side.axis() { - Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()), - Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()), - } - .boxed() - }) - .on_after_layout({ - let state = state.clone(); - move |size, _| { - state.actual_dimension.set(side.relevant_component(size)); - } - }) - .boxed(), - ); - - let child = flex.boxed(); + let child = Hook::new({ + let constrained = ConstrainedBox::new(child); + match side.axis() { + Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()), + Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()), + } + .boxed() + }) + .on_after_layout({ + let state = state.clone(); + move |size, _| { + state.actual_dimension.set(side.relevant_component(size)); + } + }) + .boxed(); Self { side, @@ -126,10 +124,14 @@ impl Resizable { _state_handle: state_handle, } } + + pub fn current_size(&self) -> f32 { + self.state.actual_dimension.get() + } } impl Element for Resizable { - type LayoutState = Vector2F; + type LayoutState = (); type PaintState = (); fn layout( @@ -137,8 +139,7 @@ impl Element for Resizable { constraint: crate::SizeConstraint, cx: &mut crate::LayoutContext, ) -> (Vector2F, Self::LayoutState) { - let child_size = self.child.layout(constraint, cx); - (child_size, child_size) + (self.child.layout(constraint, cx), ()) } fn paint( diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index fa5fa9d2a91fcf05fbd64992edee550fd7691ea9..896dfb3a09ae9e2624350060d41f056c4027d38f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -151,7 +151,7 @@ pub enum WorkingDirectory { Always { directory: String }, } -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Deserialize, JsonSchema)] +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum DockAnchor { #[default] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3bf643ec24811c99d213bc447be294d7ec5518c5..259b9a690ceaef18ab2bbc8fc153ed09cada893e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -48,7 +48,7 @@ pub struct Workspace { pub pane_divider: Border, pub leader_border_opacity: f32, pub leader_border_width: f32, - pub sidebar_resize_handle: ContainerStyle, + pub sidebar: Sidebar, pub status_bar: StatusBar, pub toolbar: Toolbar, pub disconnected_overlay: ContainedText, @@ -152,6 +152,8 @@ pub struct Toolbar { #[derive(Clone, Deserialize, Default)] pub struct Dock { + pub initial_size_right: f32, + pub initial_size_bottom: f32, pub wash_color: Color, pub flex: f32, pub panel: ContainerStyle, @@ -240,7 +242,9 @@ pub struct StatusBarLspStatus { #[derive(Deserialize, Default)] pub struct Sidebar { - pub resize_handle: ContainerStyle, + pub initial_size: f32, + #[serde(flatten)] + pub container: ContainerStyle, } #[derive(Clone, Copy, Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index aa49d24b4795e873f54525df9ddd6f8648c0e2a3..b0ac47cc90b73d1876472c9b591d2337a1314a7c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,3 +1,4 @@ +use collections::HashMap; use gpui::{ actions, elements::{ChildView, Container, Empty, Margin, MouseEventHandler, Side, Svg}, @@ -8,7 +9,7 @@ use serde::Deserialize; use settings::{DockAnchor, Settings}; use theme::Theme; -use crate::{ItemHandle, Pane, StatusItemView, Workspace}; +use crate::{sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace}; #[derive(PartialEq, Clone, Deserialize)] pub struct MoveDock(pub DockAnchor); @@ -78,6 +79,7 @@ pub type DefaultItemFactory = pub struct Dock { position: DockPosition, + panel_sizes: HashMap, pane: ViewHandle, default_item_factory: DefaultItemFactory, } @@ -94,6 +96,7 @@ impl Dock { Self { pane, + panel_sizes: Default::default(), position: DockPosition::Hidden(anchor), default_item_factory, } @@ -107,6 +110,10 @@ impl Dock { self.position.is_visible().then(|| self.pane()) } + pub fn is_anchored_at(&self, anchor: DockAnchor) -> bool { + self.position.is_visible() && self.position.anchor() == anchor + } + fn set_dock_position( workspace: &mut Workspace, new_position: DockPosition, @@ -118,8 +125,14 @@ impl Dock { pane.set_docked(Some(new_position.anchor()), cx) }); - let now_visible = workspace.dock.visible_pane().is_some(); - if now_visible { + if workspace.dock.position.is_visible() { + // Close the right sidebar if the dock is on the right side and the right sidebar is open + if workspace.dock.position.anchor() == DockAnchor::Right { + if workspace.right_sidebar().read(cx).is_open() { + workspace.toggle_sidebar(SidebarSide::Right, cx); + } + } + // Ensure that the pane has at least one item or construct a default item to put in it let pane = workspace.dock.pane.clone(); if pane.read(cx).items().next().is_none() { @@ -127,10 +140,8 @@ impl Dock { Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); } cx.focus(pane); - } else { - if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() { - cx.focus(last_active_center_pane); - } + } else if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() { + cx.focus(last_active_center_pane); } cx.emit(crate::Event::DockAnchorChanged); cx.notify(); @@ -182,11 +193,28 @@ impl Dock { }; enum DockResizeHandle {} - Container::new(ChildView::new(self.pane.clone()).boxed()) - .with_style(style.panel) - .with_resize_handle::(0, resize_side, 4., 200., cx) - .flex(style.flex, false) - .boxed() + + let resizable = Container::new(ChildView::new(self.pane.clone()).boxed()) + .with_style(panel_style) + .with_resize_handle::( + resize_side as usize, + resize_side, + 4., + self.panel_sizes.get(&anchor).copied().unwrap_or(200.), + cx, + ); + + let size = resizable.current_size(); + let workspace = cx.handle(); + cx.defer(move |cx| { + if let Some(workspace) = workspace.upgrade(cx) { + workspace.update(cx, |workspace, _| { + workspace.dock.panel_sizes.insert(anchor, size); + }) + } + }); + + resizable.flex(style.flex, false).boxed() } DockAnchor::Expanded => Container::new( MouseEventHandler::::new(0, cx, |_state, _cx| { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 97cdfaca7d4cc410712eb811dfe494d1155cafc6..a4937cbd970f46f2bf5ca30f3dc7f803c455312e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1367,7 +1367,7 @@ impl View for Pane { Flex::column() .with_child({ let mut tab_row = Flex::row() - .with_child(self.render_tabs(cx).flex(1., true).named("tabs")); + .with_child(self.render_tabs(cx).flex(1.0, true).named("tabs")); // Render pane buttons let theme = cx.global::().theme.clone(); @@ -1389,7 +1389,7 @@ impl View for Pane { icon_for_dock_anchor(anchor); tab_bar_button( - 2, + 1, dock_icon, cx, |position| DeployDockMenu { position }, @@ -1398,7 +1398,7 @@ impl View for Pane { .unwrap_or_else(|| { // Add the split menu if this pane is not a dock tab_bar_button( - 1, + 2, "icons/split_12.svg", cx, |position| DeployNewMenu { position }, @@ -1413,6 +1413,7 @@ impl View for Pane { })) .contained() .with_style(theme.workspace.tab_bar.container) + .flex(1., false) .boxed(), ) } @@ -1422,6 +1423,7 @@ impl View for Pane { .with_height(theme.workspace.tab_bar.height) .contained() .with_style(theme.workspace.tab_bar.container) + .flex(1., false) .named("tab bar") }) .with_child(ChildView::new(&self.toolbar).boxed()) @@ -1506,13 +1508,14 @@ fn tab_bar_button( .constrained() .with_width(style.button_width) .with_height(style.button_width) - .aligned() + // .aligned() .boxed() }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |e, cx| { cx.dispatch_action(action_builder(e.region.lower_right())); }) + .flex(1., false) .boxed() } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 89a750964304dd42f9686ac56087cf8fbb5be520..804d67027398317a9529d132ae1d826ce600d161 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -189,13 +189,15 @@ impl View for Sidebar { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} + let style = &cx.global::().theme.workspace.sidebar; ChildView::new(active_item.to_any()) + .contained() + .with_style(style.container) .with_resize_handle::( self.sidebar_side as usize, self.sidebar_side.to_resizable_side(), - // TODO: Expose both of these constants in the theme 4., - 260., + style.initial_size, cx, ) .boxed() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ec48b29f899e740c06a34ff77bf4ccfa6cf8c98d..0f4580947c5bb5691d7a0893c67acb894ba859e5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1479,11 +1479,17 @@ impl Workspace { let sidebar = match sidebar_side { SidebarSide::Left => &mut self.left_sidebar, SidebarSide::Right => &mut self.right_sidebar, - // Side::Top | Side::Bottom => unreachable!(), }; - sidebar.update(cx, |sidebar, cx| { - sidebar.set_open(!sidebar.is_open(), cx); + let open = sidebar.update(cx, |sidebar, cx| { + let open = !sidebar.is_open(); + sidebar.set_open(open, cx); + open }); + if open && sidebar_side == SidebarSide::Right && self.dock.is_anchored_at(DockAnchor::Right) + { + Dock::hide(self, cx); + } + cx.focus_self(); cx.notify(); } @@ -1493,7 +1499,7 @@ impl Workspace { SidebarSide::Left => &mut self.left_sidebar, SidebarSide::Right => &mut self.right_sidebar, }; - let active_item = sidebar.update(cx, |sidebar, cx| { + let active_item = sidebar.update(cx, move |sidebar, cx| { if sidebar.is_open() && sidebar.active_item_ix() == action.item_index { sidebar.set_open(false, cx); None @@ -1503,7 +1509,16 @@ impl Workspace { sidebar.active_item().cloned() } }); + if let Some(active_item) = active_item { + // If there is an active item, that means the sidebar was opened, + // which means we need to check if the dock is open and close it + if action.sidebar_side == SidebarSide::Right + && self.dock.is_anchored_at(DockAnchor::Right) + { + Dock::hide(self, cx); + } + if active_item.is_focused(cx) { cx.focus_self(); } else { @@ -1531,6 +1546,12 @@ impl Workspace { sidebar.active_item().cloned() }); if let Some(active_item) = active_item { + // If there is an active item, that means the sidebar was opened, + // which means we need to check if the dock is open and close it + if sidebar_side == SidebarSide::Right && self.dock.is_anchored_at(DockAnchor::Right) { + Dock::hide(self, cx); + } + if active_item.is_focused(cx) { cx.focus_self(); } else { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index b775578e3a324dfcab59f9f664544931c2a7daac..a32334be634b57ceaf555e0bc25df0cbd236d35e 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -37,11 +37,14 @@ export default function workspace(theme: Theme) { }, cursor: "Arrow", }, - sidebarResizeHandle: { - background: border(theme, "primary").color, - padding: { - left: 1, - }, + sidebar: { + initialSize: 240, + border: { + color: border(theme, "primary").color, + width: 1, + left: true, + right: true, + } }, paneDivider: { color: border(theme, "secondary").color, @@ -157,6 +160,8 @@ export default function workspace(theme: Theme) { margin: { right: 10, bottom: 10 }, }, dock: { + initialSizeRight: 240, + initialSizeBottom: 360, wash_color: withOpacity(theme.backgroundColor[500].base, 0.5), flex: 0.5, panel: {