Don't rely on action propagation for zooming in and out

Antonio Scandurra and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

crates/gpui/src/app.rs                     |  6 ++
crates/project_panel/src/project_panel.rs  |  8 ++
crates/terminal_view/src/terminal_panel.rs | 16 +++++
crates/workspace/src/dock.rs               | 67 ++++++++++++++---------
crates/workspace/src/pane.rs               |  6 +
crates/workspace/src/workspace.rs          | 55 ++++++++-----------
6 files changed, 94 insertions(+), 64 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -3972,6 +3972,12 @@ impl Clone for AnyViewHandle {
     }
 }
 
+impl PartialEq for AnyViewHandle {
+    fn eq(&self, other: &Self) -> bool {
+        self.window_id == other.window_id && self.view_id == other.view_id
+    }
+}
+
 impl<T> PartialEq<ViewHandle<T>> for AnyViewHandle {
     fn eq(&self, other: &ViewHandle<T>) -> bool {
         self.window_id == other.window_id && self.view_id == other.view_id

crates/project_panel/src/project_panel.rs 🔗

@@ -1373,10 +1373,16 @@ impl workspace::dock::Panel for ProjectPanel {
         cx.global::<Settings>().project_panel.default_width
     }
 
-    fn can_zoom(&self, _cx: &gpui::WindowContext) -> bool {
+    fn should_zoom_in_on_event(_: &Self::Event) -> bool {
         false
     }
 
+    fn should_zoom_out_on_event(_: &Self::Event) -> bool {
+        false
+    }
+
+    fn set_zoomed(&mut self, _: bool, _: &mut ViewContext<Self>) {}
+
     fn icon_path(&self) -> &'static str {
         "icons/folder_tree_16.svg"
     }

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -18,6 +18,8 @@ pub fn init(cx: &mut AppContext) {
 pub enum Event {
     Close,
     DockPositionChanged,
+    ZoomIn,
+    ZoomOut,
 }
 
 pub struct TerminalPanel {
@@ -96,6 +98,8 @@ impl TerminalPanel {
     ) {
         match event {
             pane::Event::Remove => cx.emit(Event::Close),
+            pane::Event::ZoomIn => cx.emit(Event::ZoomIn),
+            pane::Event::ZoomOut => cx.emit(Event::ZoomOut),
             _ => {}
         }
     }
@@ -187,8 +191,16 @@ impl Panel for TerminalPanel {
         }
     }
 
-    fn can_zoom(&self, _: &WindowContext) -> bool {
-        true
+    fn should_zoom_in_on_event(event: &Event) -> bool {
+        matches!(event, Event::ZoomIn)
+    }
+
+    fn should_zoom_out_on_event(event: &Event) -> bool {
+        matches!(event, Event::ZoomOut)
+    }
+
+    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
+        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
     }
 
     fn icon_path(&self) -> &'static str {

crates/workspace/src/dock.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{StatusItemView, ToggleZoom, Workspace};
+use crate::{StatusItemView, Workspace};
 use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
     elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
@@ -9,22 +9,20 @@ use serde::Deserialize;
 use settings::Settings;
 use std::rc::Rc;
 
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(Dock::toggle_zoom);
-}
-
 pub trait Panel: View {
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition) -> bool;
     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
     fn default_size(&self, cx: &WindowContext) -> f32;
-    fn can_zoom(&self, cx: &WindowContext) -> bool;
     fn icon_path(&self) -> &'static str;
     fn icon_tooltip(&self) -> String;
     fn icon_label(&self, _: &AppContext) -> Option<String> {
         None
     }
     fn should_change_position_on_event(_: &Self::Event) -> bool;
+    fn should_zoom_in_on_event(_: &Self::Event) -> bool;
+    fn should_zoom_out_on_event(_: &Self::Event) -> bool;
+    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>);
     fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool;
     fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool;
 }
@@ -34,8 +32,8 @@ pub trait PanelHandle {
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
     fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
+    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
     fn default_size(&self, cx: &WindowContext) -> f32;
-    fn can_zoom(&self, cx: &WindowContext) -> bool;
     fn icon_path(&self, cx: &WindowContext) -> &'static str;
     fn icon_tooltip(&self, cx: &WindowContext) -> String;
     fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -67,8 +65,8 @@ where
         self.read(cx).default_size(cx)
     }
 
-    fn can_zoom(&self, cx: &WindowContext) -> bool {
-        self.read(cx).can_zoom(cx)
+    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
     }
 
     fn icon_path(&self, cx: &WindowContext) -> &'static str {
@@ -98,10 +96,6 @@ impl From<&dyn PanelHandle> for AnyViewHandle {
     }
 }
 
-pub enum Event {
-    ZoomIn,
-}
-
 pub struct Dock {
     position: DockPosition,
     panel_entries: Vec<PanelEntry>,
@@ -192,22 +186,33 @@ impl Dock {
         cx.notify();
     }
 
-    pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        for (ix, entry) in self.panel_entries.iter_mut().enumerate() {
-            if ix == self.active_panel_index && entry.panel.can_zoom(cx) {
-                entry.zoomed = zoomed;
-            } else {
+    pub fn set_panel_zoomed(
+        &mut self,
+        panel: &AnyViewHandle,
+        zoomed: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
+        for entry in &mut self.panel_entries {
+            if entry.panel.as_any() == panel {
+                if zoomed != entry.zoomed {
+                    entry.zoomed = zoomed;
+                    entry.panel.set_zoomed(zoomed, cx);
+                }
+            } else if entry.zoomed {
                 entry.zoomed = false;
+                entry.panel.set_zoomed(false, cx);
             }
         }
 
         cx.notify();
     }
 
-    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        cx.propagate_action();
-        if !self.active_entry().map_or(false, |entry| entry.zoomed) {
-            cx.emit(Event::ZoomIn);
+    pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
+        for entry in &mut self.panel_entries {
+            if entry.zoomed {
+                entry.zoomed = false;
+                entry.panel.set_zoomed(false, cx);
+            }
         }
     }
 
@@ -342,7 +347,7 @@ impl Dock {
 }
 
 impl Entity for Dock {
-    type Event = Event;
+    type Event = ();
 }
 
 impl View for Dock {
@@ -568,6 +573,10 @@ pub(crate) mod test {
             cx.emit(TestPanelEvent::PositionChanged);
         }
 
+        fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {
+            unimplemented!()
+        }
+
         fn default_size(&self, _: &WindowContext) -> f32 {
             match self.position.axis() {
                 Axis::Horizontal => 300.,
@@ -575,10 +584,6 @@ pub(crate) mod test {
             }
         }
 
-        fn can_zoom(&self, _cx: &WindowContext) -> bool {
-            unimplemented!()
-        }
-
         fn icon_path(&self) -> &'static str {
             "icons/test_panel.svg"
         }
@@ -591,6 +596,14 @@ pub(crate) mod test {
             matches!(event, TestPanelEvent::PositionChanged)
         }
 
+        fn should_zoom_in_on_event(_: &Self::Event) -> bool {
+            false
+        }
+
+        fn should_zoom_out_on_event(_: &Self::Event) -> bool {
+            false
+        }
+
         fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool {
             matches!(event, TestPanelEvent::Activated)
         }

crates/workspace/src/pane.rs 🔗

@@ -135,6 +135,7 @@ pub enum Event {
     ChangeItemTitle,
     Focus,
     ZoomIn,
+    ZoomOut,
 }
 
 pub struct Pane {
@@ -661,8 +662,9 @@ impl Pane {
     }
 
     pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        cx.propagate_action();
-        if !self.zoomed {
+        if self.zoomed {
+            cx.emit(Event::ZoomOut);
+        } else {
             cx.emit(Event::ZoomIn);
         }
     }

crates/workspace/src/workspace.rs 🔗

@@ -182,7 +182,6 @@ pub type WorkspaceId = i64;
 impl_actions!(workspace, [ActivatePane]);
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    dock::init(cx);
     pane::init(cx);
     notifications::init(cx);
 
@@ -232,7 +231,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
         },
     );
     cx.add_action(Workspace::toggle_panel);
-    cx.add_action(Workspace::toggle_zoom);
     cx.add_action(Workspace::focus_center);
     cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
         workspace.activate_previous_pane(cx)
@@ -595,7 +593,7 @@ impl Workspace {
             active_call = Some((call, subscriptions));
         }
 
-        let mut subscriptions = vec![
+        let subscriptions = vec![
             cx.observe_fullscreen(|_, _, cx| cx.notify()),
             cx.observe_window_activation(Self::on_window_activation_changed),
             cx.observe_window_bounds(move |_, mut bounds, display, cx| {
@@ -615,10 +613,19 @@ impl Workspace {
                     .spawn(DB.set_window_bounds(workspace_id, bounds, display))
                     .detach_and_log_err(cx);
             }),
+            cx.observe(&left_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
+            cx.observe(&bottom_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
+            cx.observe(&right_dock, |this, _, cx| {
+                this.serialize_workspace(cx);
+                cx.notify();
+            }),
         ];
-        subscriptions.extend(Self::register_dock(&left_dock, cx));
-        subscriptions.extend(Self::register_dock(&bottom_dock, cx));
-        subscriptions.extend(Self::register_dock(&right_dock, cx));
 
         let mut this = Workspace {
             weak_self: weak_handle.clone(),
@@ -881,6 +888,11 @@ impl Workspace {
                             dock.activate_panel(dock.panels_len() - 1, cx);
                         }
                     });
+                } 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));
+                } else if T::should_zoom_out_on_event(event) {
+                    this.zoom_out(cx);
                 }
             }
         })
@@ -1464,23 +1476,14 @@ impl Workspace {
         cx.notify();
     }
 
-    fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        // Any time the zoom is toggled we will zoom out all panes and docks. Then,
-        // the dock or pane that was zoomed will emit an event to zoom itself back in.
-        self.zoom_out(cx);
-    }
-
     fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
         for pane in &self.panes {
             pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
         }
 
-        self.left_dock
-            .update(cx, |dock, cx| dock.set_zoomed(false, cx));
-        self.bottom_dock
-            .update(cx, |dock, cx| dock.set_zoomed(false, cx));
-        self.right_dock
-            .update(cx, |dock, cx| dock.set_zoomed(false, cx));
+        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
+        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
 
         cx.notify();
     }
@@ -1490,20 +1493,6 @@ impl Workspace {
         cx.notify();
     }
 
-    fn register_dock(dock: &ViewHandle<Dock>, cx: &mut ViewContext<Self>) -> [Subscription; 2] {
-        [
-            cx.observe(dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.subscribe(dock, |_, dock, event, cx| {
-                dock.update(cx, |dock, cx| match event {
-                    dock::Event::ZoomIn => dock.set_zoomed(true, cx),
-                })
-            }),
-        ]
-    }
-
     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
         let pane =
             cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx));
@@ -1727,9 +1716,11 @@ impl Workspace {
                 self.handle_pane_focused(pane.clone(), cx);
             }
             pane::Event::ZoomIn => {
+                self.zoom_out(cx);
                 pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
                 cx.notify();
             }
+            pane::Event::ZoomOut => self.zoom_out(cx),
         }
 
         self.serialize_workspace(cx);