Restore focus when closing context menu

Antonio Scandurra created

Change summary

Cargo.lock                              |  1 
crates/context_menu/src/context_menu.rs | 51 +++++++++++++++++++-------
crates/gpui/src/app.rs                  |  2 
crates/zed/Cargo.toml                   |  1 
crates/zed/src/main.rs                  |  1 
5 files changed, 41 insertions(+), 15 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6088,6 +6088,7 @@ dependencies = [
  "collections",
  "command_palette",
  "contacts_panel",
+ "context_menu",
  "ctor",
  "diagnostics",
  "dirs 3.0.1",

crates/context_menu/src/context_menu.rs 🔗

@@ -1,9 +1,18 @@
 use gpui::{
-    elements::*, geometry::vector::Vector2F, platform::CursorStyle, Action, Axis, Entity,
-    RenderContext, SizeConstraint, View, ViewContext,
+    elements::*, geometry::vector::Vector2F, impl_internal_actions, platform::CursorStyle, Action,
+    Axis, Entity, MutableAppContext, RenderContext, SizeConstraint, View, ViewContext,
 };
 use settings::Settings;
 
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_action(ContextMenu::dismiss);
+}
+
+#[derive(Clone)]
+struct Dismiss;
+
+impl_internal_actions!(context_menu, [Dismiss]);
+
 pub enum ContextMenuItem {
     Item {
         label: String,
@@ -25,11 +34,13 @@ impl ContextMenuItem {
     }
 }
 
+#[derive(Default)]
 pub struct ContextMenu {
     position: Vector2F,
     items: Vec<ContextMenuItem>,
     selected_index: Option<usize>,
     visible: bool,
+    previously_focused_view_id: Option<usize>,
 }
 
 impl Entity for ContextMenu {
@@ -72,11 +83,13 @@ impl View for ContextMenu {
 
 impl ContextMenu {
     pub fn new() -> Self {
-        Self {
-            position: Default::default(),
-            items: Default::default(),
-            selected_index: Default::default(),
-            visible: false,
+        Default::default()
+    }
+
+    fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+        if cx.handle().is_focused(cx) {
+            let window_id = cx.window_id();
+            (**cx).focus(window_id, self.previously_focused_view_id.take());
         }
     }
 
@@ -87,11 +100,15 @@ impl ContextMenu {
         cx: &mut ViewContext<Self>,
     ) {
         let mut items = items.into_iter().peekable();
-        assert!(items.peek().is_some(), "must have at least one item");
-        self.items = items.collect();
-        self.position = position;
-        self.visible = true;
-        cx.focus_self();
+        if items.peek().is_some() {
+            self.items = items.collect();
+            self.position = position;
+            self.visible = true;
+            self.previously_focused_view_id = cx.focused_view_id(cx.window_id());
+            cx.focus_self();
+        } else {
+            self.visible = false;
+        }
         cx.notify();
     }
 
@@ -107,7 +124,10 @@ impl ContextMenu {
                                     &Default::default(),
                                     Some(ix) == self.selected_index,
                                 );
-                                Label::new(label.to_string(), style.label.clone()).boxed()
+                                Label::new(label.to_string(), style.label.clone())
+                                    .contained()
+                                    .with_style(style.container)
+                                    .boxed()
                             }
                             ContextMenuItem::Separator => Empty::new()
                                 .collapsed()
@@ -180,7 +200,10 @@ impl ContextMenu {
                                 .boxed()
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()))
+                        .on_click(move |_, _, cx| {
+                            cx.dispatch_any_action(action.boxed_clone());
+                            cx.dispatch_action(Dismiss);
+                        })
                         .boxed()
                     }
                     ContextMenuItem::Separator => Empty::new()

crates/gpui/src/app.rs 🔗

@@ -2407,7 +2407,7 @@ impl MutableAppContext {
         })
     }
 
-    fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
+    pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
         if let Some(pending_focus_index) = self.pending_focus_index {
             self.pending_effects.remove(pending_focus_index);
         }

crates/zed/Cargo.toml 🔗

@@ -22,6 +22,7 @@ chat_panel = { path = "../chat_panel" }
 cli = { path = "../cli" }
 collections = { path = "../collections" }
 command_palette = { path = "../command_palette" }
+context_menu = { path = "../context_menu" }
 client = { path = "../client" }
 clock = { path = "../clock" }
 contacts_panel = { path = "../contacts_panel" }

crates/zed/src/main.rs 🔗

@@ -134,6 +134,7 @@ fn main() {
         let mut languages = languages::build_language_registry(login_shell_env_loaded);
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
+        context_menu::init(cx);
         auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
         project::Project::init(&client);
         client::Channel::init(&client);