Preserve panel size when re-docking between left and right

This commit is contained in:
Nathan Sobo 2023-05-12 15:15:11 -06:00
parent 214354b4da
commit bd795d7607
6 changed files with 117 additions and 41 deletions

View file

@ -196,12 +196,7 @@ pub trait Element<V: View>: 'static {
where where
Self: 'static + Sized, Self: 'static + Sized,
{ {
Resizable::new( Resizable::new(self.into_any(), side, size, on_resize)
self.into_any(),
side,
size,
on_resize
)
} }
} }

View file

@ -7,8 +7,8 @@ use crate::{
geometry::rect::RectF, geometry::rect::RectF,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
scene::MouseDrag, scene::MouseDrag,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, View, AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext, SizeConstraint, ViewContext,
}; };
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -73,7 +73,7 @@ pub struct Resizable<V: View> {
child: AnyElement<V>, child: AnyElement<V>,
handle_side: HandleSide, handle_side: HandleSide,
handle_size: f32, handle_size: f32,
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>> on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>,
} }
const DEFAULT_HANDLE_SIZE: f32 = 4.0; const DEFAULT_HANDLE_SIZE: f32 = 4.0;
@ -83,7 +83,7 @@ impl<V: View> Resizable<V> {
child: AnyElement<V>, child: AnyElement<V>,
handle_side: HandleSide, handle_side: HandleSide,
size: f32, size: f32,
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>) on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
) -> Self { ) -> Self {
let child = match handle_side.axis() { let child = match handle_side.axis() {
Axis::Horizontal => child.constrained().with_max_width(size), Axis::Horizontal => child.constrained().with_max_width(size),
@ -133,22 +133,29 @@ impl<V: View> Element<V> for Resizable<V> {
enum ResizeHandle {} enum ResizeHandle {}
scene.push_mouse_region( scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.handle_side as usize, handle_region) MouseRegion::new::<ResizeHandle>(
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere cx.view_id(),
.on_drag(MouseButton::Left, { self.handle_side as usize,
let bounds = bounds.clone(); handle_region,
let side = self.handle_side; )
let prev_size = side.relevant_component(bounds.size()); .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
let min_size = side.relevant_component(constraint.min); .on_drag(MouseButton::Left, {
let max_size = side.relevant_component(constraint.max); let bounds = bounds.clone();
let on_resize = self.on_resize.clone(); let side = self.handle_side;
move |event, view: &mut V, cx| { let prev_size = side.relevant_component(bounds.size());
let new_size = min_size.max(prev_size + side.compute_delta(event)).min(max_size).round(); let min_size = side.relevant_component(constraint.min);
if new_size != prev_size { let max_size = side.relevant_component(constraint.max);
on_resize.borrow_mut()(view, new_size, cx); let on_resize = self.on_resize.clone();
} move |event, view: &mut V, cx| {
let new_size = min_size
.max(prev_size + side.compute_delta(event))
.min(max_size)
.round();
if new_size != prev_size {
on_resize.borrow_mut()(view, new_size, cx);
} }
}), }
}),
); );
scene.push_cursor_region(crate::CursorRegion { scene.push_cursor_region(crate::CursorRegion {

View file

@ -12,8 +12,8 @@ use gpui::{
geometry::vector::Vector2F, geometry::vector::Vector2F,
keymap_matcher::KeymapContext, keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel}, platform::{CursorStyle, MouseButton, PromptLevel},
AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, AnyElement, AppContext, Axis, ClipboardItem, Element, Entity, ModelHandle, Task, View,
ViewHandle, WeakViewHandle, ViewContext, ViewHandle, WeakViewHandle,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@ -1347,9 +1347,7 @@ impl Entity for ProjectPanel {
impl workspace::dock::Panel for ProjectPanel { impl workspace::dock::Panel for ProjectPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition { fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
match settings match settings.project_panel.dock {
.project_panel
.dock {
settings::ProjectPanelDockPosition::Left => DockPosition::Left, settings::ProjectPanelDockPosition::Left => DockPosition::Left,
settings::ProjectPanelDockPosition::Right => DockPosition::Right, settings::ProjectPanelDockPosition::Right => DockPosition::Right,
} }

View file

@ -599,7 +599,10 @@ impl Settings {
} }
self.editor_overrides = data.editor; self.editor_overrides = data.editor;
merge(&mut self.project_panel.dock, data.project_panel.dock); merge(&mut self.project_panel.dock, data.project_panel.dock);
merge(&mut self.project_panel.default_width, data.project_panel.default_width); merge(
&mut self.project_panel.default_width,
data.project_panel.default_width,
);
self.git_overrides = data.git.unwrap_or_default(); self.git_overrides = data.git.unwrap_or_default();
self.journal_overrides = data.journal; self.journal_overrides = data.journal;
self.terminal_defaults.font_size = data.terminal.font_size; self.terminal_defaults.font_size = data.terminal.font_size;

View file

@ -2,7 +2,8 @@ use crate::{StatusItemView, Workspace};
use context_menu::{ContextMenu, ContextMenuItem}; use context_menu::{ContextMenu, ContextMenuItem};
use gpui::{ use gpui::{
elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
WindowContext,
}; };
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
@ -117,6 +118,13 @@ impl DockPosition {
Self::Right => HandleSide::Left, Self::Right => HandleSide::Left,
} }
} }
pub fn axis(&self) -> Axis {
match self {
Self::Left | Self::Right => Axis::Horizontal,
Self::Bottom => Axis::Vertical,
}
}
} }
struct PanelEntry { struct PanelEntry {
@ -247,6 +255,23 @@ impl Dock {
} }
} }
pub fn panel_size(&self, panel: &dyn PanelHandle) -> Option<f32> {
self.panel_entries
.iter()
.find(|entry| entry.panel.id() == panel.id())
.map(|entry| entry.size)
}
pub fn resize_panel(&mut self, panel: &dyn PanelHandle, size: f32) {
let entry = self
.panel_entries
.iter_mut()
.find(|entry| entry.panel.id() == panel.id());
if let Some(entry) = entry {
entry.size = size;
}
}
pub fn active_panel_size(&self) -> Option<f32> { pub fn active_panel_size(&self) -> Option<f32> {
if self.is_open { if self.is_open {
self.panel_entries self.panel_entries
@ -281,9 +306,13 @@ impl View for Dock {
ChildView::new(active_panel.as_any(), cx) ChildView::new(active_panel.as_any(), cx)
.contained() .contained()
.with_style(style.container) .with_style(style.container)
.resizable(self.position.to_resize_handle_side(), size, |dock: &mut Self, size, cx| { .resizable(
dock.resize_active_panel(size, cx); self.position.to_resize_handle_side(),
}) size,
|dock: &mut Self, size, cx| {
dock.resize_active_panel(size, cx);
},
)
.into_any() .into_any()
} else { } else {
Empty::new().into_any() Empty::new().into_any()
@ -487,7 +516,10 @@ pub(crate) mod test {
} }
fn default_size(&self, _: &WindowContext) -> f32 { fn default_size(&self, _: &WindowContext) -> f32 {
300. match self.position.axis() {
Axis::Horizontal => 300.,
Axis::Vertical => 200.,
}
} }
fn icon_path(&self) -> &'static str { fn icon_path(&self) -> &'static str {

View file

@ -852,10 +852,18 @@ impl Workspace {
cx.subscribe(&panel, { cx.subscribe(&panel, {
let mut dock = dock.clone(); let mut dock = dock.clone();
let mut prev_position = panel.position(cx);
move |this, panel, event, cx| { move |this, panel, event, cx| {
if T::should_change_position_on_event(event) { if T::should_change_position_on_event(event) {
let new_position = panel.read(cx).position(cx);
let mut was_visible = false; let mut was_visible = false;
let mut size = None;
dock.update(cx, |dock, cx| { dock.update(cx, |dock, cx| {
if new_position.axis() == prev_position.axis() {
size = dock.panel_size(&panel);
}
prev_position = new_position;
was_visible = dock.is_open() was_visible = dock.is_open()
&& dock && dock
.active_panel() .active_panel()
@ -869,7 +877,11 @@ impl Workspace {
} }
.clone(); .clone();
dock.update(cx, |dock, cx| { dock.update(cx, |dock, cx| {
dock.add_panel(panel, cx); dock.add_panel(panel.clone(), cx);
if let Some(size) = size {
dock.resize_panel(&panel, size);
}
if was_visible { if was_visible {
dock.set_open(true, cx); dock.set_open(true, cx);
dock.activate_panel(dock.panels_len() - 1, cx); dock.activate_panel(dock.panels_len() - 1, cx);
@ -3678,10 +3690,17 @@ mod tests {
.right_dock() .right_dock()
.update(cx, |right_dock, cx| right_dock.set_open(true, cx)); .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
let left_dock = workspace.left_dock();
assert_eq!( assert_eq!(
workspace.left_dock().read(cx).active_panel().unwrap().id(), left_dock.read(cx).active_panel().unwrap().id(),
panel_1.id() panel_1.id()
); );
assert_eq!(
left_dock.read(cx).active_panel_size().unwrap(),
panel_1.default_size(cx)
);
left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
assert_eq!( assert_eq!(
workspace.right_dock().read(cx).active_panel().unwrap().id(), workspace.right_dock().read(cx).active_panel().unwrap().id(),
panel_2.id() panel_2.id()
@ -3700,10 +3719,12 @@ mod tests {
// Since it was the only panel on the left, the left dock should now be closed. // Since it was the only panel on the left, the left dock should now be closed.
assert!(!workspace.left_dock().read(cx).is_open()); assert!(!workspace.left_dock().read(cx).is_open());
assert!(workspace.left_dock().read(cx).active_panel().is_none()); assert!(workspace.left_dock().read(cx).active_panel().is_none());
let right_dock = workspace.right_dock();
assert_eq!( assert_eq!(
workspace.right_dock().read(cx).active_panel().unwrap().id(), right_dock.read(cx).active_panel().unwrap().id(),
panel_1.id() panel_1.id()
); );
assert_eq!(right_dock.read(cx).active_panel_size().unwrap(), 1337.);
// Now we move panel_2 to the left // Now we move panel_2 to the left
panel_2.set_position(DockPosition::Left, cx); panel_2.set_position(DockPosition::Left, cx);
@ -3727,13 +3748,33 @@ mod tests {
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
// Since panel_1 was visible on the right, we open the left dock and make panel_1 active. // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
assert!(workspace.left_dock().read(cx).is_open()); let left_dock = workspace.left_dock();
assert!(left_dock.read(cx).is_open());
assert_eq!( assert_eq!(
workspace.left_dock().read(cx).active_panel().unwrap().id(), left_dock.read(cx).active_panel().unwrap().id(),
panel_1.id() panel_1.id()
); );
assert_eq!(left_dock.read(cx).active_panel_size().unwrap(), 1337.);
// And right the dock should be closed as it no longer has any panels. // And right the dock should be closed as it no longer has any panels.
assert!(!workspace.right_dock().read(cx).is_open()); assert!(!workspace.right_dock().read(cx).is_open());
// Now we move panel_1 to the bottom
panel_1.set_position(DockPosition::Bottom, cx);
});
workspace.update(cx, |workspace, cx| {
// Since panel_1 was visible on the left, we close the left dock.
assert!(!workspace.left_dock().read(cx).is_open());
// The bottom dock is sized based on the panel's default size,
// since the panel orientation changed from vertical to horizontal.
assert_eq!(
workspace
.bottom_dock()
.read(cx)
.active_panel_size()
.unwrap(),
panel_1.default_size(cx),
);
}); });
} }
} }