Merge pull request #1817 from zed-industries/show-notifications-on-all-screens

Antonio Scandurra created

Show call notifications on all screens

Change summary

crates/collab_ui/src/incoming_call_notification.rs  | 39 +++++----
crates/collab_ui/src/project_shared_notification.rs | 62 ++++++++------
crates/gpui/src/platform.rs                         | 11 ++
crates/gpui/src/platform/mac.rs                     |  1 
crates/gpui/src/platform/mac/platform.rs            | 20 ++--
crates/gpui/src/platform/mac/renderer.rs            | 10 ++
crates/gpui/src/platform/mac/screen.rs              | 44 ++++++++++
crates/gpui/src/platform/mac/window.rs              | 16 +++
crates/gpui/src/platform/test.rs                    |  4 
crates/zed/src/zed.rs                               |  1 
10 files changed, 145 insertions(+), 63 deletions(-)

Detailed changes

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -18,34 +18,37 @@ pub fn init(cx: &mut MutableAppContext) {
 
     let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
     cx.spawn(|mut cx| async move {
-        let mut notification_window = None;
+        let mut notification_windows = Vec::new();
         while let Some(incoming_call) = incoming_call.next().await {
-            if let Some(window_id) = notification_window.take() {
+            for window_id in notification_windows.drain(..) {
                 cx.remove_window(window_id);
             }
 
             if let Some(incoming_call) = incoming_call {
                 const PADDING: f32 = 16.;
-                let screen_size = cx.platform().screen_size();
-
                 let window_size = cx.read(|cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     vec2f(theme.window_width, theme.window_height)
                 });
-                let (window_id, _) = cx.add_window(
-                    WindowOptions {
-                        bounds: WindowBounds::Fixed(RectF::new(
-                            vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
-                            window_size,
-                        )),
-                        titlebar: None,
-                        center: false,
-                        kind: WindowKind::PopUp,
-                        is_movable: false,
-                    },
-                    |_| IncomingCallNotification::new(incoming_call),
-                );
-                notification_window = Some(window_id);
+
+                for screen in cx.platform().screens() {
+                    let screen_size = screen.size();
+                    let (window_id, _) = cx.add_window(
+                        WindowOptions {
+                            bounds: WindowBounds::Fixed(RectF::new(
+                                vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
+                                window_size,
+                            )),
+                            titlebar: None,
+                            center: false,
+                            kind: WindowKind::PopUp,
+                            is_movable: false,
+                            screen: Some(screen),
+                        },
+                        |_| IncomingCallNotification::new(incoming_call.clone()),
+                    );
+                    notification_windows.push(window_id);
+                }
             }
         }
     })

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -27,39 +27,49 @@ pub fn init(cx: &mut MutableAppContext) {
             worktree_root_names,
         } => {
             const PADDING: f32 = 16.;
-            let screen_size = cx.platform().screen_size();
-
             let theme = &cx.global::<Settings>().theme.project_shared_notification;
             let window_size = vec2f(theme.window_width, theme.window_height);
-            let (window_id, _) = cx.add_window(
-                WindowOptions {
-                    bounds: WindowBounds::Fixed(RectF::new(
-                        vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
-                        window_size,
-                    )),
-                    titlebar: None,
-                    center: false,
-                    kind: WindowKind::PopUp,
-                    is_movable: false,
-                },
-                |_| {
-                    ProjectSharedNotification::new(
-                        owner.clone(),
-                        *project_id,
-                        worktree_root_names.clone(),
-                    )
-                },
-            );
-            notification_windows.insert(*project_id, window_id);
+
+            for screen in cx.platform().screens() {
+                let screen_size = screen.size();
+                let (window_id, _) = cx.add_window(
+                    WindowOptions {
+                        bounds: WindowBounds::Fixed(RectF::new(
+                            vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
+                            window_size,
+                        )),
+                        titlebar: None,
+                        center: false,
+                        kind: WindowKind::PopUp,
+                        is_movable: false,
+                        screen: Some(screen),
+                    },
+                    |_| {
+                        ProjectSharedNotification::new(
+                            owner.clone(),
+                            *project_id,
+                            worktree_root_names.clone(),
+                        )
+                    },
+                );
+                notification_windows
+                    .entry(*project_id)
+                    .or_insert(Vec::new())
+                    .push(window_id);
+            }
         }
         room::Event::RemoteProjectUnshared { project_id } => {
-            if let Some(window_id) = notification_windows.remove(&project_id) {
-                cx.remove_window(window_id);
+            if let Some(window_ids) = notification_windows.remove(&project_id) {
+                for window_id in window_ids {
+                    cx.remove_window(window_id);
+                }
             }
         }
         room::Event::Left => {
-            for (_, window_id) in notification_windows.drain() {
-                cx.remove_window(window_id);
+            for (_, window_ids) in notification_windows.drain() {
+                for window_id in window_ids {
+                    cx.remove_window(window_id);
+                }
             }
         }
         _ => {}

crates/gpui/src/platform.rs 🔗

@@ -25,7 +25,7 @@ use postage::oneshot;
 use serde::Deserialize;
 use std::{
     any::Any,
-    fmt::{self, Display},
+    fmt::{self, Debug, Display},
     ops::Range,
     path::{Path, PathBuf},
     rc::Rc,
@@ -44,7 +44,7 @@ pub trait Platform: Send + Sync {
     fn unhide_other_apps(&self);
     fn quit(&self);
 
-    fn screen_size(&self) -> Vector2F;
+    fn screens(&self) -> Vec<Rc<dyn Screen>>;
 
     fn open_window(
         &self,
@@ -115,6 +115,11 @@ pub trait InputHandler {
     fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
 }
 
+pub trait Screen: Debug {
+    fn as_any(&self) -> &dyn Any;
+    fn size(&self) -> Vector2F;
+}
+
 pub trait Window {
     fn as_any_mut(&mut self) -> &mut dyn Any;
     fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
@@ -149,6 +154,7 @@ pub struct WindowOptions<'a> {
     pub center: bool,
     pub kind: WindowKind,
     pub is_movable: bool,
+    pub screen: Option<Rc<dyn Screen>>,
 }
 
 #[derive(Debug)]
@@ -292,6 +298,7 @@ impl<'a> Default for WindowOptions<'a> {
             center: false,
             kind: WindowKind::Normal,
             is_movable: true,
+            screen: None,
         }
     }
 }

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

@@ -1,10 +1,9 @@
 use super::{
-    event::key_to_native, status_item::StatusItem, BoolExt as _, Dispatcher, FontSystem, Window,
+    event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
+    FontSystem, Window,
 };
 use crate::{
-    executor,
-    geometry::vector::{vec2f, Vector2F},
-    keymap,
+    executor, keymap,
     platform::{self, CursorStyle},
     Action, AppVersion, ClipboardItem, Event, Menu, MenuItem,
 };
@@ -14,7 +13,7 @@ use cocoa::{
     appkit::{
         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
         NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
-        NSPasteboardTypeString, NSSavePanel, NSScreen, NSWindow,
+        NSPasteboardTypeString, NSSavePanel, NSWindow,
     },
     base::{id, nil, selector, YES},
     foundation::{
@@ -488,12 +487,11 @@ impl platform::Platform for MacPlatform {
         }
     }
 
-    fn screen_size(&self) -> Vector2F {
-        unsafe {
-            let screen = NSScreen::mainScreen(nil);
-            let frame = NSScreen::frame(screen);
-            vec2f(frame.size.width as f32, frame.size.height as f32)
-        }
+    fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
+        Screen::all()
+            .into_iter()
+            .map(|screen| Rc::new(screen) as Rc<_>)
+            .collect()
     }
 
     fn open_window(

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

@@ -189,7 +189,15 @@ impl Renderer {
     pub fn render(&mut self, scene: &Scene) {
         let layer = self.layer.clone();
         let drawable_size = layer.drawable_size();
-        let drawable = layer.next_drawable().unwrap();
+        let drawable = if let Some(drawable) = layer.next_drawable() {
+            drawable
+        } else {
+            log::error!(
+                "failed to retrieve next drawable, drawable size: {:?}",
+                drawable_size
+            );
+            return;
+        };
         let command_queue = self.command_queue.clone();
         let command_buffer = command_queue.new_command_buffer();
 

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

@@ -0,0 +1,44 @@
+use std::any::Any;
+
+use crate::{
+    geometry::vector::{vec2f, Vector2F},
+    platform,
+};
+use cocoa::{
+    appkit::NSScreen,
+    base::{id, nil},
+    foundation::NSArray,
+};
+
+#[derive(Debug)]
+pub struct Screen {
+    pub(crate) native_screen: id,
+}
+
+impl Screen {
+    pub fn all() -> Vec<Self> {
+        let mut screens = Vec::new();
+        unsafe {
+            let native_screens = NSScreen::screens(nil);
+            for ix in 0..native_screens.count() {
+                screens.push(Screen {
+                    native_screen: native_screens.objectAtIndex(ix),
+                });
+            }
+        }
+        screens
+    }
+}
+
+impl platform::Screen for Screen {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn size(&self) -> Vector2F {
+        unsafe {
+            let frame = self.native_screen.frame();
+            vec2f(frame.size.width as f32, frame.size.height as f32)
+        }
+    }
+}

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

@@ -8,7 +8,7 @@ use crate::{
     mac::platform::NSViewLayerContentsRedrawDuringViewResize,
     platform::{
         self,
-        mac::{geometry::RectFExt, renderer::Renderer},
+        mac::{geometry::RectFExt, renderer::Renderer, screen::Screen},
         Event, WindowBounds,
     },
     InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
@@ -377,11 +377,17 @@ impl Window {
                     msg_send![PANEL_CLASS, alloc]
                 }
             };
-            let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
+            let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
                 RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(),
                 style_mask,
                 NSBackingStoreBuffered,
                 NO,
+                options
+                    .screen
+                    .and_then(|screen| {
+                        Some(screen.as_any().downcast_ref::<Screen>()?.native_screen)
+                    })
+                    .unwrap_or(nil),
             );
             assert!(!native_window.is_null());
 
@@ -400,8 +406,12 @@ impl Window {
                                 - top_left_bounds.height(),
                         ),
                         top_left_bounds.size(),
+                    )
+                    .to_ns_rect();
+                    native_window.setFrame_display_(
+                        native_window.convertRectToScreen_(bottom_left_bounds),
+                        YES,
                     );
-                    native_window.setFrame_display_(bottom_left_bounds.to_ns_rect(), YES);
                 }
             }
 

crates/gpui/src/platform/test.rs 🔗

@@ -131,8 +131,8 @@ impl super::Platform for Platform {
 
     fn quit(&self) {}
 
-    fn screen_size(&self) -> Vector2F {
-        vec2f(1024., 768.)
+    fn screens(&self) -> Vec<Rc<dyn crate::Screen>> {
+        Default::default()
     }
 
     fn open_window(

crates/zed/src/zed.rs 🔗

@@ -344,6 +344,7 @@ pub fn build_window_options() -> WindowOptions<'static> {
         center: false,
         kind: WindowKind::Normal,
         is_movable: true,
+        screen: None,
     }
 }