From c1919438494b181ee812a6fd7992d0a355add5e9 Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Thu, 23 Nov 2023 15:00:13 +0100
Subject: [PATCH 01/19] Add basic call/user UI in top-right corner.
Allow ui::Avatar to take custom data instead of always relying on URI resolution
---
crates/collab_ui2/src/collab_titlebar_item.rs | 45 +++++++++-
crates/gpui2/src/elements/img.rs | 86 +++++++++++++------
crates/ui2/src/components/avatar.rs | 8 +-
crates/ui2/src/components/stories/avatar.rs | 4 +-
crates/util/src/channel.rs | 2 +-
crates/workspace2/src/workspace2.rs | 4 +
6 files changed, 113 insertions(+), 36 deletions(-)
diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs
index 94db2f4d9f..d9eff16e8e 100644
--- a/crates/collab_ui2/src/collab_titlebar_item.rs
+++ b/crates/collab_ui2/src/collab_titlebar_item.rs
@@ -37,7 +37,7 @@ use gpui::{
};
use project::Project;
use theme::ActiveTheme;
-use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip};
+use ui::{h_stack, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
use workspace::Workspace;
// const MAX_PROJECT_NAME_LENGTH: usize = 40;
@@ -85,6 +85,13 @@ impl Render for CollabTitlebarItem {
type Element = Stateful
;
fn render(&mut self, cx: &mut ViewContext
) -> Self::Element {
+ let is_in_room = self
+ .workspace
+ .update(cx, |this, cx| this.call_state().is_in_room(cx))
+ .unwrap_or_default();
+ let is_shared = is_in_room && self.project.read(cx).is_shared();
+ let current_user = self.user_store.read(cx).current_user();
+ let client = self.client.clone();
h_stack()
.id("titlebar")
.justify_between()
@@ -149,8 +156,40 @@ impl Render for CollabTitlebarItem {
.into()
}),
),
- ) // self.titlebar_item
- .child(h_stack().child(Label::new("Right side titlebar item")))
+ )
+ .map(|this| {
+ if let Some(user) = current_user {
+ this.when_some(user.avatar.clone(), |this, avatar| {
+ this.child(ui::Avatar::new(avatar))
+ })
+ } else {
+ this.child(Button::new("Sign in").on_click(move |_, cx| {
+ let client = client.clone();
+ cx.spawn(move |cx| async move {
+ client.authenticate_and_connect(true, &cx).await?;
+ Ok::<(), anyhow::Error>(())
+ })
+ .detach_and_log_err(cx);
+ }))
+ }
+ }) // that's obviously wrong as we should check for current call,not current user
+ .when(is_in_room, |this| {
+ this.child(
+ h_stack()
+ .child(
+ h_stack()
+ .child(Button::new(if is_shared { "Unshare" } else { "Share" }))
+ .child(IconButton::new("leave-call", ui::Icon::Exit)),
+ )
+ .child(
+ h_stack()
+ .child(IconButton::new("mute-microphone", ui::Icon::Mic))
+ .child(IconButton::new("mute-sound", ui::Icon::AudioOn))
+ .child(IconButton::new("screen-share", ui::Icon::Screen))
+ .pl_2(),
+ ),
+ )
+ })
}
}
diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs
index 3c0f4c00be..c28f0dca30 100644
--- a/crates/gpui2/src/elements/img.rs
+++ b/crates/gpui2/src/elements/img.rs
@@ -1,30 +1,59 @@
+use std::sync::Arc;
+
use crate::{
- Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels,
- RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
+ Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
+ LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
};
use futures::FutureExt;
use util::ResultExt;
+#[derive(Clone, Debug)]
+pub enum ImageSource {
+ /// Image content will be loaded from provided URI at render time.
+ Uri(SharedString),
+ Data(Arc),
+}
+
+impl From for ImageSource {
+ fn from(value: SharedString) -> Self {
+ Self::Uri(value)
+ }
+}
+
+impl From> for ImageSource {
+ fn from(value: Arc) -> Self {
+ Self::Data(value)
+ }
+}
+
pub struct Img {
interactivity: Interactivity,
- uri: Option,
+ source: Option,
grayscale: bool,
}
pub fn img() -> Img {
Img {
interactivity: Interactivity::default(),
- uri: None,
+ source: None,
grayscale: false,
}
}
impl Img {
pub fn uri(mut self, uri: impl Into) -> Self {
- self.uri = Some(uri.into());
+ self.source = Some(ImageSource::from(uri.into()));
+ self
+ }
+ pub fn data(mut self, data: Arc) -> Self {
+ self.source = Some(ImageSource::from(data));
self
}
+ pub fn source(mut self, source: impl Into) -> Self {
+ self.source = Some(source.into());
+ self
+ }
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.grayscale = grayscale;
self
@@ -58,28 +87,33 @@ impl Element for Img {
|style, _scroll_offset, cx| {
let corner_radii = style.corner_radii;
- if let Some(uri) = self.uri.clone() {
- // eprintln!(">>> image_cache.get({uri}");
- let image_future = cx.image_cache.get(uri.clone());
- // eprintln!("<<< image_cache.get({uri}");
- if let Some(data) = image_future
- .clone()
- .now_or_never()
- .and_then(|result| result.ok())
- {
- let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
- cx.with_z_index(1, |cx| {
- cx.paint_image(bounds, corner_radii, data, self.grayscale)
- .log_err()
- });
- } else {
- cx.spawn(|mut cx| async move {
- if image_future.await.ok().is_some() {
- cx.on_next_frame(|cx| cx.notify());
+ if let Some(source) = self.source {
+ let image = match source {
+ ImageSource::Uri(uri) => {
+ let image_future = cx.image_cache.get(uri.clone());
+ if let Some(data) = image_future
+ .clone()
+ .now_or_never()
+ .and_then(|result| result.ok())
+ {
+ data
+ } else {
+ cx.spawn(|mut cx| async move {
+ if image_future.await.ok().is_some() {
+ cx.on_next_frame(|cx| cx.notify());
+ }
+ })
+ .detach();
+ return;
}
- })
- .detach()
- }
+ }
+ ImageSource::Data(image) => image,
+ };
+ let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
+ cx.with_z_index(1, |cx| {
+ cx.paint_image(bounds, corner_radii, image, self.grayscale)
+ .log_err()
+ });
}
},
)
diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs
index 364a145494..872d62fa12 100644
--- a/crates/ui2/src/components/avatar.rs
+++ b/crates/ui2/src/components/avatar.rs
@@ -1,5 +1,5 @@
use crate::prelude::*;
-use gpui::{img, Img, RenderOnce};
+use gpui::{img, ImageSource, Img, RenderOnce};
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
@@ -10,7 +10,7 @@ pub enum Shape {
#[derive(RenderOnce)]
pub struct Avatar {
- src: SharedString,
+ src: ImageSource,
shape: Shape,
}
@@ -26,7 +26,7 @@ impl Component for Avatar {
img = img.rounded_md();
}
- img.uri(self.src.clone())
+ img.source(self.src.clone())
.size_4()
// todo!(Pull the avatar fallback background from the theme.)
.bg(gpui::red())
@@ -34,7 +34,7 @@ impl Component for Avatar {
}
impl Avatar {
- pub fn new(src: impl Into) -> Self {
+ pub fn new(src: impl Into) -> Self {
Self {
src: src.into(),
shape: Shape::Circle,
diff --git a/crates/ui2/src/components/stories/avatar.rs b/crates/ui2/src/components/stories/avatar.rs
index ad9c3ccb39..177065cfcb 100644
--- a/crates/ui2/src/components/stories/avatar.rs
+++ b/crates/ui2/src/components/stories/avatar.rs
@@ -14,10 +14,10 @@ impl Render for AvatarStory {
.child(Story::title_for::())
.child(Story::label("Default"))
.child(Avatar::new(
- "https://avatars.githubusercontent.com/u/1714999?v=4",
+ "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
))
.child(Avatar::new(
- "https://avatars.githubusercontent.com/u/326587?v=4",
+ "https://avatars.githubusercontent.com/u/326587?v=4".into(),
))
}
}
diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs
index 55f13df084..94260c71db 100644
--- a/crates/util/src/channel.rs
+++ b/crates/util/src/channel.rs
@@ -19,7 +19,7 @@ lazy_static! {
pub struct AppCommitSha(pub String);
-#[derive(Copy, Clone, PartialEq, Eq, Default)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum ReleaseChannel {
#[default]
Dev,
diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs
index b09b47d24c..761b09365d 100644
--- a/crates/workspace2/src/workspace2.rs
+++ b/crates/workspace2/src/workspace2.rs
@@ -3408,6 +3408,10 @@ impl Workspace {
self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
}
+
+ pub fn call_state(&mut self) -> &mut dyn CallHandler {
+ &mut *self.call_handler
+ }
}
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option {
From b463454062fae91775e4604e5a4c4ab42bf0ee7d Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Thu, 23 Nov 2023 15:03:54 +0100
Subject: [PATCH 02/19] Remove redundant comment
---
crates/collab_ui2/src/collab_titlebar_item.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs
index 13a3ee6732..7c9cb9f453 100644
--- a/crates/collab_ui2/src/collab_titlebar_item.rs
+++ b/crates/collab_ui2/src/collab_titlebar_item.rs
@@ -172,7 +172,7 @@ impl Render for CollabTitlebarItem {
.detach_and_log_err(cx);
}))
}
- }) // that's obviously wrong as we should check for current call,not current user
+ })
.when(is_in_room, |this| {
this.child(
h_stack()
From 2a2b3b5e918a8f47acd01b3bfdb371c7e34ac21f Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Thu, 23 Nov 2023 15:08:21 +0100
Subject: [PATCH 03/19] Authenticate on app startup
---
crates/zed2/src/main.rs | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs
index bbf7c3ae9c..b18eccace7 100644
--- a/crates/zed2/src/main.rs
+++ b/crates/zed2/src/main.rs
@@ -8,7 +8,7 @@ use anyhow::{anyhow, Context as _, Result};
use backtrace::Backtrace;
use chrono::Utc;
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
-use client::UserStore;
+use client::{Client, UserStore};
use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use fs::RealFs;
@@ -249,7 +249,7 @@ fn main() {
}
}
- let mut _triggered_authentication = false;
+ let mut triggered_authentication = false;
fn open_paths_and_log_errs(
paths: &[PathBuf],
@@ -328,23 +328,23 @@ fn main() {
})
.detach();
- // if !triggered_authentication {
- // cx.spawn(|cx| async move { authenticate(client, &cx).await })
- // .detach_and_log_err(cx);
- // }
+ if !triggered_authentication {
+ cx.spawn(|cx| async move { authenticate(client, &cx).await })
+ .detach_and_log_err(cx);
+ }
});
}
-// async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> {
-// if stdout_is_a_pty() {
-// if client::IMPERSONATE_LOGIN.is_some() {
-// client.authenticate_and_connect(false, &cx).await?;
-// }
-// } else if client.has_keychain_credentials(&cx) {
-// client.authenticate_and_connect(true, &cx).await?;
-// }
-// Ok::<_, anyhow::Error>(())
-// }
+async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> {
+ if stdout_is_a_pty() {
+ if client::IMPERSONATE_LOGIN.is_some() {
+ client.authenticate_and_connect(false, &cx).await?;
+ }
+ } else if client.has_keychain_credentials(&cx).await {
+ client.authenticate_and_connect(true, &cx).await?;
+ }
+ Ok::<_, anyhow::Error>(())
+}
async fn installation_id() -> Result<(String, bool)> {
let legacy_key_name = "device_id";
From e754c6626de7c8a61f4a2010fbd2ba6e176e560a Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Thu, 23 Nov 2023 16:19:25 +0100
Subject: [PATCH 04/19] Allow calling an user, render contacts in collab panel
---
crates/call2/src/call2.rs | 82 +++++++++++++++------------
crates/collab_ui2/src/collab_panel.rs | 42 +++++++++++---
crates/workspace2/src/workspace2.rs | 6 ++
3 files changed, 86 insertions(+), 44 deletions(-)
diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs
index 8e553a9b16..9aa8c11428 100644
--- a/crates/call2/src/call2.rs
+++ b/crates/call2/src/call2.rs
@@ -549,42 +549,6 @@ impl Call {
#[async_trait(?Send)]
impl CallHandler for Call {
- fn shared_screen_for_peer(
- &self,
- peer_id: PeerId,
- _pane: &View,
- cx: &mut ViewContext,
- ) -> Option> {
- let (call, _) = self.active_call.as_ref()?;
- let room = call.read(cx).room()?.read(cx);
- let participant = room.remote_participant_for_peer_id(peer_id)?;
- let _track = participant.video_tracks.values().next()?.clone();
- let _user = participant.user.clone();
- todo!();
- // for item in pane.read(cx).items_of_type::() {
- // if item.read(cx).peer_id == peer_id {
- // return Box::new(Some(item));
- // }
- // }
-
- // Some(Box::new(cx.build_view(|cx| {
- // SharedScreen::new(&track, peer_id, user.clone(), cx)
- // })))
- }
-
- fn room_id(&self, cx: &AppContext) -> Option {
- Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
- }
- fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> {
- let Some((call, _)) = self.active_call.as_ref() else {
- bail!("Cannot exit a call; not in a call");
- };
-
- call.update(&mut cx, |this, cx| this.hang_up(cx))
- }
- fn active_project(&self, cx: &AppContext) -> Option> {
- ActiveCall::global(cx).read(cx).location().cloned()
- }
fn peer_state(
&mut self,
leader_id: PeerId,
@@ -618,6 +582,52 @@ impl CallHandler for Call {
Some((leader_in_this_project, leader_in_this_app))
}
+
+ fn shared_screen_for_peer(
+ &self,
+ peer_id: PeerId,
+ _pane: &View,
+ cx: &mut ViewContext,
+ ) -> Option> {
+ let (call, _) = self.active_call.as_ref()?;
+ let room = call.read(cx).room()?.read(cx);
+ let participant = room.remote_participant_for_peer_id(peer_id)?;
+ let _track = participant.video_tracks.values().next()?.clone();
+ let _user = participant.user.clone();
+ todo!();
+ // for item in pane.read(cx).items_of_type::() {
+ // if item.read(cx).peer_id == peer_id {
+ // return Box::new(Some(item));
+ // }
+ // }
+
+ // Some(Box::new(cx.build_view(|cx| {
+ // SharedScreen::new(&track, peer_id, user.clone(), cx)
+ // })))
+ }
+ fn room_id(&self, cx: &AppContext) -> Option {
+ Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
+ }
+ fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> {
+ let Some((call, _)) = self.active_call.as_ref() else {
+ bail!("Cannot exit a call; not in a call");
+ };
+
+ call.update(&mut cx, |this, cx| this.hang_up(cx))
+ }
+ fn active_project(&self, cx: &AppContext) -> Option> {
+ ActiveCall::global(cx).read(cx).location().cloned()
+ }
+ fn invite(
+ &mut self,
+ called_user_id: u64,
+ initial_project: Option>,
+ cx: &mut AppContext,
+ ) -> Task> {
+ ActiveCall::global(cx).update(cx, |this, cx| {
+ this.invite(called_user_id, initial_project, cx)
+ })
+ }
}
#[cfg(test)]
diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs
index 6af188dfd2..abeb481d53 100644
--- a/crates/collab_ui2/src/collab_panel.rs
+++ b/crates/collab_ui2/src/collab_panel.rs
@@ -157,15 +157,17 @@ const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
use std::sync::Arc;
+use client::{Client, Contact, UserStore};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
- Focusable, FocusableView, InteractiveElement, ParentElement, Render, View, ViewContext,
- VisualContext, WeakView,
+ Focusable, FocusableView, InteractiveElement, Model, ParentElement, Render, Styled, View,
+ ViewContext, VisualContext, WeakView,
};
use project::Fs;
use serde_derive::{Deserialize, Serialize};
use settings::Settings;
+use ui::{h_stack, Avatar, Label};
use util::ResultExt;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
@@ -299,8 +301,8 @@ pub struct CollabPanel {
// channel_editing_state: Option,
// entries: Vec,
// selection: Option,
- // user_store: ModelHandle,
- // client: Arc,
+ user_store: Model,
+ client: Arc,
// channel_store: ModelHandle,
// project: ModelHandle,
// match_candidates: Vec,
@@ -595,7 +597,7 @@ impl CollabPanel {
// entries: Vec::default(),
// channel_editing_state: None,
// selection: None,
- // user_store: workspace.user_store().clone(),
+ user_store: workspace.user_store().clone(),
// channel_store: ChannelStore::global(cx),
// project: workspace.project().clone(),
// subscriptions: Vec::default(),
@@ -603,7 +605,7 @@ impl CollabPanel {
// collapsed_sections: vec![Section::Offline],
// collapsed_channels: Vec::default(),
_workspace: workspace.weak_handle(),
- // client: workspace.app_state().client.clone(),
+ client: workspace.app_state().client.clone(),
// context_menu_on_selected: true,
// drag_target_channel: ChannelDragTarget::None,
// list_state,
@@ -663,6 +665,9 @@ impl CollabPanel {
})
}
+ fn contacts(&self, cx: &AppContext) -> Option>> {
+ Some(self.user_store.read(cx).contacts().to_owned())
+ }
pub async fn load(
workspace: WeakView,
mut cx: AsyncWindowContext,
@@ -3297,11 +3302,32 @@ impl CollabPanel {
impl Render for CollabPanel {
type Element = Focusable;
- fn render(&mut self, _cx: &mut ViewContext
) -> Self::Element {
+ fn render(&mut self, cx: &mut ViewContext) -> Self::Element {
+ let contacts = self.contacts(cx).unwrap_or_default();
+ let workspace = self._workspace.clone();
div()
.key_context("CollabPanel")
.track_focus(&self.focus_handle)
- .child("COLLAB PANEL")
+ .children(contacts.into_iter().map(|contact| {
+ let id = contact.user.id;
+ h_stack()
+ .p_2()
+ .gap_2()
+ .children(
+ contact
+ .user
+ .avatar
+ .as_ref()
+ .map(|avatar| Avatar::new(avatar.clone())),
+ )
+ .child(Label::new(contact.user.github_login.clone()))
+ .on_mouse_down(gpui::MouseButton::Left, {
+ let workspace = workspace.clone();
+ move |event, cx| {
+ workspace.update(cx, |this, cx| this.call_state().invite(id, None, cx));
+ }
+ })
+ }))
}
}
diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs
index 761b09365d..8c687becd0 100644
--- a/crates/workspace2/src/workspace2.rs
+++ b/crates/workspace2/src/workspace2.rs
@@ -454,6 +454,12 @@ pub trait CallHandler {
}
fn hang_up(&self, cx: AsyncWindowContext) -> Result>>;
fn active_project(&self, cx: &AppContext) -> Option>;
+ fn invite(
+ &mut self,
+ called_user_id: u64,
+ initial_project: Option>,
+ cx: &mut AppContext,
+ ) -> Task>;
}
pub struct Workspace {
From f2b62c3946d7392469bebcf0857c893e0e8135bc Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Fri, 24 Nov 2023 00:52:44 +0100
Subject: [PATCH 05/19] Start emitting notifications for calls
---
Cargo.lock | 1 +
crates/collab_ui2/src/collab_ui.rs | 58 +--
crates/collab_ui2/src/notifications.rs | 16 +-
.../incoming_call_notification.rs | 350 ++++++++++--------
crates/zed2/Cargo.toml | 2 +-
crates/zed2/src/main.rs | 2 +-
6 files changed, 241 insertions(+), 188 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 99d4827966..693f2d9156 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11644,6 +11644,7 @@ dependencies = [
"async-recursion 0.3.2",
"async-tar",
"async-trait",
+ "audio2",
"auto_update2",
"backtrace",
"call2",
diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs
index d2e6b28115..d1e4bea7d6 100644
--- a/crates/collab_ui2/src/collab_ui.rs
+++ b/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, cx: &mut AppContext) {
+pub fn init(app_state: &Arc, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
ChatPanelSettings::register(cx);
NotificationPanelSettings::register(cx);
@@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc, 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, cx: &mut AppContext) {
// }
// }
-// fn notification_window_options(
-// screen: Rc,
-// window_size: Vector2F,
-// ) -> WindowOptions<'static> {
-// const NOTIFICATION_PADDING: f32 = 16.;
+fn notification_window_options(
+ screen: Rc,
+ window_size: Size,
+) -> 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 = window_size.into();
+
+ let bounds = gpui::Bounds:: {
+ 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(
// avatar: Option>,
diff --git a/crates/collab_ui2/src/notifications.rs b/crates/collab_ui2/src/notifications.rs
index bc5d7ad3bf..b58473476a 100644
--- a/crates/collab_ui2/src/notifications.rs
+++ b/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, cx: &mut AppContext) {
-// incoming_call_notification::init(app_state, cx);
-// project_shared_notification::init(app_state, cx);
-// }
+pub fn init(app_state: &Arc, cx: &mut AppContext) {
+ incoming_call_notification::init(app_state, cx);
+ //project_shared_notification::init(app_state, cx);
+}
diff --git a/crates/collab_ui2/src/notifications/incoming_call_notification.rs b/crates/collab_ui2/src/notifications/incoming_call_notification.rs
index c614a814ca..d400b14f5f 100644
--- a/crates/collab_ui2/src/notifications/incoming_call_notification.rs
+++ b/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, cx: &mut AppContext) {
let mut notification_windows: Vec> = 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,
}
-impl IncomingCallNotification {
+pub struct IncomingCallNotification {
+ state: Arc,
+}
+impl IncomingCallNotificationState {
pub fn new(call: IncomingCall, app_state: Weak) -> Self {
Self { call, app_state }
}
- fn respond(&mut self, accept: bool, cx: &mut ViewContext) {
+ 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) -> AnyElement {
- 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()
+impl IncomingCallNotification {
+ pub fn new(call: IncomingCall, app_state: Weak) -> Self {
+ Self {
+ state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
+ }
+ }
+ fn render_caller(&self, cx: &mut ViewContext) -> impl Element {
+ h_stack()
+ .children(
+ self.state
+ .call
+ .calling_user
+ .avatar
+ .as_ref()
+ .map(|avatar| Avatar::new(avatar.clone())),
+ )
+ .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)),
+ )
+ // 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()
+ }
+
+ fn render_buttons(&self, cx: &mut ViewContext) -> 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)
}))
- .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()
- }
- fn render_buttons(&self, cx: &mut ViewContext) -> AnyElement {
- enum Accept {}
- enum Decline {}
+ // enum Accept {}
+ // enum Decline {}
- let theme = theme::current(cx);
- Flex::column()
- .with_child(
- MouseEventHandler::new::(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::(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()
+ // let theme = theme::current(cx);
+ // Flex::column()
+ // .with_child(
+ // MouseEventHandler::new::(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::(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 Entity for IncomingCallNotification {
- type Event = ();
-}
-
-impl View for IncomingCallNotification {
- fn ui_name() -> &'static str {
- "IncomingCallNotification"
- }
-
- fn render(&mut self, cx: &mut ViewContext) -> AnyElement {
- 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()
+impl Render for IncomingCallNotification {
+ type Element = Div;
+ fn render(&mut self, cx: &mut ViewContext) -> 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()
}
}
diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml
index 24648f87f1..3212b6182b 100644
--- a/crates/zed2/Cargo.toml
+++ b/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" }
diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs
index b18eccace7..c9ed26436a 100644
--- a/crates/zed2/src/main.rs
+++ b/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);
From 6ebe5d5053af7b9d95cd28c2b1742a00a0d140fd Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Fri, 24 Nov 2023 15:16:03 +0100
Subject: [PATCH 06/19] Add mute handling
---
crates/call2/src/call2.rs | 37 +++++++++
crates/call2/src/room.rs | 3 +-
crates/client2/src/client2.rs | 1 -
crates/collab_ui2/src/collab_titlebar_item.rs | 77 ++++++++++++++-----
crates/workspace2/src/workspace2.rs | 25 +++++-
5 files changed, 122 insertions(+), 21 deletions(-)
diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs
index 9aa8c11428..e778316d59 100644
--- a/crates/call2/src/call2.rs
+++ b/crates/call2/src/call2.rs
@@ -17,6 +17,7 @@ use gpui::{
Subscription, Task, View, ViewContext, WeakModel, WeakView,
};
pub use participant::ParticipantLocation;
+use participant::RemoteParticipant;
use postage::watch;
use project::Project;
use room::Event;
@@ -628,6 +629,42 @@ impl CallHandler for Call {
this.invite(called_user_id, initial_project, cx)
})
}
+ fn remote_participants(&self, cx: &AppContext) -> Option>> {
+ self.active_call
+ .as_ref()
+ .map(|call| {
+ call.0.read(cx).room().map(|room| {
+ room.read(cx)
+ .remote_participants()
+ .iter()
+ .map(|participant| participant.1.user.clone())
+ .collect()
+ })
+ })
+ .flatten()
+ }
+ fn is_muted(&self, cx: &AppContext) -> Option {
+ self.active_call
+ .as_ref()
+ .map(|call| {
+ call.0
+ .read(cx)
+ .room()
+ .map(|room| room.read(cx).is_muted(cx))
+ })
+ .flatten()
+ }
+ fn toggle_mute(&self, cx: &mut AppContext) {
+ self.active_call.as_ref().map(|call| {
+ call.0.update(cx, |this, cx| {
+ this.room().map(|room| {
+ room.update(cx, |this, cx| {
+ this.toggle_mute(cx);
+ })
+ })
+ })
+ });
+ }
}
#[cfg(test)]
diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs
index 87118764fd..d73a522937 100644
--- a/crates/call2/src/room.rs
+++ b/crates/call2/src/room.rs
@@ -333,7 +333,8 @@ impl Room {
}
pub fn mute_on_join(cx: &AppContext) -> bool {
- CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
+ false
+ //CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
}
fn from_join_response(
diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs
index 4ad354f2f9..b31451aa87 100644
--- a/crates/client2/src/client2.rs
+++ b/crates/client2/src/client2.rs
@@ -551,7 +551,6 @@ impl Client {
F: 'static + Future