Fix more issues with panels and zoom (#2545)

Release Notes:

* Fixed the behavior of panel buttons when their panel is open. Show the
key binding for closing the containing dock (preview only).
* Improved the styling of zoomed panels and panes, giving them a
stronger border, and color the zoom button with an "active" style
(preview only).
* Improved the stability of panels' zoom state. Close their dock instead
of resetting their zoom state when dismissing panels to reveal other
items (preview only).
This commit is contained in:
Max Brunsfeld 2023-05-31 13:17:12 -07:00 committed by GitHub
commit 62660f2766
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 83 deletions

View file

@ -70,6 +70,7 @@ impl TerminalPanel {
.with_child(Pane::render_tab_bar_button( .with_child(Pane::render_tab_bar_button(
0, 0,
"icons/plus_12.svg", "icons/plus_12.svg",
false,
Some(( Some((
"New Terminal".into(), "New Terminal".into(),
Some(Box::new(workspace::NewTerminal)), Some(Box::new(workspace::NewTerminal)),
@ -94,6 +95,7 @@ impl TerminalPanel {
} else { } else {
"icons/maximize_8.svg" "icons/maximize_8.svg"
}, },
pane.is_zoomed(),
Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))), Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))),
cx, cx,
move |pane, cx| pane.toggle_zoom(&Default::default(), cx), move |pane, cx| pane.toggle_zoom(&Default::default(), cx),

View file

@ -175,6 +175,10 @@ impl Dock {
} }
} }
pub fn position(&self) -> DockPosition {
self.position
}
pub fn is_open(&self) -> bool { pub fn is_open(&self) -> bool {
self.is_open self.is_open
} }
@ -472,11 +476,22 @@ impl View for PanelButtons {
Flex::row() Flex::row()
.with_children(panels.into_iter().enumerate().map( .with_children(panels.into_iter().enumerate().map(
|(panel_ix, (view, context_menu))| { |(panel_ix, (view, context_menu))| {
let (tooltip, tooltip_action) = view.icon_tooltip(cx); let is_active = is_open && panel_ix == active_ix;
let (tooltip, tooltip_action) = if is_active {
(
format!("Close {} dock", dock_position.to_label()),
Some(match dock_position {
DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
}),
)
} else {
view.icon_tooltip(cx)
};
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| { MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
let is_active = is_open && panel_ix == active_ix;
let style = button_style.style_for(state, is_active); let style = button_style.style_for(state, is_active);
Flex::row() Flex::row()
.with_child( .with_child(

View file

@ -268,6 +268,7 @@ impl Pane {
.with_child(Self::render_tab_bar_button( .with_child(Self::render_tab_bar_button(
0, 0,
"icons/plus_12.svg", "icons/plus_12.svg",
false,
Some(("New...".into(), None)), Some(("New...".into(), None)),
cx, cx,
|pane, cx| pane.deploy_new_menu(cx), |pane, cx| pane.deploy_new_menu(cx),
@ -277,6 +278,7 @@ impl Pane {
.with_child(Self::render_tab_bar_button( .with_child(Self::render_tab_bar_button(
1, 1,
"icons/split_12.svg", "icons/split_12.svg",
false,
Some(("Split Pane".into(), None)), Some(("Split Pane".into(), None)),
cx, cx,
|pane, cx| pane.deploy_split_menu(cx), |pane, cx| pane.deploy_split_menu(cx),
@ -290,6 +292,7 @@ impl Pane {
} else { } else {
"icons/maximize_8.svg" "icons/maximize_8.svg"
}, },
pane.is_zoomed(),
Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))), Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))),
cx, cx,
move |pane, cx| pane.toggle_zoom(&Default::default(), cx), move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
@ -1401,6 +1404,7 @@ impl Pane {
pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>( pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
index: usize, index: usize,
icon: &'static str, icon: &'static str,
active: bool,
tooltip: Option<(String, Option<Box<dyn Action>>)>, tooltip: Option<(String, Option<Box<dyn Action>>)>,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
on_click: F, on_click: F,
@ -1410,7 +1414,7 @@ impl Pane {
let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| { let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar; let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
let style = theme.pane_button.style_for(mouse_state, false); let style = theme.pane_button.style_for(mouse_state, active);
Svg::new(icon) Svg::new(icon)
.with_color(style.color) .with_color(style.color)
.constrained() .constrained()

View file

@ -908,18 +908,24 @@ impl Workspace {
} }
}); });
} else if T::should_zoom_in_on_event(event) { } else if T::should_zoom_in_on_event(event) {
this.zoom_out(cx);
dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
if panel.has_focus(cx) { if panel.has_focus(cx) {
this.zoomed = Some(panel.downgrade().into_any()); this.zoomed = Some(panel.downgrade().into_any());
this.zoomed_position = Some(panel.read(cx).position(cx)); this.zoomed_position = Some(panel.read(cx).position(cx));
} }
} else if T::should_zoom_out_on_event(event) { } else if T::should_zoom_out_on_event(event) {
this.zoom_out(cx); dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
if this.zoomed_position == Some(prev_position) {
this.zoomed = None;
this.zoomed_position = None;
}
cx.notify();
} else if T::is_focus_event(event) { } else if T::is_focus_event(event) {
let position = panel.read(cx).position(cx);
this.dismiss_zoomed_items_to_reveal(Some(position), cx);
if panel.is_zoomed(cx) { if panel.is_zoomed(cx) {
this.zoomed = Some(panel.downgrade().into_any()); this.zoomed = Some(panel.downgrade().into_any());
this.zoomed_position = Some(panel.read(cx).position(cx)); this.zoomed_position = Some(position);
} else { } else {
this.zoomed = None; this.zoomed = None;
this.zoomed_position = None; this.zoomed_position = None;
@ -1592,7 +1598,7 @@ impl Workspace {
DockPosition::Right => &self.right_dock, DockPosition::Right => &self.right_dock,
}; };
let mut focus_center = false; let mut focus_center = false;
let mut zoom_out = false; let mut reveal_dock = false;
dock.update(cx, |dock, cx| { dock.update(cx, |dock, cx| {
let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
let was_visible = dock.is_open() && !other_is_zoomed; let was_visible = dock.is_open() && !other_is_zoomed;
@ -1607,14 +1613,15 @@ impl Workspace {
if active_panel.is_zoomed(cx) { if active_panel.is_zoomed(cx) {
cx.focus(active_panel.as_any()); cx.focus(active_panel.as_any());
} }
zoom_out = true; reveal_dock = true;
} }
} }
}); });
if zoom_out { if reveal_dock {
self.zoom_out_everything_except(dock_side, cx); self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
} }
if focus_center { if focus_center {
cx.focus_self(); cx.focus_self();
} }
@ -1623,62 +1630,49 @@ impl Workspace {
self.serialize_workspace(cx); self.serialize_workspace(cx);
} }
/// Transfer focus to the panel of the given type.
pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> { pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
self.show_or_hide_panel::<T>(cx, |_, _| true)? self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
.as_any() .as_any()
.clone() .clone()
.downcast() .downcast()
} }
/// Focus the panel of the given type if it isn't already focused. If it is
/// already focused, then transfer focus back to the workspace center.
pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) { pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
self.show_or_hide_panel::<T>(cx, |panel, cx| !panel.has_focus(cx)); self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
} }
fn show_or_hide_panel<T: Panel>( /// Focus or unfocus the given panel type, depending on the given callback.
fn focus_or_unfocus_panel<T: Panel>(
&mut self, &mut self,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
show: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool, should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
) -> Option<Rc<dyn PanelHandle>> { ) -> Option<Rc<dyn PanelHandle>> {
for (dock, position) in [ for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
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>() { if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
let mut focus_center = false; let mut focus_center = false;
let mut zoom_out = false; let mut reveal_dock = false;
let panel = dock.update(cx, |dock, cx| { let panel = dock.update(cx, |dock, cx| {
dock.activate_panel(panel_index, cx); dock.activate_panel(panel_index, cx);
let panel = dock.active_panel().cloned(); let panel = dock.active_panel().cloned();
if let Some(panel) = panel.as_ref() { if let Some(panel) = panel.as_ref() {
let should_show = show(&**panel, cx); if should_focus(&**panel, cx) {
if should_show {
dock.set_open(true, cx); dock.set_open(true, cx);
cx.focus(panel.as_any()); cx.focus(panel.as_any());
zoom_out = true; reveal_dock = true;
} else { } else {
if panel.is_zoomed(cx) { // if panel.is_zoomed(cx) {
dock.set_open(false, cx); // dock.set_open(false, cx);
} // }
focus_center = true; focus_center = true;
} }
} }
panel panel
}); });
if zoom_out {
self.zoom_out_everything_except(position, cx);
}
if focus_center { if focus_center {
cx.focus_self(); cx.focus_self();
} }
@ -1705,28 +1699,38 @@ impl Workspace {
cx.notify(); cx.notify();
} }
fn zoom_out_everything_except( fn dismiss_zoomed_items_to_reveal(
&mut self, &mut self,
except_position: DockPosition, dock_to_reveal: Option<DockPosition>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
// If a center pane is zoomed, unzoom it.
for pane in &self.panes { for pane in &self.panes {
pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); if pane != &self.active_pane {
pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
}
} }
if except_position != DockPosition::Left { // If another dock is zoomed, hide it.
self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); let mut focus_center = false;
for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
dock.update(cx, |dock, cx| {
if Some(dock.position()) != dock_to_reveal {
if let Some(panel) = dock.active_panel() {
if panel.is_zoomed(cx) {
focus_center |= panel.has_focus(cx);
dock.set_open(false, cx);
}
}
}
});
} }
if except_position != DockPosition::Bottom { if focus_center {
self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); cx.focus_self();
} }
if except_position != DockPosition::Right { if self.zoomed_position != dock_to_reveal {
self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
}
if self.zoomed_position != Some(except_position) {
self.zoomed = None; self.zoomed = None;
self.zoomed_position = None; self.zoomed_position = None;
} }
@ -1937,6 +1941,7 @@ impl Workspace {
self.last_active_center_pane = Some(pane.downgrade()); self.last_active_center_pane = Some(pane.downgrade());
} }
self.dismiss_zoomed_items_to_reveal(None, cx);
if pane.read(cx).is_zoomed() { if pane.read(cx).is_zoomed() {
self.zoomed = Some(pane.downgrade().into_any()); self.zoomed = Some(pane.downgrade().into_any());
} else { } else {
@ -1998,7 +2003,6 @@ impl Workspace {
} }
pane::Event::ZoomIn => { pane::Event::ZoomIn => {
if pane == self.active_pane { if pane == self.active_pane {
self.zoom_out(cx);
pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
if pane.read(cx).has_focus() { if pane.read(cx).has_focus() {
self.zoomed = Some(pane.downgrade().into_any()); self.zoomed = Some(pane.downgrade().into_any());
@ -2007,7 +2011,13 @@ impl Workspace {
cx.notify(); cx.notify();
} }
} }
pane::Event::ZoomOut => self.zoom_out(cx), pane::Event::ZoomOut => {
pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
if self.zoomed_position.is_none() {
self.zoomed = None;
}
cx.notify();
}
} }
self.serialize_workspace(cx); self.serialize_workspace(cx);
@ -3278,32 +3288,36 @@ impl View for Workspace {
enum ZoomBackground {} enum ZoomBackground {}
let zoomed = zoomed.upgrade(cx)?; let zoomed = zoomed.upgrade(cx)?;
let mut foreground_style; let mut foreground_style =
match self.zoomed_position { theme.workspace.zoomed_pane_foreground;
Some(DockPosition::Left) => { if let Some(zoomed_dock_position) = self.zoomed_position {
foreground_style = foreground_style =
theme.workspace.zoomed_panel_foreground; theme.workspace.zoomed_panel_foreground;
foreground_style.margin.left = 0.; let margin = foreground_style.margin.top;
foreground_style.margin.top = 0.; let border = foreground_style.border.top;
foreground_style.margin.bottom = 0.;
} // Only include a margin and border on the opposite side.
Some(DockPosition::Right) => { foreground_style.margin.top = 0.;
foreground_style = foreground_style.margin.left = 0.;
theme.workspace.zoomed_panel_foreground; foreground_style.margin.bottom = 0.;
foreground_style.margin.right = 0.; foreground_style.margin.right = 0.;
foreground_style.margin.top = 0.; foreground_style.border.top = false;
foreground_style.margin.bottom = 0.; foreground_style.border.left = false;
} foreground_style.border.bottom = false;
Some(DockPosition::Bottom) => { foreground_style.border.right = false;
foreground_style = match zoomed_dock_position {
theme.workspace.zoomed_panel_foreground; DockPosition::Left => {
foreground_style.margin.left = 0.; foreground_style.margin.right = margin;
foreground_style.margin.right = 0.; foreground_style.border.right = border;
foreground_style.margin.bottom = 0.; }
} DockPosition::Right => {
None => { foreground_style.margin.left = margin;
foreground_style = foreground_style.border.left = border;
theme.workspace.zoomed_pane_foreground; }
DockPosition::Bottom => {
foreground_style.margin.top = margin;
foreground_style.border.top = border;
}
} }
} }

View file

@ -94,6 +94,9 @@ export default function tabBar(colorScheme: ColorScheme) {
hover: { hover: {
color: foreground(layer, "hovered"), color: foreground(layer, "hovered"),
}, },
active: {
color: foreground(layer, "accent"),
}
}, },
paneButtonContainer: { paneButtonContainer: {
background: tab.background, background: tab.background,

View file

@ -13,6 +13,7 @@ import tabBar from "./tabBar"
export default function workspace(colorScheme: ColorScheme) { export default function workspace(colorScheme: ColorScheme) {
const layer = colorScheme.lowest const layer = colorScheme.lowest
const isLight = colorScheme.isLight
const itemSpacing = 8 const itemSpacing = 8
const titlebarButton = { const titlebarButton = {
cornerRadius: 6, cornerRadius: 6,
@ -120,16 +121,18 @@ export default function workspace(colorScheme: ColorScheme) {
}, },
zoomedBackground: { zoomedBackground: {
cursor: "Arrow", cursor: "Arrow",
background: withOpacity(background(colorScheme.lowest), 0.85) background: isLight
? withOpacity(background(colorScheme.lowest), 0.8)
: withOpacity(background(colorScheme.highest), 0.6)
}, },
zoomedPaneForeground: { zoomedPaneForeground: {
margin: 10, margin: 16,
shadow: colorScheme.modalShadow, shadow: colorScheme.modalShadow,
border: border(colorScheme.highest, { overlay: true }), border: border(colorScheme.lowest, { overlay: true }),
}, },
zoomedPanelForeground: { zoomedPanelForeground: {
margin: 18, margin: 16,
border: border(colorScheme.highest, { overlay: true }), border: border(colorScheme.lowest, { overlay: true }),
}, },
dock: { dock: {
left: { left: {