diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 814353ce85..8024527903 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 fa5fa9d2a9..896dfb3a09 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 3bf643ec24..259b9a690c 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 aa49d24b47..b0ac47cc90 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 97cdfaca7d..a4937cbd97 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 89a7509643..804d670273 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 ec48b29f89..0f4580947c 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 b775578e3a..a32334be63 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: {