Style incoming call notification

Antonio Scandurra created

Change summary

crates/collab_ui/src/incoming_call_notification.rs | 85 ++++++++++++---
crates/gpui/src/platform.rs                        |  2 
crates/gpui/src/platform/mac/platform.rs           | 14 ++
crates/gpui/src/platform/test.rs                   |  4 
crates/theme/src/theme.rs                          |  6 +
styles/src/styleTree/incomingCallNotification.ts   | 28 ++++-
6 files changed, 111 insertions(+), 28 deletions(-)

Detailed changes

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -3,8 +3,8 @@ use futures::StreamExt;
 use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
-    impl_internal_actions, Entity, MouseButton, MutableAppContext, RenderContext, View,
-    ViewContext, WindowBounds, WindowKind, WindowOptions,
+    impl_internal_actions, CursorStyle, Entity, MouseButton, MutableAppContext, RenderContext,
+    View, ViewContext, WindowBounds, WindowKind, WindowOptions,
 };
 use settings::Settings;
 use util::ResultExt;
@@ -24,11 +24,17 @@ pub fn init(cx: &mut MutableAppContext) {
             }
 
             if let Some(incoming_call) = incoming_call {
+                const PADDING: f32 = 16.;
+                let screen_size = cx.platform().screen_size();
+                let window_size = vec2f(304., 64.);
                 let (window_id, _) = cx.add_window(
                     WindowOptions {
-                        bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(300., 400.))),
+                        bounds: WindowBounds::Fixed(RectF::new(
+                            vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
+                            window_size,
+                        )),
                         titlebar: None,
-                        center: true,
+                        center: false,
                         kind: WindowKind::PopUp,
                         is_movable: false,
                     },
@@ -84,22 +90,40 @@ impl IncomingCallNotification {
     fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
         let theme = &cx.global::<Settings>().theme.incoming_call_notification;
         Flex::row()
-            .with_children(
-                self.call
-                    .caller
-                    .avatar
-                    .clone()
-                    .map(|avatar| Image::new(avatar).with_style(theme.caller_avatar).boxed()),
-            )
+            .with_children(self.call.caller.avatar.clone().map(|avatar| {
+                Image::new(avatar)
+                    .with_style(theme.caller_avatar)
+                    .aligned()
+                    .boxed()
+            }))
             .with_child(
-                Label::new(
-                    self.call.caller.github_login.clone(),
-                    theme.caller_username.text.clone(),
-                )
-                .contained()
-                .with_style(theme.caller_username.container)
-                .boxed(),
+                Flex::column()
+                    .with_child(
+                        Label::new(
+                            self.call.caller.github_login.clone(),
+                            theme.caller_username.text.clone(),
+                        )
+                        .contained()
+                        .with_style(theme.caller_username.container)
+                        .boxed(),
+                    )
+                    .with_child(
+                        Label::new(
+                            "Incoming Zed call...".into(),
+                            theme.caller_message.text.clone(),
+                        )
+                        .contained()
+                        .with_style(theme.caller_message.container)
+                        .boxed(),
+                    )
+                    .contained()
+                    .with_style(theme.caller_metadata)
+                    .aligned()
+                    .boxed(),
             )
+            .contained()
+            .with_style(theme.caller_container)
+            .flex(1., true)
             .boxed()
     }
 
@@ -107,33 +131,46 @@ impl IncomingCallNotification {
         enum Accept {}
         enum Decline {}
 
-        Flex::row()
+        Flex::column()
             .with_child(
                 MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Accept".to_string(), theme.accept_button.text.clone())
+                        .aligned()
                         .contained()
                         .with_style(theme.accept_button.container)
                         .boxed()
                 })
+                .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, |_, cx| {
                     cx.dispatch_action(RespondToCall { accept: true });
                 })
+                .flex(1., true)
                 .boxed(),
             )
             .with_child(
                 MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Decline".to_string(), theme.decline_button.text.clone())
+                        .aligned()
                         .contained()
                         .with_style(theme.decline_button.container)
                         .boxed()
                 })
+                .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, |_, cx| {
                     cx.dispatch_action(RespondToCall { accept: false });
                 })
+                .flex(1., true)
                 .boxed(),
             )
+            .constrained()
+            .with_width(
+                cx.global::<Settings>()
+                    .theme
+                    .incoming_call_notification
+                    .button_width,
+            )
             .boxed()
     }
 }
@@ -148,9 +185,17 @@ impl View for IncomingCallNotification {
     }
 
     fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
-        Flex::column()
+        let background = cx
+            .global::<Settings>()
+            .theme
+            .incoming_call_notification
+            .background;
+        Flex::row()
             .with_child(self.render_caller(cx))
             .with_child(self.render_buttons(cx))
+            .contained()
+            .with_background_color(background)
+            .expanded()
             .boxed()
     }
 }

crates/gpui/src/platform.rs 🔗

@@ -44,6 +44,8 @@ pub trait Platform: Send + Sync {
     fn unhide_other_apps(&self);
     fn quit(&self);
 
+    fn screen_size(&self) -> Vector2F;
+
     fn open_window(
         &self,
         id: usize,

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

@@ -2,7 +2,9 @@ use super::{
     event::key_to_native, status_item::StatusItem, BoolExt as _, Dispatcher, FontSystem, Window,
 };
 use crate::{
-    executor, keymap,
+    executor,
+    geometry::vector::{vec2f, Vector2F},
+    keymap,
     platform::{self, CursorStyle},
     Action, ClipboardItem, Event, Menu, MenuItem,
 };
@@ -12,7 +14,7 @@ use cocoa::{
     appkit::{
         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
         NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
-        NSPasteboardTypeString, NSSavePanel, NSWindow,
+        NSPasteboardTypeString, NSSavePanel, NSScreen, NSWindow,
     },
     base::{id, nil, selector, YES},
     foundation::{
@@ -485,6 +487,14 @@ 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 open_window(
         &self,
         id: usize,

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

@@ -131,6 +131,10 @@ impl super::Platform for Platform {
 
     fn quit(&self) {}
 
+    fn screen_size(&self) -> Vector2F {
+        vec2f(1024., 768.)
+    }
+
     fn open_window(
         &self,
         _: usize,

crates/theme/src/theme.rs 🔗

@@ -479,8 +479,14 @@ pub struct ProjectSharedNotification {
 
 #[derive(Deserialize, Default)]
 pub struct IncomingCallNotification {
+    #[serde(default)]
+    pub background: Color,
+    pub caller_container: ContainerStyle,
     pub caller_avatar: ImageStyle,
+    pub caller_metadata: ContainerStyle,
     pub caller_username: ContainedText,
+    pub caller_message: ContainedText,
+    pub button_width: f32,
     pub accept_button: ContainedText,
     pub decline_button: ContainedText,
 }

styles/src/styleTree/incomingCallNotification.ts 🔗

@@ -1,22 +1,38 @@
 import Theme from "../themes/common/theme";
-import { text } from "./components";
+import { backgroundColor, borderColor, text } from "./components";
 
 export default function incomingCallNotification(theme: Theme): Object {
-  const avatarSize = 12;
+  const avatarSize = 32;
   return {
+    background: backgroundColor(theme, 300),
+    callerContainer: {
+      padding: 12,
+    },
     callerAvatar: {
       height: avatarSize,
       width: avatarSize,
-      cornerRadius: 6,
+      cornerRadius: avatarSize / 2,
+    },
+    callerMetadata: {
+      margin: { left: 10 },
     },
     callerUsername: {
-      ...text(theme, "sans", "primary", { size: "xs" }),
+      ...text(theme, "sans", "active", { size: "sm", weight: "bold" }),
+      margin: { top: -3 },
+    },
+    callerMessage: {
+      ...text(theme, "sans", "secondary", { size: "xs" }),
+      margin: { top: -3 },
     },
+    buttonWidth: 96,
     acceptButton: {
-      ...text(theme, "sans", "primary", { size: "xs" })
+      background: backgroundColor(theme, "ok", "active"),
+      border: { left: true, bottom: true, width: 1, color: borderColor(theme, "primary") },
+      ...text(theme, "sans", "ok", { size: "xs", weight: "extra_bold" })
     },
     declineButton: {
-      ...text(theme, "sans", "primary", { size: "xs" })
+      border: { left: true, width: 1, color: borderColor(theme, "primary") },
+      ...text(theme, "sans", "error", { size: "xs", weight: "extra_bold" })
     },
   };
 }