Re-focus window on workspace on corresponding window blur

Kirill Bulatov and Antonio created

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

Change summary

crates/gpui2/src/app.rs             | 94 +++++++++++++++---------------
crates/gpui2/src/window.rs          | 44 ++++++++++---
crates/workspace2/src/workspace2.rs |  2 
3 files changed, 82 insertions(+), 58 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -569,59 +569,61 @@ impl AppContext {
     /// such as notifying observers, emitting events, etc. Effects can themselves
     /// cause effects, so we continue looping until all effects are processed.
     fn flush_effects(&mut self) {
-        loop {
-            self.release_dropped_entities();
-            self.release_dropped_focus_handles();
-            if let Some(effect) = self.pending_effects.pop_front() {
-                match effect {
-                    Effect::Notify { emitter } => {
-                        self.apply_notify_effect(emitter);
-                    }
-                    Effect::Emit {
-                        emitter,
-                        event_type,
-                        event,
-                    } => self.apply_emit_effect(emitter, event_type, event),
-                    Effect::FocusChanged {
-                        window_handle,
-                        focused,
-                    } => {
-                        self.apply_focus_changed_effect(window_handle, focused);
-                    }
-                    Effect::Refresh => {
-                        self.apply_refresh_effect();
-                    }
-                    Effect::NotifyGlobalObservers { global_type } => {
-                        self.apply_notify_global_observers_effect(global_type);
-                    }
-                    Effect::Defer { callback } => {
-                        self.apply_defer_effect(callback);
+        while !self.pending_effects.is_empty() {
+            loop {
+                self.release_dropped_entities();
+                self.release_dropped_focus_handles();
+                if let Some(effect) = self.pending_effects.pop_front() {
+                    match effect {
+                        Effect::Notify { emitter } => {
+                            self.apply_notify_effect(emitter);
+                        }
+                        Effect::Emit {
+                            emitter,
+                            event_type,
+                            event,
+                        } => self.apply_emit_effect(emitter, event_type, event),
+                        Effect::FocusChanged {
+                            window_handle,
+                            focused,
+                        } => {
+                            self.apply_focus_changed_effect(window_handle, focused);
+                        }
+                        Effect::Refresh => {
+                            self.apply_refresh_effect();
+                        }
+                        Effect::NotifyGlobalObservers { global_type } => {
+                            self.apply_notify_global_observers_effect(global_type);
+                        }
+                        Effect::Defer { callback } => {
+                            self.apply_defer_effect(callback);
+                        }
                     }
+                } else {
+                    break;
                 }
-            } else {
-                break;
             }
-        }
 
-        for window in self.windows.values() {
-            if let Some(window) = window.as_ref() {
-                if window.dirty {
-                    window.platform_window.invalidate();
+            for window in self.windows.values() {
+                if let Some(window) = window.as_ref() {
+                    if window.dirty {
+                        window.platform_window.invalidate();
+                    }
                 }
             }
-        }
 
-        #[cfg(any(test, feature = "test-support"))]
-        for window in self
-            .windows
-            .values()
-            .filter_map(|window| {
-                let window = window.as_ref()?;
-                window.dirty.then_some(window.handle)
-            })
-            .collect::<Vec<_>>()
-        {
-            self.update_window(window, |_, cx| cx.draw()).unwrap();
+            #[cfg(any(test, feature = "test-support"))]
+            for window in self
+                .windows
+                .values()
+                .filter_map(|window| {
+                    let window = window.as_ref()?;
+                    window.dirty.then_some(window.handle)
+                })
+                .collect::<Vec<_>>()
+            {
+                self.update_window(window, |_, cx| cx.draw()).unwrap();
+            }
         }
     }
 

crates/gpui2/src/window.rs 🔗

@@ -229,6 +229,7 @@ pub struct Window {
     pub(crate) next_frame: Frame,
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
     pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
+    pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
     requested_cursor_style: Option<CursorStyle>,
@@ -362,6 +363,7 @@ impl Window {
             next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
             focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             focus_listeners: SubscriberSet::new(),
+            blur_listeners: SubscriberSet::new(),
             default_prevented: true,
             mouse_position,
             requested_cursor_style: None,
@@ -1235,6 +1237,16 @@ impl<'a> WindowContext<'a> {
 
     /// Draw pixels to the display for this window based on the contents of its scene.
     pub(crate) fn draw(&mut self) -> Scene {
+        let window_was_focused = self
+            .window
+            .focus
+            .and_then(|focus_id| {
+                self.window
+                    .rendered_frame
+                    .dispatch_tree
+                    .focusable_node_id(focus_id)
+            })
+            .is_some();
         self.text_system().start_frame();
         self.window.platform_window.clear_input_handler();
         self.window.layout_engine.as_mut().unwrap().clear();
@@ -1273,6 +1285,23 @@ impl<'a> WindowContext<'a> {
             });
         }
 
+        let window_is_focused = self
+            .window
+            .focus
+            .and_then(|focus_id| {
+                self.window
+                    .next_frame
+                    .dispatch_tree
+                    .focusable_node_id(focus_id)
+            })
+            .is_some();
+        if window_was_focused && !window_is_focused {
+            self.window
+                .blur_listeners
+                .clone()
+                .retain(&(), |listener| listener(self));
+        }
+
         self.window
             .next_frame
             .dispatch_tree
@@ -2423,23 +2452,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     /// Register a listener to be called when the window loses focus.
     /// Unlike [on_focus_changed], returns a subscription and persists until the subscription
     /// is dropped.
-    pub fn on_window_focus_lost(
+    pub fn on_blur_window(
         &mut self,
         mut listener: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
     ) -> Subscription {
         let view = self.view.downgrade();
-        let (subscription, activate) = self.window.focus_listeners.insert(
+        let (subscription, activate) = self.window.blur_listeners.insert(
             (),
-            Box::new(move |event, cx| {
-                view.update(cx, |view, cx| {
-                    if event.blurred.is_none() && event.focused.is_none() {
-                        listener(view, cx)
-                    }
-                })
-                .is_ok()
-            }),
+            Box::new(move |cx| view.update(cx, |view, cx| listener(view, cx)).is_ok()),
         );
-        self.app.defer(move |_| activate());
+        activate();
         subscription
     }
 

crates/workspace2/src/workspace2.rs 🔗

@@ -532,7 +532,7 @@ impl Workspace {
             cx.notify()
         })
         .detach();
-        cx.on_window_focus_lost(|this, cx| {
+        cx.on_blur_window(|this, cx| {
             let focus_handle = this.focus_handle(cx);
             cx.focus(&focus_handle);
         })