Start emitting notifications for calls

Piotr Osiewicz created

Change summary

Cargo.lock                                                        |   1 
crates/collab_ui2/src/collab_ui.rs                                |  58 
crates/collab_ui2/src/notifications.rs                            |  16 
crates/collab_ui2/src/notifications/incoming_call_notification.rs | 346 
crates/zed2/Cargo.toml                                            |   2 
crates/zed2/src/main.rs                                           |   2 
6 files changed, 239 insertions(+), 186 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -11644,6 +11644,7 @@ dependencies = [
  "async-recursion 0.3.2",
  "async-tar",
  "async-trait",
+ "audio2",
  "auto_update2",
  "backtrace",
  "call2",

crates/collab_ui2/src/collab_ui.rs 🔗

@@ -7,11 +7,14 @@ pub mod notification_panel;
 pub mod notifications;
 mod panel_settings;
 
-use std::sync::Arc;
+use std::{rc::Rc, sync::Arc};
 
 pub use collab_panel::CollabPanel;
 pub use collab_titlebar_item::CollabTitlebarItem;
-use gpui::AppContext;
+use gpui::{
+    point, px, AppContext, GlobalPixels, Pixels, PlatformDisplay, Point, Size, WindowBounds,
+    WindowKind, WindowOptions,
+};
 pub use panel_settings::{
     ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
 };
@@ -23,7 +26,7 @@ use workspace::AppState;
 //     [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
 // );
 
-pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     CollaborationPanelSettings::register(cx);
     ChatPanelSettings::register(cx);
     NotificationPanelSettings::register(cx);
@@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
     collab_titlebar_item::init(cx);
     collab_panel::init(cx);
     // chat_panel::init(cx);
-    // notifications::init(&app_state, cx);
+    notifications::init(&app_state, cx);
 
     // cx.add_global_action(toggle_screen_sharing);
     // cx.add_global_action(toggle_mute);
@@ -95,31 +98,30 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
 //     }
 // }
 
-// fn notification_window_options(
-//     screen: Rc<dyn Screen>,
-//     window_size: Vector2F,
-// ) -> WindowOptions<'static> {
-//     const NOTIFICATION_PADDING: f32 = 16.;
+fn notification_window_options(
+    screen: Rc<dyn PlatformDisplay>,
+    window_size: Size<Pixels>,
+) -> WindowOptions {
+    let notification_padding = Pixels::from(16.);
 
-//     let screen_bounds = screen.content_bounds();
-//     WindowOptions {
-//         bounds: WindowBounds::Fixed(RectF::new(
-//             screen_bounds.upper_right()
-//                 + vec2f(
-//                     -NOTIFICATION_PADDING - window_size.x(),
-//                     NOTIFICATION_PADDING,
-//                 ),
-//             window_size,
-//         )),
-//         titlebar: None,
-//         center: false,
-//         focus: false,
-//         show: true,
-//         kind: WindowKind::PopUp,
-//         is_movable: false,
-//         screen: Some(screen),
-//     }
-// }
+    let screen_bounds = screen.bounds();
+    let size: Size<GlobalPixels> = window_size.into();
+
+    let bounds = gpui::Bounds::<GlobalPixels> {
+        origin: screen_bounds.origin,
+        size: window_size.into(),
+    };
+    WindowOptions {
+        bounds: WindowBounds::Fixed(bounds),
+        titlebar: None,
+        center: false,
+        focus: false,
+        show: true,
+        kind: WindowKind::PopUp,
+        is_movable: false,
+        display_id: Some(screen.id()),
+    }
+}
 
 // fn render_avatar<T: 'static>(
 //     avatar: Option<Arc<ImageData>>,

crates/collab_ui2/src/notifications.rs 🔗

@@ -1,11 +1,11 @@
-// use gpui::AppContext;
-// use std::sync::Arc;
-// use workspace::AppState;
+use gpui::AppContext;
+use std::sync::Arc;
+use workspace::AppState;
 
-// pub mod incoming_call_notification;
+pub mod incoming_call_notification;
 // pub mod project_shared_notification;
 
-// pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
-//     incoming_call_notification::init(app_state, cx);
-//     project_shared_notification::init(app_state, cx);
-// }
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
+    incoming_call_notification::init(app_state, cx);
+    //project_shared_notification::init(app_state, cx);
+}

crates/collab_ui2/src/notifications/incoming_call_notification.rs 🔗

@@ -3,12 +3,12 @@ use call::{ActiveCall, IncomingCall};
 use client::proto;
 use futures::StreamExt;
 use gpui::{
-    elements::*,
-    geometry::vector::vec2f,
-    platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
+    blue, div, green, px, red, AnyElement, AppContext, Component, Context, Div, Element, Entity,
+    EventEmitter, GlobalPixels, ParentElement, Render, RenderOnce, StatefulInteractiveElement,
+    Styled, View, ViewContext, VisualContext as _, WindowHandle,
 };
 use std::sync::{Arc, Weak};
+use ui::{h_stack, v_stack, Avatar, Button, Label};
 use util::ResultExt;
 use workspace::AppState;
 
@@ -19,23 +19,44 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
         let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
         while let Some(incoming_call) = incoming_call.next().await {
             for window in notification_windows.drain(..) {
-                window.remove(&mut cx);
+                window.update(&mut cx, |this, cx| {
+                    //cx.remove_window();
+                });
+                //cx.update_window(window.into(), |this, cx| cx.remove_window());
+                //window.remove(&mut cx);
             }
 
             if let Some(incoming_call) = incoming_call {
-                let window_size = cx.read(|cx| {
-                    let theme = &theme::current(cx).incoming_call_notification;
-                    vec2f(theme.window_width, theme.window_height)
-                });
+                let unique_screens = cx.update(|cx| cx.displays()).unwrap();
+                let window_size = gpui::Size {
+                    width: px(380.),
+                    height: px(64.),
+                };
 
-                for screen in cx.platform().screens() {
+                for window in unique_screens {
+                    let options = notification_window_options(window, window_size);
+                    dbg!(&options);
                     let window = cx
-                        .add_window(notification_window_options(screen, window_size), |_| {
-                            IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
-                        });
-
+                        .open_window(options, |cx| {
+                            cx.build_view(|_| {
+                                IncomingCallNotification::new(
+                                    incoming_call.clone(),
+                                    app_state.clone(),
+                                )
+                            })
+                        })
+                        .unwrap();
                     notification_windows.push(window);
                 }
+
+                // for screen in cx.platform().screens() {
+                //     let window = cx
+                //         .add_window(notification_window_options(screen, window_size), |_| {
+                //             IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
+                //         });
+
+                //     notification_windows.push(window);
+                // }
             }
         }
     })
@@ -47,167 +68,196 @@ struct RespondToCall {
     accept: bool,
 }
 
-pub struct IncomingCallNotification {
+struct IncomingCallNotificationState {
     call: IncomingCall,
     app_state: Weak<AppState>,
 }
 
-impl IncomingCallNotification {
+pub struct IncomingCallNotification {
+    state: Arc<IncomingCallNotificationState>,
+}
+impl IncomingCallNotificationState {
     pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
         Self { call, app_state }
     }
 
-    fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
+    fn respond(&self, accept: bool, cx: &mut AppContext) {
         let active_call = ActiveCall::global(cx);
         if accept {
             let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
             let caller_user_id = self.call.calling_user.id;
             let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
             let app_state = self.app_state.clone();
-            cx.app_context()
-                .spawn(|mut cx| async move {
-                    join.await?;
-                    if let Some(project_id) = initial_project_id {
-                        cx.update(|cx| {
-                            if let Some(app_state) = app_state.upgrade() {
-                                workspace::join_remote_project(
-                                    project_id,
-                                    caller_user_id,
-                                    app_state,
-                                    cx,
-                                )
-                                .detach_and_log_err(cx);
-                            }
-                        });
-                    }
-                    anyhow::Ok(())
-                })
-                .detach_and_log_err(cx);
+            let cx: &mut AppContext = cx;
+            cx.spawn(|mut cx| async move {
+                join.await?;
+                if let Some(project_id) = initial_project_id {
+                    cx.update(|cx| {
+                        if let Some(app_state) = app_state.upgrade() {
+                            // workspace::join_remote_project(
+                            //     project_id,
+                            //     caller_user_id,
+                            //     app_state,
+                            //     cx,
+                            // )
+                            // .detach_and_log_err(cx);
+                        }
+                    });
+                }
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
         } else {
             active_call.update(cx, |active_call, cx| {
                 active_call.decline_incoming(cx).log_err();
             });
         }
     }
+}
 
-    fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).incoming_call_notification;
-        let default_project = proto::ParticipantProject::default();
-        let initial_project = self
-            .call
-            .initial_project
-            .as_ref()
-            .unwrap_or(&default_project);
-        Flex::row()
-            .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
-                Image::from_data(avatar)
-                    .with_style(theme.caller_avatar)
-                    .aligned()
-            }))
-            .with_child(
-                Flex::column()
-                    .with_child(
-                        Label::new(
-                            self.call.calling_user.github_login.clone(),
-                            theme.caller_username.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.caller_username.container),
-                    )
-                    .with_child(
-                        Label::new(
-                            format!(
-                                "is sharing a project in Zed{}",
-                                if initial_project.worktree_root_names.is_empty() {
-                                    ""
-                                } else {
-                                    ":"
-                                }
-                            ),
-                            theme.caller_message.text.clone(),
-                        )
-                        .contained()
-                        .with_style(theme.caller_message.container),
-                    )
-                    .with_children(if initial_project.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(
-                            Label::new(
-                                initial_project.worktree_root_names.join(", "),
-                                theme.worktree_roots.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.worktree_roots.container),
-                        )
-                    })
-                    .contained()
-                    .with_style(theme.caller_metadata)
-                    .aligned(),
-            )
-            .contained()
-            .with_style(theme.caller_container)
-            .flex(1., true)
-            .into_any()
+impl IncomingCallNotification {
+    pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
+        Self {
+            state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
+        }
     }
-
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum Accept {}
-        enum Decline {}
-
-        let theme = theme::current(cx);
-        Flex::column()
-            .with_child(
-                MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
-                    let theme = &theme.incoming_call_notification;
-                    Label::new("Accept", theme.accept_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.accept_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.respond(true, cx);
-                })
-                .flex(1., true),
+    fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element {
+        h_stack()
+            .children(
+                self.state
+                    .call
+                    .calling_user
+                    .avatar
+                    .as_ref()
+                    .map(|avatar| Avatar::new(avatar.clone())),
             )
-            .with_child(
-                MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
-                    let theme = &theme.incoming_call_notification;
-                    Label::new("Decline", theme.decline_button.text.clone())
-                        .aligned()
-                        .contained()
-                        .with_style(theme.decline_button.container)
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, this, cx| {
-                    this.respond(false, cx);
-                })
-                .flex(1., true),
+            .child(
+                v_stack()
+                    .child(Label::new(format!(
+                        "{} is sharing a project in Zed",
+                        self.state.call.calling_user.github_login
+                    )))
+                    .child(self.render_buttons(cx)),
             )
-            .constrained()
-            .with_width(theme.incoming_call_notification.button_width)
-            .into_any()
+        // let theme = &theme::current(cx).incoming_call_notification;
+        // let default_project = proto::ParticipantProject::default();
+        // let initial_project = self
+        //     .call
+        //     .initial_project
+        //     .as_ref()
+        //     .unwrap_or(&default_project);
+        // Flex::row()
+        //     .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
+        //         Image::from_data(avatar)
+        //             .with_style(theme.caller_avatar)
+        //             .aligned()
+        //     }))
+        //     .with_child(
+        //         Flex::column()
+        //             .with_child(
+        //                 Label::new(
+        //                     self.call.calling_user.github_login.clone(),
+        //                     theme.caller_username.text.clone(),
+        //                 )
+        //                 .contained()
+        //                 .with_style(theme.caller_username.container),
+        //             )
+        //             .with_child(
+        //                 Label::new(
+        //                     format!(
+        //                         "is sharing a project in Zed{}",
+        //                         if initial_project.worktree_root_names.is_empty() {
+        //                             ""
+        //                         } else {
+        //                             ":"
+        //                         }
+        //                     ),
+        //                     theme.caller_message.text.clone(),
+        //                 )
+        //                 .contained()
+        //                 .with_style(theme.caller_message.container),
+        //             )
+        //             .with_children(if initial_project.worktree_root_names.is_empty() {
+        //                 None
+        //             } else {
+        //                 Some(
+        //                     Label::new(
+        //                         initial_project.worktree_root_names.join(", "),
+        //                         theme.worktree_roots.text.clone(),
+        //                     )
+        //                     .contained()
+        //                     .with_style(theme.worktree_roots.container),
+        //                 )
+        //             })
+        //             .contained()
+        //             .with_style(theme.caller_metadata)
+        //             .aligned(),
+        //     )
+        //     .contained()
+        //     .with_style(theme.caller_container)
+        //     .flex(1., true)
+        //     .into_any()
     }
-}
 
-impl Entity for IncomingCallNotification {
-    type Event = ();
-}
+    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
+        h_stack()
+            .child(Button::new("Accept").render(cx).bg(green()).on_click({
+                let state = self.state.clone();
+                move |_, cx| state.respond(true, cx)
+            }))
+            .child(Button::new("Decline").render(cx).bg(red()).on_click({
+                let state = self.state.clone();
+                move |_, cx| state.respond(false, cx)
+            }))
 
-impl View for IncomingCallNotification {
-    fn ui_name() -> &'static str {
-        "IncomingCallNotification"
-    }
+        // enum Accept {}
+        // enum Decline {}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let background = theme::current(cx).incoming_call_notification.background;
-        Flex::row()
-            .with_child(self.render_caller(cx))
-            .with_child(self.render_buttons(cx))
-            .contained()
-            .with_background_color(background)
-            .expanded()
-            .into_any()
+        // let theme = theme::current(cx);
+        // Flex::column()
+        //     .with_child(
+        //         MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
+        //             let theme = &theme.incoming_call_notification;
+        //             Label::new("Accept", theme.accept_button.text.clone())
+        //                 .aligned()
+        //                 .contained()
+        //                 .with_style(theme.accept_button.container)
+        //         })
+        //         .with_cursor_style(CursorStyle::PointingHand)
+        //         .on_click(MouseButton::Left, |_, this, cx| {
+        //             this.respond(true, cx);
+        //         })
+        //         .flex(1., true),
+        //     )
+        //     .with_child(
+        //         MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
+        //             let theme = &theme.incoming_call_notification;
+        //             Label::new("Decline", theme.decline_button.text.clone())
+        //                 .aligned()
+        //                 .contained()
+        //                 .with_style(theme.decline_button.container)
+        //         })
+        //         .with_cursor_style(CursorStyle::PointingHand)
+        //         .on_click(MouseButton::Left, |_, this, cx| {
+        //             this.respond(false, cx);
+        //         })
+        //         .flex(1., true),
+        //     )
+        //     .constrained()
+        //     .with_width(theme.incoming_call_notification.button_width)
+        //     .into_any()
+    }
+}
+impl Render for IncomingCallNotification {
+    type Element = Div;
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div().bg(red()).flex_none().child(self.render_caller(cx))
+        // Flex::row()
+        //     .with_child()
+        //     .with_child(self.render_buttons(cx))
+        //     .contained()
+        //     .with_background_color(background)
+        //     .expanded()
+        //     .into_any()
     }
 }

crates/zed2/Cargo.toml 🔗

@@ -16,7 +16,7 @@ path = "src/main.rs"
 
 [dependencies]
 ai = { package = "ai2", path = "../ai2"}
-# audio = { path = "../audio" }
+audio = { package = "audio2", path = "../audio2" }
 # activity_indicator = { path = "../activity_indicator" }
 auto_update = { package = "auto_update2", path = "../auto_update2" }
 # breadcrumbs = { path = "../breadcrumbs" }

crates/zed2/src/main.rs 🔗

@@ -199,7 +199,7 @@ fn main() {
         });
         cx.set_global(Arc::downgrade(&app_state));
 
-        // audio::init(Assets, cx);
+        audio::init(Assets, cx);
         auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
 
         workspace::init(app_state.clone(), cx);