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 <mikayla@zed.dev>
This commit is contained in:
K Simmons 2022-09-12 18:32:15 -07:00
parent 31a3fdb23e
commit df59b28aaf
8 changed files with 120 additions and 56 deletions

View file

@ -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(

View file

@ -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]

View file

@ -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)]

View file

@ -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<DockAnchor, f32>,
pane: ViewHandle<Pane>,
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::<DockResizeHandle, _>(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::<DockResizeHandle, _>(
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::<Dock>::new(0, cx, |_state, _cx| {

View file

@ -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::<Settings>().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<A: Action>(
.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()
}

View file

@ -189,13 +189,15 @@ impl View for Sidebar {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
if let Some(active_item) = self.active_item() {
enum ResizeHandleTag {}
let style = &cx.global::<Settings>().theme.workspace.sidebar;
ChildView::new(active_item.to_any())
.contained()
.with_style(style.container)
.with_resize_handle::<ResizeHandleTag, _>(
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()

View file

@ -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 {

View file

@ -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: {