Allow contacts popover to be activated even if app isn't foregrounded

Antonio Scandurra created

Change summary

crates/contacts_status_item/src/contacts_popover.rs     |  18 +
crates/contacts_status_item/src/contacts_status_item.rs |  22 +
crates/gpui/src/platform.rs                             |   8 
crates/gpui/src/platform/mac/window.rs                  | 135 ++++++----
crates/zed/src/zed.rs                                   |   5 
5 files changed, 118 insertions(+), 70 deletions(-)

Detailed changes

crates/contacts_status_item/src/contacts_popover.rs 🔗

@@ -1,9 +1,13 @@
-use gpui::{color::Color, elements::*, Entity, RenderContext, View};
+use gpui::{color::Color, elements::*, Entity, RenderContext, View, ViewContext};
+
+pub enum Event {
+    Deactivated,
+}
 
 pub struct ContactsPopover;
 
 impl Entity for ContactsPopover {
-    type Event = ();
+    type Event = Event;
 }
 
 impl View for ContactsPopover {
@@ -20,7 +24,15 @@ impl View for ContactsPopover {
 }
 
 impl ContactsPopover {
-    pub fn new() -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        cx.observe_window_activation(Self::window_activation_changed)
+            .detach();
         Self
     }
+
+    fn window_activation_changed(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
+        if !is_active {
+            cx.emit(Event::Deactivated);
+        }
+    }
 }

crates/contacts_status_item/src/contacts_status_item.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     Appearance, Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext,
-    ViewHandle,
+    ViewHandle, WindowKind,
 };
 
 actions!(contacts_status_item, [ToggleContactsPopover]);
@@ -67,12 +67,28 @@ impl ContactsStatusItem {
                         bounds: gpui::WindowBounds::Fixed(RectF::new(origin, size)),
                         titlebar: None,
                         center: false,
-                        level: gpui::WindowLevel::PopUp,
+                        kind: WindowKind::PopUp,
+                        is_movable: false,
                     },
-                    |_| ContactsPopover::new(),
+                    |cx| ContactsPopover::new(cx),
                 );
+                cx.subscribe(&popover, Self::on_popover_event).detach();
                 self.popover = Some(popover);
             }
         }
     }
+
+    fn on_popover_event(
+        &mut self,
+        popover: ViewHandle<ContactsPopover>,
+        event: &contacts_popover::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            contacts_popover::Event::Deactivated => {
+                self.popover.take();
+                cx.remove_window(popover.window_id());
+            }
+        }
+    }
 }

crates/gpui/src/platform.rs 🔗

@@ -142,7 +142,8 @@ pub struct WindowOptions<'a> {
     pub bounds: WindowBounds,
     pub titlebar: Option<TitlebarOptions<'a>>,
     pub center: bool,
-    pub level: WindowLevel,
+    pub kind: WindowKind,
+    pub is_movable: bool,
 }
 
 #[derive(Debug)]
@@ -167,7 +168,7 @@ impl Default for Appearance {
 }
 
 #[derive(Copy, Clone, Debug)]
-pub enum WindowLevel {
+pub enum WindowKind {
     Normal,
     PopUp,
 }
@@ -283,7 +284,8 @@ impl<'a> Default for WindowOptions<'a> {
                 traffic_light_position: Default::default(),
             }),
             center: false,
-            level: WindowLevel::Normal,
+            kind: WindowKind::Normal,
+            is_movable: true,
         }
     }
 }

crates/gpui/src/platform/mac/window.rs 🔗

@@ -12,7 +12,7 @@ use crate::{
         Event, WindowBounds,
     },
     InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
-    MouseMovedEvent, Scene, WindowLevel,
+    MouseMovedEvent, Scene, WindowKind,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -54,6 +54,7 @@ use std::{
 const WINDOW_STATE_IVAR: &str = "windowState";
 
 static mut WINDOW_CLASS: *const Class = ptr::null();
+static mut PANEL_CLASS: *const Class = ptr::null();
 static mut VIEW_CLASS: *const Class = ptr::null();
 
 #[allow(non_upper_case_globals)]
@@ -114,50 +115,8 @@ unsafe impl objc::Encode for NSRange {
 
 #[ctor]
 unsafe fn build_classes() {
-    WINDOW_CLASS = {
-        let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
-        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
-        decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
-        decl.add_method(
-            sel!(canBecomeMainWindow),
-            yes as extern "C" fn(&Object, Sel) -> BOOL,
-        );
-        decl.add_method(
-            sel!(canBecomeKeyWindow),
-            yes as extern "C" fn(&Object, Sel) -> BOOL,
-        );
-        decl.add_method(
-            sel!(sendEvent:),
-            send_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowDidResize:),
-            window_did_resize as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowWillEnterFullScreen:),
-            window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowWillExitFullScreen:),
-            window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowDidBecomeKey:),
-            window_did_change_key_status as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowDidResignKey:),
-            window_did_change_key_status as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(windowShouldClose:),
-            window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
-        );
-        decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
-        decl.register()
-    };
-
+    WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
+    PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
     VIEW_CLASS = {
         let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
         decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
@@ -286,6 +245,50 @@ unsafe fn build_classes() {
     };
 }
 
+unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
+    let mut decl = ClassDecl::new(name, superclass).unwrap();
+    decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+    decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
+    decl.add_method(
+        sel!(canBecomeMainWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(canBecomeKeyWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(sendEvent:),
+        send_event as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidResize:),
+        window_did_resize as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillEnterFullScreen:),
+        window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillExitFullScreen:),
+        window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidBecomeKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidResignKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowShouldClose:),
+        window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
+    );
+    decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+    decl.register()
+}
+
 pub struct Window(Rc<RefCell<WindowState>>);
 
 ///Used to track what the IME does when we send it a keystroke.
@@ -352,10 +355,21 @@ impl Window {
                     style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
                 }
             } else {
-                style_mask = NSWindowStyleMask::empty();
+                style_mask = NSWindowStyleMask::NSTitledWindowMask
+                    | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
             }
 
-            let native_window: id = msg_send![WINDOW_CLASS, alloc];
+            let native_window: id = match options.kind {
+                WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
+                WindowKind::PopUp => {
+                    #[allow(non_upper_case_globals)]
+                    const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
+                        unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
+
+                    style_mask |= NSWindowStyleMaskNonactivatingPanel;
+                    msg_send![PANEL_CLASS, alloc]
+                }
+            };
             let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
                 RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(),
                 style_mask,
@@ -425,13 +439,17 @@ impl Window {
                 Rc::into_raw(window.0.clone()) as *const c_void,
             );
 
-            if let Some(titlebar) = options.titlebar {
-                if let Some(title) = titlebar.title {
-                    native_window.setTitle_(NSString::alloc(nil).init_str(title));
-                }
-                if titlebar.appears_transparent {
-                    native_window.setTitlebarAppearsTransparent_(YES);
-                }
+            if let Some(title) = options.titlebar.as_ref().and_then(|t| t.title) {
+                native_window.setTitle_(NSString::alloc(nil).init_str(title));
+            }
+
+            native_window.setMovable_(options.is_movable);
+
+            if options
+                .titlebar
+                .map_or(true, |titlebar| titlebar.appears_transparent)
+            {
+                native_window.setTitlebarAppearsTransparent_(YES);
             }
 
             native_window.setAcceptsMouseMovedEvents_(YES);
@@ -458,11 +476,10 @@ impl Window {
             }
 
             native_window.makeKeyAndOrderFront_(nil);
-            let native_level = match options.level {
-                WindowLevel::Normal => NSNormalWindowLevel,
-                WindowLevel::PopUp => NSPopUpWindowLevel,
-            };
-            native_window.setLevel_(native_level);
+            match options.kind {
+                WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel),
+                WindowKind::PopUp => native_window.setLevel_(NSPopUpWindowLevel),
+            }
 
             window.0.borrow().move_traffic_light();
             pool.drain();

crates/zed/src/zed.rs 🔗

@@ -20,7 +20,7 @@ use gpui::{
     geometry::vector::vec2f,
     impl_actions,
     platform::{WindowBounds, WindowOptions},
-    AssetSource, AsyncAppContext, TitlebarOptions, ViewContext, WindowLevel,
+    AssetSource, AsyncAppContext, TitlebarOptions, ViewContext, WindowKind,
 };
 use language::Rope;
 pub use lsp;
@@ -336,7 +336,8 @@ pub fn build_window_options() -> WindowOptions<'static> {
             traffic_light_position: Some(vec2f(8., 8.)),
         }),
         center: false,
-        level: WindowLevel::Normal,
+        kind: WindowKind::Normal,
+        is_movable: true,
     }
 }