WIP: iron out bugs in interaction between toggle and toggle focus key bindings

Mikayla Maki and max created

co-authored-by: max <max@zed.dev>

Change summary

crates/workspace/src/workspace.rs | 143 ++++++++++++++++++++++++++++----
1 file changed, 122 insertions(+), 21 deletions(-)

Detailed changes

crates/workspace/src/workspace.rs 🔗

@@ -478,6 +478,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>,
@@ -683,6 +684,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(),
@@ -885,6 +887,11 @@ impl Workspace {
                                 .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,
@@ -903,14 +910,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();
                 }
@@ -1573,33 +1583,36 @@ impl Workspace {
         }
     }
 
-    pub fn toggle_dock(
-        &mut self,
-        dock_side: DockPosition,
-        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 focus_center = dock.update(cx, |dock, cx| {
-            let was_open = dock.is_open();
-            dock.set_open(!was_open, cx);
+        let mut focus_center = false;
+        let mut zoom_out = false;
+        dock.update(cx, |dock, 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_open {
+                if was_visible {
                     if active_panel.has_focus(cx) {
-                        return true;
+                        focus_center = true;
+                    }
+                } else {
+                    if active_panel.is_zoomed(cx) {
+                        cx.focus(active_panel.as_any());
                     }
-                } else if active_panel.is_zoomed(cx) {
-                    cx.focus(active_panel.as_any());
+                    zoom_out = true;
                 }
             }
-
-            false
         });
 
+        if zoom_out {
+            self.zoom_out_everything_except(dock_side, cx);
+        }
         if focus_center {
             cx.focus_self();
         }
@@ -1646,9 +1659,24 @@ impl Workspace {
     }
 
     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] {
+        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 focus_center = dock.update(cx, |dock, cx| {
+                let mut focus_center = false;
+                let mut zoom_out = false;
+                dock.update(cx, |dock, cx| {
                     dock.activate_panel(panel_index, cx);
 
                     if let Some(panel) = dock.active_panel().cloned() {
@@ -1656,15 +1684,18 @@ impl Workspace {
                             if panel.is_zoomed(cx) {
                                 dock.set_open(false, cx);
                             }
-                            return true
+                            focus_center = true;
                         } else {
                             dock.set_open(true, cx);
                             cx.focus(panel.as_any());
+                            zoom_out = true;
                         }
                     }
-                    false
                 });
 
+                if zoom_out {
+                    self.zoom_out_everything_except(position, cx);
+                }
                 if focus_center {
                     cx.focus_self();
                 }
@@ -1685,6 +1716,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();
     }
@@ -1901,6 +1962,7 @@ impl Workspace {
         } else {
             self.zoomed = None;
         }
+        self.zoomed_position = None;
 
         self.update_followers(
             proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
@@ -1960,6 +2022,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();
                 }
@@ -3234,13 +3297,38 @@ impl View for Workspace {
                                     .with_children(self.zoomed.as_ref().and_then(|zoomed| {
                                         enum ZoomBackground {}
                                         let zoomed = zoomed.upgrade(cx)?;
+
+                                        let mut background_style =
+                                            theme.workspace.zoomed_background;
+                                        match self.zoomed_position {
+                                            Some(DockPosition::Left) => {
+                                                background_style.padding.left = 0.;
+                                                background_style.padding.top = 0.;
+                                                background_style.padding.bottom = 0.;
+                                                background_style.padding.right *= 1.;
+                                            }
+                                            Some(DockPosition::Right) => {
+                                                background_style.padding.right = 0.;
+                                                background_style.padding.top = 0.;
+                                                background_style.padding.bottom = 0.;
+                                                background_style.padding.left *= 1.;
+                                            }
+                                            Some(DockPosition::Bottom) => {
+                                                background_style.padding.left = 0.;
+                                                background_style.padding.right = 0.;
+                                                background_style.padding.bottom = 0.;
+                                                background_style.padding.top *= 1.;
+                                            }
+                                            None => {}
+                                        }
+
                                         Some(
                                             ChildView::new(&zoomed, cx)
                                                 .contained()
                                                 .with_style(theme.workspace.zoomed_foreground)
                                                 .aligned()
                                                 .contained()
-                                                .with_style(theme.workspace.zoomed_background)
+                                                .with_style(background_style)
                                                 .mouse::<ZoomBackground>(0)
                                                 .capture_all()
                                                 .on_down(
@@ -4436,6 +4524,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
@@ -4444,12 +4540,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
@@ -4457,18 +4555,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
@@ -4476,8 +4577,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());
         });
     }