Let WindowContext::dispatch_action handle global actions

Piotr Osiewicz and Antonio created

Co-authored-by: Antonio <antonio@zed.dev>

Change summary

crates/gpui2/src/app.rs          | 82 ++++++++++++++-------------------
crates/gpui2/src/key_dispatch.rs | 20 +++----
crates/gpui2/src/window.rs       | 16 ++++++
3 files changed, 60 insertions(+), 58 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -1040,20 +1040,10 @@ impl AppContext {
 
     pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
         if let Some(window) = self.active_window() {
-            let window_action_available = window
-                .update(self, |_, cx| {
-                    if let Some(focus_id) = cx.window.focus {
-                        cx.window
-                            .current_frame
-                            .dispatch_tree
-                            .is_action_available(action, focus_id)
-                    } else {
-                        false
-                    }
-                })
-                .unwrap_or(false);
-            if window_action_available {
-                return true;
+            if let Ok(window_action_available) =
+                window.update(self, |_, cx| cx.is_action_available(action))
+            {
+                return window_action_available;
             }
         }
 
@@ -1075,44 +1065,19 @@ impl AppContext {
     }
 
     pub fn dispatch_action(&mut self, action: &dyn Action) {
-        self.propagate_event = true;
-
-        if let Some(mut global_listeners) = self
-            .global_action_listeners
-            .remove(&action.as_any().type_id())
-        {
-            for listener in &global_listeners {
-                listener(action, DispatchPhase::Capture, self);
-                if !self.propagate_event {
-                    break;
-                }
-            }
-
-            global_listeners.extend(
-                self.global_action_listeners
-                    .remove(&action.as_any().type_id())
-                    .unwrap_or_default(),
-            );
-
-            self.global_action_listeners
-                .insert(action.as_any().type_id(), global_listeners);
-        }
-
-        if self.propagate_event {
-            if let Some(active_window) = self.active_window() {
-                active_window
-                    .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
-                    .log_err();
-            }
-        }
+        if let Some(active_window) = self.active_window() {
+            active_window
+                .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
+                .log_err();
+        } else {
+            self.propagate_event = true;
 
-        if self.propagate_event {
             if let Some(mut global_listeners) = self
                 .global_action_listeners
                 .remove(&action.as_any().type_id())
             {
-                for listener in global_listeners.iter().rev() {
-                    listener(action, DispatchPhase::Bubble, self);
+                for listener in &global_listeners {
+                    listener(action.as_any(), DispatchPhase::Capture, self);
                     if !self.propagate_event {
                         break;
                     }
@@ -1127,6 +1092,29 @@ impl AppContext {
                 self.global_action_listeners
                     .insert(action.as_any().type_id(), global_listeners);
             }
+
+            if self.propagate_event {
+                if let Some(mut global_listeners) = self
+                    .global_action_listeners
+                    .remove(&action.as_any().type_id())
+                {
+                    for listener in global_listeners.iter().rev() {
+                        listener(action.as_any(), DispatchPhase::Bubble, self);
+                        if !self.propagate_event {
+                            break;
+                        }
+                    }
+
+                    global_listeners.extend(
+                        self.global_action_listeners
+                            .remove(&action.as_any().type_id())
+                            .unwrap_or_default(),
+                    );
+
+                    self.global_action_listeners
+                        .insert(action.as_any().type_id(), global_listeners);
+                }
+            }
         }
     }
 }

crates/gpui2/src/key_dispatch.rs 🔗

@@ -161,17 +161,15 @@ impl DispatchTree {
         actions
     }
 
-    pub fn is_action_available(&self, action: &dyn Action, target: FocusId) -> bool {
-        if let Some(node) = self.focusable_node_ids.get(&target) {
-            for node_id in self.dispatch_path(*node) {
-                let node = &self.nodes[node_id.0];
-                if node
-                    .action_listeners
-                    .iter()
-                    .any(|listener| listener.action_type == action.as_any().type_id())
-                {
-                    return true;
-                }
+    pub fn is_action_available(&self, action: &dyn Action, target: DispatchNodeId) -> bool {
+        for node_id in self.dispatch_path(target) {
+            let node = &self.nodes[node_id.0];
+            if node
+                .action_listeners
+                .iter()
+                .any(|listener| listener.action_type == action.as_any().type_id())
+            {
+                return true;
             }
         }
         false

crates/gpui2/src/window.rs 🔗

@@ -804,6 +804,22 @@ impl<'a> WindowContext<'a> {
         );
     }
 
+    pub fn is_action_available(&self, action: &dyn Action) -> bool {
+        let target = self
+            .focused()
+            .and_then(|focused_handle| {
+                self.window
+                    .current_frame
+                    .dispatch_tree
+                    .focusable_node_id(focused_handle.id)
+            })
+            .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
+        self.window
+            .current_frame
+            .dispatch_tree
+            .is_action_available(action, target)
+    }
+
     /// The position of the mouse relative to the window.
     pub fn mouse_position(&self) -> Point<Pixels> {
         self.window.mouse_position