Fix usability issues with new panel system. (#2544)

This PR updates the dock key bindings according to the following model:

There are three bits: 
Visible: Opened / closed.
Focus: Panel focused / center focused.
Zoom: Zoomed / Not zoomed.

Each of these variables is 'sticky' in that they won't effect each other
unless they need to. 'Zooming' a panel conceptually merges the visible
and focus bits.

cmd-shift-j/b/r have all been removed.

cmd-j/b/r have been updated to mean 'toggle visibility of a certain
dock', firing them should *always* reveal the panel to you (where you
last left it), or hide it, without moving focus (unless the focused
element is invisible). This means that, when the terminal panel is
zoomed, cmd-j has the same effect as ctrl-`

ctrl-` and cmd-shift-e now toggle a panel's focus, without updating the
zoom state of a panel. Toggling the focus of a zoomed panel causes it to
automatically hide itself, without losing the zoom bit.

When focused or made visible, panels which cannot be zoomed
automatically unzoom everything else so as to preserve user intent of
'show me this panel' and 'everything stays where it is if I don't take
an action'

Release Notes:

- cmd-shift-j/b/r have been removed.  (preview only)
- cmd-j/b/r unconditionally show or hide their associated dock,
respecting zoom settings. (preview only)
- ctrl-` and cmd-shift-e now retain zoom state. (preview only)
- Fixed a bug where terminal dock tab would always be in the active
state (preview only)
- Fixed a bug where terminals would not always open in the terminal
panel
- Changed the look of zoomed panels to fill more of the screen (preview
only)
This commit is contained in:
Mikayla Maki 2023-05-30 16:39:06 -07:00 committed by Mikayla Maki
parent 8a3a0245e0
commit fc0bfd75ad
No known key found for this signature in database
11 changed files with 396 additions and 198 deletions

View file

@ -373,30 +373,9 @@
"workspace::ActivatePane",
8
],
"cmd-b": [
"workspace::ToggleLeftDock",
{ "focus": true }
],
"cmd-shift-b": [
"workspace::ToggleLeftDock",
{ "focus": false }
],
"cmd-r": [
"workspace::ToggleRightDock",
{ "focus": true }
],
"cmd-shift-r": [
"workspace::ToggleRightDock",
{ "focus": false }
],
"cmd-j": [
"workspace::ToggleBottomDock",
{ "focus": true }
],
"cmd-shift-j": [
"workspace::ToggleBottomDock",
{ "focus": false }
],
"cmd-b": "workspace::ToggleLeftDock",
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
"cmd-shift-f": "workspace::NewSearch",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",

View file

@ -22,7 +22,7 @@ const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
actions!(terminal_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) {
cx.add_action(TerminalPanel::add_terminal);
cx.add_action(TerminalPanel::new_terminal);
}
pub enum Event {
@ -79,7 +79,7 @@ impl TerminalPanel {
cx.window_context().defer(move |cx| {
if let Some(this) = this.upgrade(cx) {
this.update(cx, |this, cx| {
this.add_terminal(&Default::default(), cx);
this.add_terminal(cx);
});
}
})
@ -220,7 +220,19 @@ impl TerminalPanel {
}
}
fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext<Self>) {
fn new_terminal(
workspace: &mut Workspace,
_: &workspace::NewTerminal,
cx: &mut ViewContext<Workspace>,
) {
let Some(this) = workspace.focus_panel::<Self>(cx) else {
return;
};
this.update(cx, |this, cx| this.add_terminal(cx))
}
fn add_terminal(&mut self, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
cx.spawn(|this, mut cx| async move {
let pane = this.read_with(&cx, |this, _| this.pane.clone())?;
@ -361,7 +373,7 @@ impl Panel for TerminalPanel {
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
if active && self.pane.read(cx).items_len() == 0 {
self.add_terminal(&Default::default(), cx)
self.add_terminal(cx)
}
}

View file

@ -38,7 +38,7 @@ use workspace::{
notifications::NotifyResultExt,
pane, register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
Pane, ToolbarItemLocation, Workspace, WorkspaceId,
NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
};
pub use terminal::TerminalSettings;
@ -66,10 +66,10 @@ pub fn init(cx: &mut AppContext) {
terminal_panel::init(cx);
terminal::init(cx);
cx.add_action(TerminalView::deploy);
register_deserializable_item::<TerminalView>(cx);
cx.add_action(TerminalView::deploy);
//Useful terminal views
cx.add_action(TerminalView::send_text);
cx.add_action(TerminalView::send_keystroke);
@ -101,7 +101,7 @@ impl TerminalView {
///Create a new Terminal in the current working directory or the user's home directory
pub fn deploy(
workspace: &mut Workspace,
_: &workspace::NewTerminal,
_: &NewCenterTerminal,
cx: &mut ViewContext<Workspace>,
) {
let strategy = settings::get::<TerminalSettings>(cx);

View file

@ -89,7 +89,8 @@ pub struct Workspace {
pub breadcrumbs: Interactive<ContainedText>,
pub disconnected_overlay: ContainedText,
pub modal: ContainerStyle,
pub zoomed_foreground: ContainerStyle,
pub zoomed_panel_foreground: ContainerStyle,
pub zoomed_pane_foreground: ContainerStyle,
pub zoomed_background: ContainerStyle,
pub notification: ContainerStyle,
pub notifications: Notifications,

View file

@ -32,7 +32,7 @@ pub fn init(cx: &mut AppContext) {
pub fn show_welcome_experience(app_state: &Arc<AppState>, cx: &mut AppContext) {
open_new(&app_state, cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Left, false, cx);
workspace.toggle_dock(DockPosition::Left, cx);
let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
cx.focus(&welcome_page);

View file

@ -180,7 +180,7 @@ impl Dock {
}
pub fn has_focus(&self, cx: &WindowContext) -> bool {
self.active_panel()
self.visible_panel()
.map_or(false, |panel| panel.has_focus(cx))
}
@ -201,7 +201,7 @@ impl Dock {
self.active_panel_index
}
pub fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
if open != self.is_open {
self.is_open = open;
if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
@ -212,11 +212,6 @@ impl Dock {
}
}
pub fn toggle_open(&mut self, cx: &mut ViewContext<Self>) {
self.set_open(!self.is_open, cx);
cx.notify();
}
pub fn set_panel_zoomed(
&mut self,
panel: &AnyViewHandle,
@ -259,7 +254,7 @@ impl Dock {
cx.focus(&panel);
}
} else if T::should_close_on_event(event)
&& this.active_panel().map_or(false, |p| p.id() == panel.id())
&& this.visible_panel().map_or(false, |p| p.id() == panel.id())
{
this.set_open(false, cx);
}
@ -315,12 +310,16 @@ impl Dock {
}
}
pub fn active_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
let entry = self.active_entry()?;
pub fn visible_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
let entry = self.visible_entry()?;
Some(&entry.panel)
}
fn active_entry(&self) -> Option<&PanelEntry> {
pub fn active_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
Some(&self.panel_entries.get(self.active_panel_index)?.panel)
}
fn visible_entry(&self) -> Option<&PanelEntry> {
if self.is_open {
self.panel_entries.get(self.active_panel_index)
} else {
@ -329,7 +328,7 @@ impl Dock {
}
pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Rc<dyn PanelHandle>> {
let entry = self.active_entry()?;
let entry = self.visible_entry()?;
if entry.panel.is_zoomed(cx) {
Some(entry.panel.clone())
} else {
@ -362,7 +361,7 @@ impl Dock {
}
pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
if let Some(active_entry) = self.active_entry() {
if let Some(active_entry) = self.visible_entry() {
Empty::new()
.into_any()
.contained()
@ -399,7 +398,7 @@ impl View for Dock {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(active_entry) = self.active_entry() {
if let Some(active_entry) = self.visible_entry() {
let style = self.style(cx);
ChildView::new(active_entry.panel.as_any(), cx)
.contained()
@ -417,7 +416,7 @@ impl View for Dock {
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
if let Some(active_entry) = self.active_entry() {
if let Some(active_entry) = self.visible_entry() {
cx.focus(active_entry.panel.as_any());
} else {
cx.focus_parent();
@ -504,13 +503,22 @@ impl View for PanelButtons {
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let tooltip_action =
tooltip_action.as_ref().map(|action| action.boxed_clone());
move |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
workspace.toggle_panel(dock_position, panel_ix, cx)
});
});
if let Some(tooltip_action) = &tooltip_action {
let window_id = cx.window_id();
let view_id = this.workspace.id();
let tooltip_action = tooltip_action.boxed_clone();
cx.spawn(|_, mut cx| async move {
cx.dispatch_action(
window_id,
view_id,
&*tooltip_action,
)
.ok();
})
.detach();
}
}
})

View file

@ -2,8 +2,8 @@ mod dragged_item_receiver;
use super::{ItemHandle, SplitDirection};
use crate::{
item::WeakItemHandle, toolbar::Toolbar, AutosaveSetting, Item, NewFile, NewSearch, NewTerminal,
ToggleZoom, Workspace, WorkspaceSettings,
item::WeakItemHandle, toolbar::Toolbar, AutosaveSetting, Item, NewCenterTerminal, NewFile,
NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
@ -150,7 +150,6 @@ pub enum Event {
pub struct Pane {
items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<usize>,
is_active: bool,
zoomed: bool,
active_item_index: usize,
last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
@ -255,7 +254,6 @@ impl Pane {
Self {
items: Vec::new(),
activation_history: Vec::new(),
is_active: true,
zoomed: false,
active_item_index: 0,
last_focused_view_by_item: Default::default(),
@ -323,15 +321,6 @@ impl Pane {
&self.workspace
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn set_active(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
self.is_active = is_active;
cx.notify();
}
pub fn has_focus(&self) -> bool {
self.has_focus
}
@ -1219,7 +1208,7 @@ impl Pane {
AnchorCorner::TopRight,
vec![
ContextMenuItem::action("New File", NewFile),
ContextMenuItem::action("New Terminal", NewTerminal),
ContextMenuItem::action("New Terminal", NewCenterTerminal),
ContextMenuItem::action("New Search", NewSearch),
],
cx,
@ -1343,7 +1332,7 @@ impl Pane {
None
};
let pane_active = self.is_active;
let pane_active = self.has_focus;
enum Tabs {}
let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
@ -1722,7 +1711,7 @@ impl View for Pane {
let mut tab_row = Flex::row()
.with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
if self.is_active {
if self.has_focus {
let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
tab_row.add_child(
(render_tab_bar_buttons)(self, cx)
@ -1813,6 +1802,7 @@ impl View for Pane {
if !self.has_focus {
self.has_focus = true;
cx.emit(Event::Focus);
cx.notify();
}
self.toolbar.update(cx, |toolbar, cx| {
@ -1847,6 +1837,7 @@ impl View for Pane {
self.toolbar.update(cx, |toolbar, cx| {
toolbar.pane_focus_update(false, cx);
});
cx.notify();
}
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {

View file

@ -53,6 +53,7 @@ use std::{
cmp, env,
future::Future,
path::{Path, PathBuf},
rc::Rc,
str,
sync::{atomic::AtomicUsize, Arc},
time::Duration,
@ -103,24 +104,6 @@ pub trait Modal: View {
#[derive(Clone, PartialEq)]
pub struct RemoveWorktreeFromProject(pub WorktreeId);
#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
pub struct ToggleLeftDock {
#[serde(default = "default_true")]
pub focus: bool,
}
#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
pub struct ToggleBottomDock {
#[serde(default = "default_true")]
pub focus: bool,
}
#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
pub struct ToggleRightDock {
#[serde(default = "default_true")]
pub focus: bool,
}
actions!(
workspace,
[
@ -137,22 +120,21 @@ actions!(
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
Feedback,
Restart,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
]
);
actions!(zed, [OpenSettings]);
impl_actions!(
workspace,
[ToggleLeftDock, ToggleBottomDock, ToggleRightDock]
);
#[derive(Clone, PartialEq)]
pub struct OpenPaths {
pub paths: Vec<PathBuf>,
@ -268,14 +250,14 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
workspace.activate_next_pane(cx)
});
cx.add_action(|workspace: &mut Workspace, action: &ToggleLeftDock, cx| {
workspace.toggle_dock(DockPosition::Left, action.focus, cx);
cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
workspace.toggle_dock(DockPosition::Left, cx);
});
cx.add_action(|workspace: &mut Workspace, action: &ToggleRightDock, cx| {
workspace.toggle_dock(DockPosition::Right, action.focus, cx);
cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
workspace.toggle_dock(DockPosition::Right, cx);
});
cx.add_action(|workspace: &mut Workspace, action: &ToggleBottomDock, cx| {
workspace.toggle_dock(DockPosition::Bottom, action.focus, cx);
cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
workspace.toggle_dock(DockPosition::Bottom, cx);
});
cx.add_action(Workspace::activate_pane_at_index);
@ -485,6 +467,7 @@ pub struct Workspace {
remote_entity_subscription: Option<client::Subscription>,
modal: Option<AnyViewHandle>,
zoomed: Option<AnyWeakViewHandle>,
zoomed_position: Option<DockPosition>,
center: PaneGroup,
left_dock: ViewHandle<Dock>,
bottom_dock: ViewHandle<Dock>,
@ -689,6 +672,7 @@ impl Workspace {
weak_self: weak_handle.clone(),
modal: None,
zoomed: None,
zoomed_position: None,
center: PaneGroup::new(center_pane.clone()),
panes: vec![center_pane.clone()],
panes_by_item: Default::default(),
@ -887,10 +871,15 @@ impl Workspace {
was_visible = dock.is_open()
&& dock
.active_panel()
.visible_panel()
.map_or(false, |active_panel| active_panel.id() == panel.id());
dock.remove_panel(&panel, cx);
});
if panel.is_zoomed(cx) {
this.zoomed_position = Some(new_position);
}
dock = match panel.read(cx).position(cx) {
DockPosition::Left => &this.left_dock,
DockPosition::Bottom => &this.bottom_dock,
@ -909,14 +898,17 @@ impl Workspace {
dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
if panel.has_focus(cx) {
this.zoomed = Some(panel.downgrade().into_any());
this.zoomed_position = Some(panel.read(cx).position(cx));
}
} else if T::should_zoom_out_on_event(event) {
this.zoom_out(cx);
} else if T::is_focus_event(event) {
if panel.is_zoomed(cx) {
this.zoomed = Some(panel.downgrade().into_any());
this.zoomed_position = Some(panel.read(cx).position(cx));
} else {
this.zoomed = None;
this.zoomed_position = None;
}
cx.notify();
}
@ -1486,89 +1478,110 @@ impl Workspace {
}
}
pub fn toggle_dock(
&mut self,
dock_side: DockPosition,
focus: bool,
cx: &mut ViewContext<Self>,
) {
pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
let dock = match dock_side {
DockPosition::Left => &self.left_dock,
DockPosition::Bottom => &self.bottom_dock,
DockPosition::Right => &self.right_dock,
};
let mut focus_center = false;
let mut zoom_out = false;
dock.update(cx, |dock, cx| {
let open = !dock.is_open();
dock.set_open(open, cx);
let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
let was_visible = dock.is_open() && !other_is_zoomed;
dock.set_open(!was_visible, cx);
if let Some(active_panel) = dock.active_panel() {
if was_visible {
if active_panel.has_focus(cx) {
focus_center = true;
}
} else {
if active_panel.is_zoomed(cx) {
cx.focus(active_panel.as_any());
}
zoom_out = true;
}
}
});
if dock.read(cx).is_open() && focus {
cx.focus(dock);
} else {
if zoom_out {
self.zoom_out_everything_except(dock_side, cx);
}
if focus_center {
cx.focus_self();
}
cx.notify();
self.serialize_workspace(cx);
}
pub fn toggle_panel(
&mut self,
position: DockPosition,
panel_index: usize,
cx: &mut ViewContext<Self>,
) {
let dock = match position {
DockPosition::Left => &mut self.left_dock,
DockPosition::Bottom => &mut self.bottom_dock,
DockPosition::Right => &mut self.right_dock,
};
let active_item = dock.update(cx, move |dock, cx| {
if dock.is_open() && dock.active_panel_index() == panel_index {
dock.set_open(false, cx);
None
} else {
dock.set_open(true, cx);
dock.activate_panel(panel_index, cx);
dock.active_panel().cloned()
}
});
if let Some(active_item) = active_item {
if active_item.has_focus(cx) {
cx.focus_self();
} else {
cx.focus(active_item.as_any());
}
} else {
cx.focus_self();
}
self.serialize_workspace(cx);
cx.notify();
pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
self.show_or_hide_panel::<T>(cx, |_, _| true)?
.as_any()
.clone()
.downcast()
}
pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
self.show_or_hide_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
}
fn show_or_hide_panel<T: Panel>(
&mut self,
cx: &mut ViewContext<Self>,
show: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
) -> Option<Rc<dyn PanelHandle>> {
for (dock, position) in [
self.left_dock.clone(),
self.bottom_dock.clone(),
self.right_dock.clone(),
]
.into_iter()
.zip(
[
DockPosition::Left,
DockPosition::Bottom,
DockPosition::Right,
]
.into_iter(),
) {
if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
let active_item = dock.update(cx, |dock, cx| {
dock.set_open(true, cx);
let mut focus_center = false;
let mut zoom_out = false;
let panel = dock.update(cx, |dock, cx| {
dock.activate_panel(panel_index, cx);
dock.active_panel().cloned()
});
if let Some(active_item) = active_item {
if active_item.has_focus(cx) {
cx.focus_self();
} else {
cx.focus(active_item.as_any());
let panel = dock.active_panel().cloned();
if let Some(panel) = panel.as_ref() {
let should_show = show(&**panel, cx);
if should_show {
dock.set_open(true, cx);
cx.focus(panel.as_any());
zoom_out = true;
} else {
if panel.is_zoomed(cx) {
dock.set_open(false, cx);
}
focus_center = true;
}
}
panel
});
if zoom_out {
self.zoom_out_everything_except(position, cx);
}
if focus_center {
cx.focus_self();
}
self.serialize_workspace(cx);
cx.notify();
break;
return panel;
}
}
None
}
fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
@ -1580,6 +1593,36 @@ impl Workspace {
self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
self.zoomed = None;
self.zoomed_position = None;
cx.notify();
}
fn zoom_out_everything_except(
&mut self,
except_position: DockPosition,
cx: &mut ViewContext<Self>,
) {
for pane in &self.panes {
pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
}
if except_position != DockPosition::Left {
self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
}
if except_position != DockPosition::Bottom {
self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
}
if except_position != DockPosition::Right {
self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
}
if self.zoomed_position != Some(except_position) {
self.zoomed = None;
self.zoomed_position = None;
}
cx.notify();
}
@ -1780,11 +1823,7 @@ impl Workspace {
fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
if self.active_pane != pane {
self.active_pane
.update(cx, |pane, cx| pane.set_active(false, cx));
self.active_pane = pane.clone();
self.active_pane
.update(cx, |pane, cx| pane.set_active(true, cx));
self.status_bar.update(cx, |status_bar, cx| {
status_bar.set_active_pane(&self.active_pane, cx);
});
@ -1797,6 +1836,7 @@ impl Workspace {
} else {
self.zoomed = None;
}
self.zoomed_position = None;
self.update_followers(
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
@ -1855,6 +1895,7 @@ impl Workspace {
pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
if pane.read(cx).has_focus() {
self.zoomed = Some(pane.downgrade().into_any());
self.zoomed_position = None;
}
cx.notify();
}
@ -2663,7 +2704,7 @@ impl Workspace {
})
})
.collect::<Vec<_>>(),
pane.is_active(),
pane.has_focus(),
)
};
@ -2691,7 +2732,7 @@ impl Workspace {
fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
let left_dock = this.left_dock.read(cx);
let left_visible = left_dock.is_open();
let left_active_panel = left_dock.active_panel().and_then(|panel| {
let left_active_panel = left_dock.visible_panel().and_then(|panel| {
Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())?
.to_string(),
@ -2700,7 +2741,7 @@ impl Workspace {
let right_dock = this.right_dock.read(cx);
let right_visible = right_dock.is_open();
let right_active_panel = right_dock.active_panel().and_then(|panel| {
let right_active_panel = right_dock.visible_panel().and_then(|panel| {
Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())?
.to_string(),
@ -2709,7 +2750,7 @@ impl Workspace {
let bottom_dock = this.bottom_dock.read(cx);
let bottom_visible = bottom_dock.is_open();
let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| {
let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
Some(
cx.view_ui_name(panel.as_any().window_id(), panel.id())?
.to_string(),
@ -2891,7 +2932,7 @@ impl Workspace {
DockPosition::Right => &self.right_dock,
DockPosition::Bottom => &self.bottom_dock,
};
let active_panel = dock.read(cx).active_panel()?;
let active_panel = dock.read(cx).visible_panel()?;
let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
dock.read(cx).render_placeholder(cx)
} else {
@ -3092,10 +3133,40 @@ impl View for Workspace {
.with_children(self.zoomed.as_ref().and_then(|zoomed| {
enum ZoomBackground {}
let zoomed = zoomed.upgrade(cx)?;
let mut foreground_style;
match self.zoomed_position {
Some(DockPosition::Left) => {
foreground_style =
theme.workspace.zoomed_panel_foreground;
foreground_style.margin.left = 0.;
foreground_style.margin.top = 0.;
foreground_style.margin.bottom = 0.;
}
Some(DockPosition::Right) => {
foreground_style =
theme.workspace.zoomed_panel_foreground;
foreground_style.margin.right = 0.;
foreground_style.margin.top = 0.;
foreground_style.margin.bottom = 0.;
}
Some(DockPosition::Bottom) => {
foreground_style =
theme.workspace.zoomed_panel_foreground;
foreground_style.margin.left = 0.;
foreground_style.margin.right = 0.;
foreground_style.margin.bottom = 0.;
}
None => {
foreground_style =
theme.workspace.zoomed_pane_foreground;
}
}
Some(
ChildView::new(&zoomed, cx)
.contained()
.with_style(theme.workspace.zoomed_foreground)
.with_style(foreground_style)
.aligned()
.contained()
.with_style(theme.workspace.zoomed_background)
@ -3445,10 +3516,6 @@ fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
Some(vec2f(width as f32, height as f32))
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
@ -4029,6 +4096,128 @@ mod tests {
});
}
#[gpui::test]
async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let panel = workspace.update(cx, |workspace, cx| {
let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
workspace.add_panel(panel.clone(), cx);
workspace
.right_dock()
.update(cx, |right_dock, cx| right_dock.set_open(true, cx));
panel
});
// Transfer focus from center to panel
workspace.update(cx, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(!panel.is_zoomed(cx));
assert!(panel.has_focus(cx));
});
// Transfer focus from panel to center
workspace.update(cx, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(!panel.is_zoomed(cx));
assert!(!panel.has_focus(cx));
});
// Close the dock
workspace.update(cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Right, cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(!workspace.right_dock().read(cx).is_open());
assert!(!panel.is_zoomed(cx));
assert!(!panel.has_focus(cx));
});
// Open the dock
workspace.update(cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Right, cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(!panel.is_zoomed(cx));
assert!(!panel.has_focus(cx));
});
// Focus and zoom panel
panel.update(cx, |panel, cx| {
cx.focus_self();
panel.set_zoomed(true, cx)
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(panel.is_zoomed(cx));
assert!(panel.has_focus(cx));
});
// Transfer focus to the center closes the dock
workspace.update(cx, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(!workspace.right_dock().read(cx).is_open());
assert!(panel.is_zoomed(cx));
assert!(!panel.has_focus(cx));
});
// Transfering focus back to the panel keeps it zoomed
workspace.update(cx, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(panel.is_zoomed(cx));
assert!(panel.has_focus(cx));
});
// Close the dock while it is zoomed
workspace.update(cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Right, cx)
});
workspace.read_with(cx, |workspace, cx| {
assert!(!workspace.right_dock().read(cx).is_open());
assert!(panel.is_zoomed(cx));
assert!(workspace.zoomed.is_none());
assert!(!panel.has_focus(cx));
});
// Opening the dock, when it's zoomed, retains focus
workspace.update(cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Right, cx)
});
workspace.read_with(cx, |workspace, cx| {
assert!(workspace.right_dock().read(cx).is_open());
assert!(panel.is_zoomed(cx));
assert!(workspace.zoomed.is_some());
assert!(panel.has_focus(cx));
});
}
#[gpui::test]
async fn test_panels(cx: &mut gpui::TestAppContext) {
init_test(cx);
@ -4052,7 +4241,7 @@ mod tests {
let left_dock = workspace.left_dock();
assert_eq!(
left_dock.read(cx).active_panel().unwrap().id(),
left_dock.read(cx).visible_panel().unwrap().id(),
panel_1.id()
);
assert_eq!(
@ -4062,7 +4251,12 @@ mod tests {
left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
assert_eq!(
workspace.right_dock().read(cx).active_panel().unwrap().id(),
workspace
.right_dock()
.read(cx)
.visible_panel()
.unwrap()
.id(),
panel_2.id()
);
@ -4078,10 +4272,10 @@ mod tests {
// Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
// 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).active_panel().is_none());
assert!(workspace.left_dock().read(cx).visible_panel().is_none());
let right_dock = workspace.right_dock();
assert_eq!(
right_dock.read(cx).active_panel().unwrap().id(),
right_dock.read(cx).visible_panel().unwrap().id(),
panel_1.id()
);
assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
@ -4096,7 +4290,12 @@ mod tests {
// And the right dock is unaffected in it's displaying of panel_1
assert!(workspace.right_dock().read(cx).is_open());
assert_eq!(
workspace.right_dock().read(cx).active_panel().unwrap().id(),
workspace
.right_dock()
.read(cx)
.visible_panel()
.unwrap()
.id(),
panel_1.id()
);
});
@ -4111,7 +4310,7 @@ mod tests {
let left_dock = workspace.left_dock();
assert!(left_dock.read(cx).is_open());
assert_eq!(
left_dock.read(cx).active_panel().unwrap().id(),
left_dock.read(cx).visible_panel().unwrap().id(),
panel_1.id()
);
assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
@ -4145,7 +4344,7 @@ mod tests {
let left_dock = workspace.left_dock();
assert!(left_dock.read(cx).is_open());
assert_eq!(
left_dock.read(cx).active_panel().unwrap().id(),
left_dock.read(cx).visible_panel().unwrap().id(),
panel_1.id()
);
assert!(panel_1.is_focused(cx));
@ -4159,7 +4358,7 @@ mod tests {
let left_dock = workspace.left_dock();
assert!(left_dock.read(cx).is_open());
assert_eq!(
left_dock.read(cx).active_panel().unwrap().id(),
left_dock.read(cx).visible_panel().unwrap().id(),
panel_1.id()
);
});
@ -4168,6 +4367,14 @@ mod tests {
panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
});
// Move panel to another dock while it is zoomed
panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
});
// If focus is transferred to another view that's not a panel or another pane, we still show
@ -4176,12 +4383,14 @@ mod tests {
focus_receiver.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
});
// If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
workspace.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, None);
assert_eq!(workspace.zoomed_position, None);
});
// If focus is transferred again to another view that's not a panel or a pane, we won't
@ -4189,18 +4398,21 @@ mod tests {
focus_receiver.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, None);
assert_eq!(workspace.zoomed_position, None);
});
// When focus is transferred back to the panel, it is zoomed again.
panel_1.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
});
// Emitting a ZoomOut event unzooms the panel.
panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, None);
assert_eq!(workspace.zoomed_position, None);
});
// Emit closed event on panel 1, which is active
@ -4208,8 +4420,8 @@ mod tests {
// Now the left dock is closed, because panel_1 was the active panel
workspace.read_with(cx, |workspace, cx| {
let left_dock = workspace.left_dock();
assert!(!left_dock.read(cx).is_open());
let right_dock = workspace.right_dock();
assert!(!right_dock.read(cx).is_open());
});
}

View file

@ -89,18 +89,9 @@ pub fn menus() -> Vec<Menu<'static>> {
MenuItem::action("Zoom Out", super::DecreaseBufferFontSize),
MenuItem::action("Reset Zoom", super::ResetBufferFontSize),
MenuItem::separator(),
MenuItem::action(
"Toggle Left Dock",
workspace::ToggleLeftDock { focus: false },
),
MenuItem::action(
"Toggle Right Dock",
workspace::ToggleRightDock { focus: false },
),
MenuItem::action(
"Toggle Bottom Dock",
workspace::ToggleBottomDock { focus: false },
),
MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock),
MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock),
MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock),
MenuItem::submenu(Menu {
name: "Editor Layout",
items: vec![

View file

@ -354,7 +354,7 @@ pub fn initialize_workspace(
.map_or(false, |entry| entry.is_dir())
})
{
workspace.toggle_dock(project_panel_position, false, cx);
workspace.toggle_dock(project_panel_position, cx);
}
workspace.add_panel(terminal_panel, cx)

View file

@ -119,14 +119,18 @@ export default function workspace(colorScheme: ColorScheme) {
cursor: "Arrow",
},
zoomedBackground: {
padding: 10,
cursor: "Arrow",
background: withOpacity(background(colorScheme.lowest), 0.5)
background: withOpacity(background(colorScheme.lowest), 0.85)
},
zoomedForeground: {
zoomedPaneForeground: {
margin: 10,
shadow: colorScheme.modalShadow,
border: border(colorScheme.highest, { overlay: true }),
},
zoomedPanelForeground: {
margin: 18,
border: border(colorScheme.highest, { overlay: true }),
},
dock: {
left: {
border: border(layer, { right: true }),