Add top level leave call button

Julia and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

assets/icons/leave_12.svg                    |   3 
crates/collab_ui/src/collab_titlebar_item.rs | 113 +++++++++++++++------
crates/collab_ui/src/contact_list.rs         |  19 --
3 files changed, 86 insertions(+), 49 deletions(-)

Detailed changes

assets/icons/leave_12.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.585786 0.335786 0.25 0.75 0.25H7.25C7.66421 0.25 8 0.585786 8 1C8 1.41421 7.66421 1.75 7.25 1.75H1.5V10.25H7.25C7.66421 10.25 8 10.5858 8 11C8 11.4142 7.66421 11.75 7.25 11.75H0.75C0.335786 11.75 0 11.4142 0 11V1ZM8.78148 2.91435C9.10493 2.65559 9.57689 2.70803 9.83565 3.03148L11.8357 5.53148C12.0548 5.80539 12.0548 6.19461 11.8357 6.46852L9.83565 8.96852C9.57689 9.29197 9.10493 9.34441 8.78148 9.08565C8.45803 8.82689 8.40559 8.35493 8.66435 8.03148L9.68953 6.75H3.75C3.33579 6.75 3 6.41421 3 6C3 5.58579 3.33579 5.25 3.75 5.25H9.68953L8.66435 3.96852C8.40559 3.64507 8.45803 3.17311 8.78148 2.91435Z" fill="#ABB2BF"/>
+</svg>

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -12,6 +12,7 @@ use gpui::{
     color::Color,
     elements::*,
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
+    impl_internal_actions,
     json::{self, ToJson},
     CursorStyle, Entity, ImageData, ModelHandle, MouseButton, MutableAppContext, RenderContext,
     Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
@@ -32,6 +33,11 @@ actions!(
     ]
 );
 
+impl_internal_actions!(collab, [LeaveCall]);
+
+#[derive(Copy, Clone, PartialEq)]
+pub(crate) struct LeaveCall;
+
 #[derive(PartialEq, Eq)]
 enum ContactsPopoverSide {
     Left,
@@ -43,6 +49,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
     cx.add_action(CollabTitlebarItem::share_project);
     cx.add_action(CollabTitlebarItem::unshare_project);
+    cx.add_action(CollabTitlebarItem::leave_call);
 }
 
 pub struct CollabTitlebarItem {
@@ -104,9 +111,9 @@ impl View for CollabTitlebarItem {
 
         let mut right_container = Flex::row();
 
-        right_container.add_children(self.render_toggle_screen_sharing_button(&theme, cx));
-
-        if ActiveCall::global(cx).read(cx).room().is_some() {
+        if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+            right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
+            right_container.add_child(self.render_leave_call_button(&theme, cx));
             right_container
                 .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
         } else {
@@ -284,6 +291,12 @@ impl CollabTitlebarItem {
         cx.notify();
     }
 
+    fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
+        ActiveCall::global(cx)
+            .update(cx, |call, cx| call.hang_up(cx))
+            .log_err();
+    }
+
     fn render_toggle_contacts_button(
         &self,
         theme: &Theme,
@@ -351,13 +364,11 @@ impl CollabTitlebarItem {
     fn render_toggle_screen_sharing_button(
         &self,
         theme: &Theme,
+        room: &ModelHandle<Room>,
         cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
-        let active_call = ActiveCall::global(cx);
-        let room = active_call.read(cx).room().cloned()?;
+    ) -> ElementBox {
         let icon;
         let tooltip;
-
         if room.read(cx).is_screen_sharing() {
             icon = "icons/disable_screen_sharing_12.svg";
             tooltip = "Stop Sharing Screen"
@@ -367,35 +378,67 @@ impl CollabTitlebarItem {
         }
 
         let titlebar = &theme.workspace.titlebar;
-        Some(
-            MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
-                let style = titlebar.call_control.style_for(state, false);
-                Svg::new(icon)
-                    .with_color(style.color)
-                    .constrained()
-                    .with_width(style.icon_width)
-                    .aligned()
-                    .constrained()
-                    .with_width(style.button_width)
-                    .with_height(style.button_width)
-                    .contained()
-                    .with_style(style.container)
-                    .boxed()
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, cx| {
-                cx.dispatch_action(ToggleScreenSharing);
-            })
-            .with_tooltip::<ToggleScreenSharing, _>(
-                0,
-                tooltip.into(),
-                Some(Box::new(ToggleScreenSharing)),
-                theme.tooltip.clone(),
-                cx,
-            )
-            .aligned()
-            .boxed(),
+        MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
+            let style = titlebar.call_control.style_for(state, false);
+            Svg::new(icon)
+                .with_color(style.color)
+                .constrained()
+                .with_width(style.icon_width)
+                .aligned()
+                .constrained()
+                .with_width(style.button_width)
+                .with_height(style.button_width)
+                .contained()
+                .with_style(style.container)
+                .boxed()
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, cx| {
+            cx.dispatch_action(ToggleScreenSharing);
+        })
+        .with_tooltip::<ToggleScreenSharing, _>(
+            0,
+            tooltip.into(),
+            Some(Box::new(ToggleScreenSharing)),
+            theme.tooltip.clone(),
+            cx,
         )
+        .aligned()
+        .boxed()
+    }
+
+    fn render_leave_call_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+        let titlebar = &theme.workspace.titlebar;
+
+        MouseEventHandler::<LeaveCall>::new(0, cx, |state, _| {
+            let style = titlebar.call_control.style_for(state, false);
+            Svg::new("icons/leave_12.svg")
+                .with_color(style.color)
+                .constrained()
+                .with_width(style.icon_width)
+                .aligned()
+                .constrained()
+                .with_width(style.button_width)
+                .with_height(style.button_width)
+                .contained()
+                .with_style(style.container)
+                .boxed()
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, cx| {
+            cx.dispatch_action(LeaveCall);
+        })
+        .with_tooltip::<LeaveCall, _>(
+            0,
+            "Leave call".to_owned(),
+            Some(Box::new(LeaveCall)),
+            theme.tooltip.clone(),
+            cx,
+        )
+        .contained()
+        .with_margin_left(theme.workspace.titlebar.item_spacing)
+        .aligned()
+        .boxed()
     }
 
     fn render_in_call_share_unshare_button(

crates/collab_ui/src/contact_list.rs 🔗

@@ -1,3 +1,4 @@
+use super::collab_titlebar_item::LeaveCall;
 use crate::contacts_popover;
 use call::ActiveCall;
 use client::{proto::PeerId, Contact, User, UserStore};
@@ -18,11 +19,10 @@ use serde::Deserialize;
 use settings::Settings;
 use std::{mem, sync::Arc};
 use theme::IconButton;
-use util::ResultExt;
 use workspace::{JoinProject, OpenSharedScreen};
 
 impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
-impl_internal_actions!(contact_list, [ToggleExpanded, Call, LeaveCall]);
+impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ContactList::remove_contact);
@@ -33,7 +33,6 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ContactList::confirm);
     cx.add_action(ContactList::toggle_expanded);
     cx.add_action(ContactList::call);
-    cx.add_action(ContactList::leave_call);
 }
 
 #[derive(Clone, PartialEq)]
@@ -45,9 +44,6 @@ struct Call {
     initial_project: Option<ModelHandle<Project>>,
 }
 
-#[derive(Copy, Clone, PartialEq)]
-struct LeaveCall;
-
 #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
 enum Section {
     ActiveCall,
@@ -981,6 +977,7 @@ impl ContactList {
         cx: &mut RenderContext<Self>,
     ) -> ElementBox {
         enum Header {}
+        enum LeaveCallContactList {}
 
         let header_style = theme
             .header_row
@@ -993,9 +990,9 @@ impl ContactList {
         };
         let leave_call = if section == Section::ActiveCall {
             Some(
-                MouseEventHandler::<LeaveCall>::new(0, cx, |state, _| {
+                MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
                     let style = theme.leave_call.style_for(state, false);
-                    Label::new("Leave Session", style.text.clone())
+                    Label::new("Leave Call", style.text.clone())
                         .contained()
                         .with_style(style.container)
                         .boxed()
@@ -1284,12 +1281,6 @@ impl ContactList {
             })
             .detach_and_log_err(cx);
     }
-
-    fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
-        ActiveCall::global(cx)
-            .update(cx, |call, cx| call.hang_up(cx))
-            .log_err();
-    }
 }
 
 impl Entity for ContactList {