mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-27 12:54:42 +00:00
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:
parent
31a3fdb23e
commit
df59b28aaf
8 changed files with 120 additions and 56 deletions
|
@ -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(
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue