Move sharing status indicator out of the call crate and into collab_ui in order so that the model doesn't depend on the view

Kay Simmons created

Change summary

crates/call/src/call.rs                          |  41 ----
crates/collab_ui/src/collab_titlebar_item.rs     |  23 ++
crates/collab_ui/src/collab_ui.rs                | 171 ++++++++++-------
crates/collab_ui/src/sharing_status_indicator.rs |  24 ++
4 files changed, 141 insertions(+), 118 deletions(-)

Detailed changes

crates/call/src/call.rs 🔗

@@ -1,4 +1,3 @@
-mod indicator;
 pub mod participant;
 pub mod room;
 
@@ -10,22 +9,17 @@ use collections::HashSet;
 use postage::watch;
 
 use gpui::{
-    actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
-    Subscription, Task, ViewHandle, WeakModelHandle,
+    AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
+    Subscription, Task, WeakModelHandle,
 };
 use project::Project;
-use settings::Settings;
 
-use indicator::SharingStatusIndicator;
 pub use participant::ParticipantLocation;
 pub use room::Room;
 
-actions!(collab, [ToggleScreenSharing]);
-
 pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut MutableAppContext) {
     let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
     cx.set_global(active_call);
-    cx.add_global_action(toggle_screen_sharing);
 }
 
 #[derive(Clone)]
@@ -36,6 +30,7 @@ pub struct IncomingCall {
     pub initial_project: Option<proto::ParticipantProject>,
 }
 
+/// Singleton global maintaining the user's participation in a room across workspaces.
 pub struct ActiveCall {
     room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
     location: Option<WeakModelHandle<Project>>,
@@ -46,7 +41,6 @@ pub struct ActiveCall {
     ),
     client: Arc<Client>,
     user_store: ModelHandle<UserStore>,
-    sharing_status_indicator: Option<(usize, ViewHandle<SharingStatusIndicator>)>,
     _subscriptions: Vec<client::Subscription>,
 }
 
@@ -71,7 +65,6 @@ impl ActiveCall {
             ],
             client,
             user_store,
-            sharing_status_indicator: None,
         }
     }
 
@@ -290,8 +283,6 @@ impl ActiveCall {
                                 this.set_room(None, cx).detach_and_log_err(cx);
                             }
 
-                            this.set_sharing_status(room.read(cx).is_screen_sharing(), cx);
-
                             cx.notify();
                         }),
                         cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
@@ -316,30 +307,4 @@ impl ActiveCall {
     pub fn pending_invites(&self) -> &HashSet<u64> {
         &self.pending_invites
     }
-
-    pub fn set_sharing_status(&mut self, is_screen_sharing: bool, cx: &mut MutableAppContext) {
-        if is_screen_sharing {
-            if self.sharing_status_indicator.is_none()
-                && cx.global::<Settings>().show_call_status_icon
-            {
-                self.sharing_status_indicator =
-                    Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
-            }
-        } else if let Some((window_id, _)) = self.sharing_status_indicator.take() {
-            cx.remove_status_bar_item(window_id);
-        }
-    }
-}
-
-pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) {
-    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-        let toggle_screen_sharing = room.update(cx, |room, cx| {
-            if room.is_screen_sharing() {
-                Task::ready(room.unshare_screen(cx))
-            } else {
-                room.share_screen(cx)
-            }
-        });
-        toggle_screen_sharing.detach_and_log_err(cx);
-    }
 }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{contact_notification::ContactNotification, contacts_popover};
-use call::{ActiveCall, ParticipantLocation, ToggleScreenSharing};
+use call::{ActiveCall, ParticipantLocation};
 use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore};
 use clock::ReplicaId;
 use contacts_popover::ContactsPopover;
@@ -10,17 +10,21 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
     json::{self, ToJson},
     Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
-    Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+    Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 use std::ops::Range;
 use theme::Theme;
 use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
 
-actions!(collab, [ToggleCollaborationMenu, ShareProject]);
+actions!(
+    collab,
+    [ToggleCollaborationMenu, ToggleScreenSharing, ShareProject]
+);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
+    cx.add_global_action(CollabTitlebarItem::toggle_screen_sharing);
     cx.add_action(CollabTitlebarItem::share_project);
 }
 
@@ -168,6 +172,19 @@ impl CollabTitlebarItem {
         cx.notify();
     }
 
+    pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) {
+        if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+            let toggle_screen_sharing = room.update(cx, |room, cx| {
+                if room.is_screen_sharing() {
+                    Task::ready(room.unshare_screen(cx))
+                } else {
+                    room.share_screen(cx)
+                }
+            });
+            toggle_screen_sharing.detach_and_log_err(cx);
+        }
+    }
+
     fn render_toggle_contacts_button(
         &self,
         theme: &Theme,

crates/collab_ui/src/collab_ui.rs 🔗

@@ -6,14 +6,17 @@ mod contacts_popover;
 mod incoming_call_notification;
 mod notifications;
 mod project_shared_notification;
+mod sharing_status_indicator;
 
 use anyhow::anyhow;
 use call::ActiveCall;
 pub use collab_titlebar_item::{CollabTitlebarItem, ToggleCollaborationMenu};
-use gpui::MutableAppContext;
+use gpui::{actions, MutableAppContext, Task};
 use std::sync::Arc;
 use workspace::{AppState, JoinProject, ToggleFollow, Workspace};
 
+actions!(collab, [ToggleScreenSharing]);
+
 pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     collab_titlebar_item::init(cx);
     contact_notification::init(cx);
@@ -22,89 +25,107 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     contacts_popover::init(cx);
     incoming_call_notification::init(cx);
     project_shared_notification::init(cx);
+    sharing_status_indicator::init(cx);
 
+    cx.add_global_action(toggle_screen_sharing);
     cx.add_global_action(move |action: &JoinProject, cx| {
-        let project_id = action.project_id;
-        let follow_user_id = action.follow_user_id;
-        let app_state = app_state.clone();
-        cx.spawn(|mut cx| async move {
-            let existing_workspace = cx.update(|cx| {
-                cx.window_ids()
-                    .filter_map(|window_id| cx.root_view::<Workspace>(window_id))
-                    .find(|workspace| {
-                        workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
-                    })
-            });
+        join_project(action, app_state.clone(), cx);
+    });
+}
 
-            let workspace = if let Some(existing_workspace) = existing_workspace {
-                existing_workspace
+pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) {
+    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+        let toggle_screen_sharing = room.update(cx, |room, cx| {
+            if room.is_screen_sharing() {
+                Task::ready(room.unshare_screen(cx))
             } else {
-                let active_call = cx.read(ActiveCall::global);
-                let room = active_call
-                    .read_with(&cx, |call, _| call.room().cloned())
-                    .ok_or_else(|| anyhow!("not in a call"))?;
-                let project = room
-                    .update(&mut cx, |room, cx| {
-                        room.join_project(
-                            project_id,
-                            app_state.languages.clone(),
-                            app_state.fs.clone(),
-                            cx,
-                        )
-                    })
-                    .await?;
+                room.share_screen(cx)
+            }
+        });
+        toggle_screen_sharing.detach_and_log_err(cx);
+    }
+}
 
-                let (_, workspace) = cx.add_window(
-                    (app_state.build_window_options)(None, None, cx.platform().as_ref()),
-                    |cx| {
-                        let mut workspace = Workspace::new(
-                            Default::default(),
-                            0,
-                            project,
-                            app_state.dock_default_item_factory,
-                            cx,
-                        );
-                        (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
-                        workspace
-                    },
-                );
-                workspace
-            };
+fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut MutableAppContext) {
+    let project_id = action.project_id;
+    let follow_user_id = action.follow_user_id;
+    cx.spawn(|mut cx| async move {
+        let existing_workspace = cx.update(|cx| {
+            cx.window_ids()
+                .filter_map(|window_id| cx.root_view::<Workspace>(window_id))
+                .find(|workspace| {
+                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
+                })
+        });
 
-            cx.activate_window(workspace.window_id());
-            cx.platform().activate(true);
+        let workspace = if let Some(existing_workspace) = existing_workspace {
+            existing_workspace
+        } else {
+            let active_call = cx.read(ActiveCall::global);
+            let room = active_call
+                .read_with(&cx, |call, _| call.room().cloned())
+                .ok_or_else(|| anyhow!("not in a call"))?;
+            let project = room
+                .update(&mut cx, |room, cx| {
+                    room.join_project(
+                        project_id,
+                        app_state.languages.clone(),
+                        app_state.fs.clone(),
+                        cx,
+                    )
+                })
+                .await?;
 
-            workspace.update(&mut cx, |workspace, cx| {
-                if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-                    let follow_peer_id = room
-                        .read(cx)
-                        .remote_participants()
-                        .iter()
-                        .find(|(_, participant)| participant.user.id == follow_user_id)
-                        .map(|(_, p)| p.peer_id)
-                        .or_else(|| {
-                            // If we couldn't follow the given user, follow the host instead.
-                            let collaborator = workspace
-                                .project()
-                                .read(cx)
-                                .collaborators()
-                                .values()
-                                .find(|collaborator| collaborator.replica_id == 0)?;
-                            Some(collaborator.peer_id)
-                        });
+            let (_, workspace) = cx.add_window(
+                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
+                |cx| {
+                    let mut workspace = Workspace::new(
+                        Default::default(),
+                        0,
+                        project,
+                        app_state.dock_default_item_factory,
+                        cx,
+                    );
+                    (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
+                    workspace
+                },
+            );
+            workspace
+        };
 
-                    if let Some(follow_peer_id) = follow_peer_id {
-                        if !workspace.is_following(follow_peer_id) {
-                            workspace
-                                .toggle_follow(&ToggleFollow(follow_peer_id), cx)
-                                .map(|follow| follow.detach_and_log_err(cx));
-                        }
+        cx.activate_window(workspace.window_id());
+        cx.platform().activate(true);
+
+        workspace.update(&mut cx, |workspace, cx| {
+            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+                let follow_peer_id = room
+                    .read(cx)
+                    .remote_participants()
+                    .iter()
+                    .find(|(_, participant)| participant.user.id == follow_user_id)
+                    .map(|(_, p)| p.peer_id)
+                    .or_else(|| {
+                        // If we couldn't follow the given user, follow the host instead.
+                        let collaborator = workspace
+                            .project()
+                            .read(cx)
+                            .collaborators()
+                            .values()
+                            .find(|collaborator| collaborator.replica_id == 0)?;
+                        Some(collaborator.peer_id)
+                    });
+
+                if let Some(follow_peer_id) = follow_peer_id {
+                    if !workspace.is_following(follow_peer_id) {
+                        workspace
+                            .toggle_follow(&ToggleFollow(follow_peer_id), cx)
+                            .map(|follow| follow.detach_and_log_err(cx));
                     }
                 }
-            });
+            }
+        });
 
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    });
+        anyhow::Ok(())
+    })
+    .detach_and_log_err(cx);
 }

crates/call/src/indicator.rs → crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -1,10 +1,30 @@
+use call::ActiveCall;
 use gpui::{
     color::Color,
     elements::{MouseEventHandler, Svg},
-    Appearance, Element, ElementBox, Entity, MouseButton, RenderContext, View,
+    Appearance, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, View,
 };
+use settings::Settings;
 
-use crate::ToggleScreenSharing;
+use crate::collab_titlebar_item::ToggleScreenSharing;
+
+pub fn init(cx: &mut MutableAppContext) {
+    let active_call = ActiveCall::global(cx);
+
+    let mut status_indicator = None;
+    cx.observe(&active_call, move |call, cx| {
+        if let Some(room) = call.read(cx).room() {
+            if room.read(cx).is_screen_sharing() {
+                if status_indicator.is_none() && cx.global::<Settings>().show_call_status_icon {
+                    status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
+                }
+            } else if let Some((window_id, _)) = status_indicator.take() {
+                cx.remove_status_bar_item(window_id);
+            }
+        }
+    })
+    .detach();
+}
 
 pub struct SharingStatusIndicator;