collab_titlebar_item.rs

   1// use crate::{
   2//     face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall,
   3//     ToggleDeafen, ToggleMute, ToggleScreenSharing,
   4// };
   5// use auto_update::AutoUpdateStatus;
   6// use call::{ActiveCall, ParticipantLocation, Room};
   7// use client::{proto::PeerId, Client, SignIn, SignOut, User, UserStore};
   8// use clock::ReplicaId;
   9// use context_menu::{ContextMenu, ContextMenuItem};
  10// use gpui::{
  11//     actions,
  12//     color::Color,
  13//     elements::*,
  14//     geometry::{rect::RectF, vector::vec2f, PathBuilder},
  15//     json::{self, ToJson},
  16//     platform::{CursorStyle, MouseButton},
  17//     AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
  18//     WeakViewHandle,
  19// };
  20// use picker::PickerEvent;
  21// use project::{Project, RepositoryEntry};
  22// use recent_projects::{build_recent_projects, RecentProjects};
  23// use std::{ops::Range, sync::Arc};
  24// use theme::{AvatarStyle, Theme};
  25// use util::ResultExt;
  26// use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
  27// use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB};
  28
  29use std::sync::Arc;
  30
  31use call::ActiveCall;
  32use client::{Client, UserStore};
  33use gpui::{
  34    div, px, rems, AppContext, Div, InteractiveElement, Model, ParentElement, Render, RenderOnce,
  35    Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
  36    WeakView, WindowBounds,
  37};
  38use project::Project;
  39use theme::ActiveTheme;
  40use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip};
  41use workspace::Workspace;
  42
  43// const MAX_PROJECT_NAME_LENGTH: usize = 40;
  44// const MAX_BRANCH_NAME_LENGTH: usize = 40;
  45
  46// actions!(
  47//     collab,
  48//     [
  49//         ToggleUserMenu,
  50//         ToggleProjectMenu,
  51//         SwitchBranch,
  52//         ShareProject,
  53//         UnshareProject,
  54//     ]
  55// );
  56
  57pub fn init(cx: &mut AppContext) {
  58    cx.observe_new_views(|workspace: &mut Workspace, cx| {
  59        let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
  60        workspace.set_titlebar_item(titlebar_item.into(), cx)
  61    })
  62    .detach();
  63    // cx.add_action(CollabTitlebarItem::share_project);
  64    // cx.add_action(CollabTitlebarItem::unshare_project);
  65    // cx.add_action(CollabTitlebarItem::toggle_user_menu);
  66    // cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
  67    // cx.add_action(CollabTitlebarItem::toggle_project_menu);
  68}
  69
  70pub struct CollabTitlebarItem {
  71    project: Model<Project>,
  72    #[allow(unused)] // todo!()
  73    user_store: Model<UserStore>,
  74    #[allow(unused)] // todo!()
  75    client: Arc<Client>,
  76    #[allow(unused)] // todo!()
  77    workspace: WeakView<Workspace>,
  78    //branch_popover: Option<ViewHandle<BranchList>>,
  79    //project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
  80    //user_menu: ViewHandle<ContextMenu>,
  81    _subscriptions: Vec<Subscription>,
  82}
  83
  84impl Render for CollabTitlebarItem {
  85    type Element = Stateful<Div>;
  86
  87    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
  88        h_stack()
  89            .id("titlebar")
  90            .justify_between()
  91            .w_full()
  92            .h(rems(1.75))
  93            // Set a non-scaling min-height here to ensure the titlebar is
  94            // always at least the height of the traffic lights.
  95            .min_h(px(32.))
  96            .when(
  97                !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
  98                // Use pixels here instead of a rem-based size because the macOS traffic
  99                // lights are a static size, and don't scale with the rest of the UI.
 100                |s| s.pl(px(68.)),
 101            )
 102            .bg(cx.theme().colors().title_bar_background)
 103            .on_click(|event, cx| {
 104                if event.up.click_count == 2 {
 105                    cx.zoom_window();
 106                }
 107            })
 108            .child(
 109                h_stack()
 110                    .gap_1()
 111                    // TODO - Add player menu
 112                    .child(
 113                        div()
 114                            .id("project_owner_indicator")
 115                            .child(
 116                                Button::new("player")
 117                                    .variant(ButtonVariant::Ghost)
 118                                    .color(Some(Color::Player(0))),
 119                            )
 120                            .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
 121                    )
 122                    // TODO - Add project menu
 123                    .child(
 124                        div()
 125                            .id("titlebar_project_menu_button")
 126                            .child(Button::new("project_name").variant(ButtonVariant::Ghost))
 127                            .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
 128                    )
 129                    // TODO - Add git menu
 130                    .child(
 131                        div()
 132                            .id("titlebar_git_menu_button")
 133                            .child(
 134                                Button::new("branch_name")
 135                                    .variant(ButtonVariant::Ghost)
 136                                    .color(Some(Color::Muted)),
 137                            )
 138                            .tooltip(move |cx| {
 139                                cx.build_view(|_| {
 140                                    Tooltip::new("Recent Branches")
 141                                        .key_binding(KeyBinding::new(gpui::KeyBinding::new(
 142                                            "cmd-b",
 143                                            // todo!() Replace with real action.
 144                                            gpui::NoAction,
 145                                            None,
 146                                        )))
 147                                        .meta("Only local branches shown")
 148                                })
 149                                .into()
 150                            }),
 151                    ),
 152            ) // self.titlebar_item
 153            .child(h_stack().child(Label::new("Right side titlebar item")))
 154    }
 155}
 156
 157// impl Entity for CollabTitlebarItem {
 158//     type Event = ();
 159// }
 160
 161// impl View for CollabTitlebarItem {
 162//     fn ui_name() -> &'static str {
 163//         "CollabTitlebarItem"
 164//     }
 165
 166//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 167//         let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
 168//             workspace
 169//         } else {
 170//             return Empty::new().into_any();
 171//         };
 172
 173//         let theme = theme::current(cx).clone();
 174//         let mut left_container = Flex::row();
 175//         let mut right_container = Flex::row().align_children_center();
 176
 177//         left_container.add_child(self.collect_title_root_names(theme.clone(), cx));
 178
 179//         let user = self.user_store.read(cx).current_user();
 180//         let peer_id = self.client.peer_id();
 181//         if let Some(((user, peer_id), room)) = user
 182//             .as_ref()
 183//             .zip(peer_id)
 184//             .zip(ActiveCall::global(cx).read(cx).room().cloned())
 185//         {
 186//             if room.read(cx).can_publish() {
 187//                 right_container
 188//                     .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
 189//             }
 190//             right_container.add_child(self.render_leave_call(&theme, cx));
 191//             let muted = room.read(cx).is_muted(cx);
 192//             let speaking = room.read(cx).is_speaking();
 193//             left_container.add_child(
 194//                 self.render_current_user(&workspace, &theme, &user, peer_id, muted, speaking, cx),
 195//             );
 196//             left_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
 197//             if room.read(cx).can_publish() {
 198//                 right_container.add_child(self.render_toggle_mute(&theme, &room, cx));
 199//             }
 200//             right_container.add_child(self.render_toggle_deafen(&theme, &room, cx));
 201//             if room.read(cx).can_publish() {
 202//                 right_container
 203//                     .add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
 204//             }
 205//         }
 206
 207//         let status = workspace.read(cx).client().status();
 208//         let status = &*status.borrow();
 209//         if matches!(status, client::Status::Connected { .. }) {
 210//             let avatar = user.as_ref().and_then(|user| user.avatar.clone());
 211//             right_container.add_child(self.render_user_menu_button(&theme, avatar, cx));
 212//         } else {
 213//             right_container.add_children(self.render_connection_status(status, cx));
 214//             right_container.add_child(self.render_sign_in_button(&theme, cx));
 215//             right_container.add_child(self.render_user_menu_button(&theme, None, cx));
 216//         }
 217
 218//         Stack::new()
 219//             .with_child(left_container)
 220//             .with_child(
 221//                 Flex::row()
 222//                     .with_child(
 223//                         right_container.contained().with_background_color(
 224//                             theme
 225//                                 .titlebar
 226//                                 .container
 227//                                 .background_color
 228//                                 .unwrap_or_else(|| Color::transparent_black()),
 229//                         ),
 230//                     )
 231//                     .aligned()
 232//                     .right(),
 233//             )
 234//             .into_any()
 235//     }
 236// }
 237
 238impl CollabTitlebarItem {
 239    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
 240        let project = workspace.project().clone();
 241        let user_store = workspace.app_state().user_store.clone();
 242        let client = workspace.app_state().client.clone();
 243        let active_call = ActiveCall::global(cx);
 244        let mut subscriptions = Vec::new();
 245        subscriptions.push(
 246            cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
 247                cx.notify()
 248            }),
 249        );
 250        subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
 251        subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
 252        subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
 253        subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
 254
 255        Self {
 256            workspace: workspace.weak_handle(),
 257            project,
 258            user_store,
 259            client,
 260            //         user_menu: cx.add_view(|cx| {
 261            //             let view_id = cx.view_id();
 262            //             let mut menu = ContextMenu::new(view_id, cx);
 263            //             menu.set_position_mode(OverlayPositionMode::Local);
 264            //             menu
 265            //         }),
 266            //         branch_popover: None,
 267            //         project_popover: None,
 268            _subscriptions: subscriptions,
 269        }
 270    }
 271
 272    // fn collect_title_root_names(
 273    //     &self,
 274    //     theme: Arc<Theme>,
 275    //     cx: &mut ViewContext<Self>,
 276    // ) -> AnyElement<Self> {
 277    //     let project = self.project.read(cx);
 278
 279    //     let (name, entry) = {
 280    //         let mut names_and_branches = project.visible_worktrees(cx).map(|worktree| {
 281    //             let worktree = worktree.read(cx);
 282    //             (worktree.root_name(), worktree.root_git_entry())
 283    //         });
 284
 285    //         names_and_branches.next().unwrap_or(("", None))
 286    //     };
 287
 288    //     let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
 289    //     let branch_prepended = entry
 290    //         .as_ref()
 291    //         .and_then(RepositoryEntry::branch)
 292    //         .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH));
 293    //     let project_style = theme.titlebar.project_menu_button.clone();
 294    //     let git_style = theme.titlebar.git_menu_button.clone();
 295    //     let item_spacing = theme.titlebar.item_spacing;
 296
 297    //     let mut ret = Flex::row();
 298
 299    //     if let Some(project_host) = self.collect_project_host(theme.clone(), cx) {
 300    //         ret = ret.with_child(project_host)
 301    //     }
 302
 303    //     ret = ret.with_child(
 304    //         Stack::new()
 305    //             .with_child(
 306    //                 MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
 307    //                     let style = project_style
 308    //                         .in_state(self.project_popover.is_some())
 309    //                         .style_for(mouse_state);
 310    //                     enum RecentProjectsTooltip {}
 311    //                     Label::new(name, style.text.clone())
 312    //                         .contained()
 313    //                         .with_style(style.container)
 314    //                         .aligned()
 315    //                         .left()
 316    //                         .with_tooltip::<RecentProjectsTooltip>(
 317    //                             0,
 318    //                             "Recent projects",
 319    //                             Some(Box::new(recent_projects::OpenRecent)),
 320    //                             theme.tooltip.clone(),
 321    //                             cx,
 322    //                         )
 323    //                         .into_any_named("title-project-name")
 324    //                 })
 325    //                 .with_cursor_style(CursorStyle::PointingHand)
 326    //                 .on_down(MouseButton::Left, move |_, this, cx| {
 327    //                     this.toggle_project_menu(&Default::default(), cx)
 328    //                 })
 329    //                 .on_click(MouseButton::Left, move |_, _, _| {}),
 330    //             )
 331    //             .with_children(self.render_project_popover_host(&theme.titlebar, cx)),
 332    //     );
 333    //     if let Some(git_branch) = branch_prepended {
 334    //         ret = ret.with_child(
 335    //             Flex::row().with_child(
 336    //                 Stack::new()
 337    //                     .with_child(
 338    //                         MouseEventHandler::new::<ToggleVcsMenu, _>(0, cx, |mouse_state, cx| {
 339    //                             enum BranchPopoverTooltip {}
 340    //                             let style = git_style
 341    //                                 .in_state(self.branch_popover.is_some())
 342    //                                 .style_for(mouse_state);
 343    //                             Label::new(git_branch, style.text.clone())
 344    //                                 .contained()
 345    //                                 .with_style(style.container.clone())
 346    //                                 .with_margin_right(item_spacing)
 347    //                                 .aligned()
 348    //                                 .left()
 349    //                                 .with_tooltip::<BranchPopoverTooltip>(
 350    //                                     0,
 351    //                                     "Recent branches",
 352    //                                     Some(Box::new(ToggleVcsMenu)),
 353    //                                     theme.tooltip.clone(),
 354    //                                     cx,
 355    //                                 )
 356    //                                 .into_any_named("title-project-branch")
 357    //                         })
 358    //                         .with_cursor_style(CursorStyle::PointingHand)
 359    //                         .on_down(MouseButton::Left, move |_, this, cx| {
 360    //                             this.toggle_vcs_menu(&Default::default(), cx)
 361    //                         })
 362    //                         .on_click(MouseButton::Left, move |_, _, _| {}),
 363    //                     )
 364    //                     .with_children(self.render_branches_popover_host(&theme.titlebar, cx)),
 365    //             ),
 366    //         )
 367    //     }
 368    //     ret.into_any()
 369    // }
 370
 371    // fn collect_project_host(
 372    //     &self,
 373    //     theme: Arc<Theme>,
 374    //     cx: &mut ViewContext<Self>,
 375    // ) -> Option<AnyElement<Self>> {
 376    //     if ActiveCall::global(cx).read(cx).room().is_none() {
 377    //         return None;
 378    //     }
 379    //     let project = self.project.read(cx);
 380    //     let user_store = self.user_store.read(cx);
 381
 382    //     if project.is_local() {
 383    //         return None;
 384    //     }
 385
 386    //     let Some(host) = project.host() else {
 387    //         return None;
 388    //     };
 389    //     let (Some(host_user), Some(participant_index)) = (
 390    //         user_store.get_cached_user(host.user_id),
 391    //         user_store.participant_indices().get(&host.user_id),
 392    //     ) else {
 393    //         return None;
 394    //     };
 395
 396    //     enum ProjectHost {}
 397    //     enum ProjectHostTooltip {}
 398
 399    //     let host_style = theme.titlebar.project_host.clone();
 400    //     let selection_style = theme
 401    //         .editor
 402    //         .selection_style_for_room_participant(participant_index.0);
 403    //     let peer_id = host.peer_id.clone();
 404
 405    //     Some(
 406    //         MouseEventHandler::new::<ProjectHost, _>(0, cx, |mouse_state, _| {
 407    //             let mut host_style = host_style.style_for(mouse_state).clone();
 408    //             host_style.text.color = selection_style.cursor;
 409    //             Label::new(host_user.github_login.clone(), host_style.text)
 410    //                 .contained()
 411    //                 .with_style(host_style.container)
 412    //                 .aligned()
 413    //                 .left()
 414    //         })
 415    //         .with_cursor_style(CursorStyle::PointingHand)
 416    //         .on_click(MouseButton::Left, move |_, this, cx| {
 417    //             if let Some(workspace) = this.workspace.upgrade(cx) {
 418    //                 if let Some(task) =
 419    //                     workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
 420    //                 {
 421    //                     task.detach_and_log_err(cx);
 422    //                 }
 423    //             }
 424    //         })
 425    //         .with_tooltip::<ProjectHostTooltip>(
 426    //             0,
 427    //             host_user.github_login.clone() + " is sharing this project. Click to follow.",
 428    //             None,
 429    //             theme.tooltip.clone(),
 430    //             cx,
 431    //         )
 432    //         .into_any_named("project-host"),
 433    //     )
 434    // }
 435
 436    fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
 437        let project = if cx.is_window_active() {
 438            Some(self.project.clone())
 439        } else {
 440            None
 441        };
 442        ActiveCall::global(cx)
 443            .update(cx, |call, cx| call.set_location(project.as_ref(), cx))
 444            .detach_and_log_err(cx);
 445    }
 446
 447    fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
 448        cx.notify();
 449    }
 450
 451    // fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
 452    //     let active_call = ActiveCall::global(cx);
 453    //     let project = self.project.clone();
 454    //     active_call
 455    //         .update(cx, |call, cx| call.share_project(project, cx))
 456    //         .detach_and_log_err(cx);
 457    // }
 458
 459    // fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
 460    //     let active_call = ActiveCall::global(cx);
 461    //     let project = self.project.clone();
 462    //     active_call
 463    //         .update(cx, |call, cx| call.unshare_project(project, cx))
 464    //         .log_err();
 465    // }
 466
 467    // pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
 468    //     self.user_menu.update(cx, |user_menu, cx| {
 469    //         let items = if let Some(_) = self.user_store.read(cx).current_user() {
 470    //             vec![
 471    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
 472    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
 473    //                 ContextMenuItem::separator(),
 474    //                 ContextMenuItem::action(
 475    //                     "Share Feedback",
 476    //                     feedback::feedback_editor::GiveFeedback,
 477    //                 ),
 478    //                 ContextMenuItem::action("Sign Out", SignOut),
 479    //             ]
 480    //         } else {
 481    //             vec![
 482    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
 483    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
 484    //                 ContextMenuItem::separator(),
 485    //                 ContextMenuItem::action(
 486    //                     "Share Feedback",
 487    //                     feedback::feedback_editor::GiveFeedback,
 488    //                 ),
 489    //             ]
 490    //         };
 491    //         user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
 492    //     });
 493    // }
 494
 495    // fn render_branches_popover_host<'a>(
 496    //     &'a self,
 497    //     _theme: &'a theme::Titlebar,
 498    //     cx: &'a mut ViewContext<Self>,
 499    // ) -> Option<AnyElement<Self>> {
 500    //     self.branch_popover.as_ref().map(|child| {
 501    //         let theme = theme::current(cx).clone();
 502    //         let child = ChildView::new(child, cx);
 503    //         let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
 504    //             child
 505    //                 .flex(1., true)
 506    //                 .contained()
 507    //                 .constrained()
 508    //                 .with_width(theme.titlebar.menu.width)
 509    //                 .with_height(theme.titlebar.menu.height)
 510    //         })
 511    //         .on_click(MouseButton::Left, |_, _, _| {})
 512    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
 513    //             this.branch_popover.take();
 514    //             cx.emit(());
 515    //             cx.notify();
 516    //         })
 517    //         .contained()
 518    //         .into_any();
 519
 520    //         Overlay::new(child)
 521    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
 522    //             .with_anchor_corner(AnchorCorner::TopLeft)
 523    //             .with_z_index(999)
 524    //             .aligned()
 525    //             .bottom()
 526    //             .left()
 527    //             .into_any()
 528    //     })
 529    // }
 530
 531    // fn render_project_popover_host<'a>(
 532    //     &'a self,
 533    //     _theme: &'a theme::Titlebar,
 534    //     cx: &'a mut ViewContext<Self>,
 535    // ) -> Option<AnyElement<Self>> {
 536    //     self.project_popover.as_ref().map(|child| {
 537    //         let theme = theme::current(cx).clone();
 538    //         let child = ChildView::new(child, cx);
 539    //         let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
 540    //             child
 541    //                 .flex(1., true)
 542    //                 .contained()
 543    //                 .constrained()
 544    //                 .with_width(theme.titlebar.menu.width)
 545    //                 .with_height(theme.titlebar.menu.height)
 546    //         })
 547    //         .on_click(MouseButton::Left, |_, _, _| {})
 548    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
 549    //             this.project_popover.take();
 550    //             cx.emit(());
 551    //             cx.notify();
 552    //         })
 553    //         .into_any();
 554
 555    //         Overlay::new(child)
 556    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
 557    //             .with_anchor_corner(AnchorCorner::TopLeft)
 558    //             .with_z_index(999)
 559    //             .aligned()
 560    //             .bottom()
 561    //             .left()
 562    //             .into_any()
 563    //     })
 564    // }
 565
 566    // pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
 567    //     if self.branch_popover.take().is_none() {
 568    //         if let Some(workspace) = self.workspace.upgrade(cx) {
 569    //             let Some(view) =
 570    //                 cx.add_option_view(|cx| build_branch_list(workspace, cx).log_err())
 571    //             else {
 572    //                 return;
 573    //             };
 574    //             cx.subscribe(&view, |this, _, event, cx| {
 575    //                 match event {
 576    //                     PickerEvent::Dismiss => {
 577    //                         this.branch_popover = None;
 578    //                     }
 579    //                 }
 580
 581    //                 cx.notify();
 582    //             })
 583    //             .detach();
 584    //             self.project_popover.take();
 585    //             cx.focus(&view);
 586    //             self.branch_popover = Some(view);
 587    //         }
 588    //     }
 589
 590    //     cx.notify();
 591    // }
 592
 593    // pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
 594    //     let workspace = self.workspace.clone();
 595    //     if self.project_popover.take().is_none() {
 596    //         cx.spawn(|this, mut cx| async move {
 597    //             let workspaces = WORKSPACE_DB
 598    //                 .recent_workspaces_on_disk()
 599    //                 .await
 600    //                 .unwrap_or_default()
 601    //                 .into_iter()
 602    //                 .map(|(_, location)| location)
 603    //                 .collect();
 604
 605    //             let workspace = workspace.clone();
 606    //             this.update(&mut cx, move |this, cx| {
 607    //                 let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
 608
 609    //                 cx.subscribe(&view, |this, _, event, cx| {
 610    //                     match event {
 611    //                         PickerEvent::Dismiss => {
 612    //                             this.project_popover = None;
 613    //                         }
 614    //                     }
 615
 616    //                     cx.notify();
 617    //                 })
 618    //                 .detach();
 619    //                 cx.focus(&view);
 620    //                 this.branch_popover.take();
 621    //                 this.project_popover = Some(view);
 622    //                 cx.notify();
 623    //             })
 624    //             .log_err();
 625    //         })
 626    //         .detach();
 627    //     }
 628    //     cx.notify();
 629    // }
 630
 631    // fn render_toggle_screen_sharing_button(
 632    //     &self,
 633    //     theme: &Theme,
 634    //     room: &ModelHandle<Room>,
 635    //     cx: &mut ViewContext<Self>,
 636    // ) -> AnyElement<Self> {
 637    //     let icon;
 638    //     let tooltip;
 639    //     if room.read(cx).is_screen_sharing() {
 640    //         icon = "icons/desktop.svg";
 641    //         tooltip = "Stop Sharing Screen"
 642    //     } else {
 643    //         icon = "icons/desktop.svg";
 644    //         tooltip = "Share Screen";
 645    //     }
 646
 647    //     let active = room.read(cx).is_screen_sharing();
 648    //     let titlebar = &theme.titlebar;
 649    //     MouseEventHandler::new::<ToggleScreenSharing, _>(0, cx, |state, _| {
 650    //         let style = titlebar
 651    //             .screen_share_button
 652    //             .in_state(active)
 653    //             .style_for(state);
 654
 655    //         Svg::new(icon)
 656    //             .with_color(style.color)
 657    //             .constrained()
 658    //             .with_width(style.icon_width)
 659    //             .aligned()
 660    //             .constrained()
 661    //             .with_width(style.button_width)
 662    //             .with_height(style.button_width)
 663    //             .contained()
 664    //             .with_style(style.container)
 665    //     })
 666    //     .with_cursor_style(CursorStyle::PointingHand)
 667    //     .on_click(MouseButton::Left, move |_, _, cx| {
 668    //         toggle_screen_sharing(&Default::default(), cx)
 669    //     })
 670    //     .with_tooltip::<ToggleScreenSharing>(
 671    //         0,
 672    //         tooltip,
 673    //         Some(Box::new(ToggleScreenSharing)),
 674    //         theme.tooltip.clone(),
 675    //         cx,
 676    //     )
 677    //     .aligned()
 678    //     .into_any()
 679    // }
 680    // fn render_toggle_mute(
 681    //     &self,
 682    //     theme: &Theme,
 683    //     room: &ModelHandle<Room>,
 684    //     cx: &mut ViewContext<Self>,
 685    // ) -> AnyElement<Self> {
 686    //     let icon;
 687    //     let tooltip;
 688    //     let is_muted = room.read(cx).is_muted(cx);
 689    //     if is_muted {
 690    //         icon = "icons/mic-mute.svg";
 691    //         tooltip = "Unmute microphone";
 692    //     } else {
 693    //         icon = "icons/mic.svg";
 694    //         tooltip = "Mute microphone";
 695    //     }
 696
 697    //     let titlebar = &theme.titlebar;
 698    //     MouseEventHandler::new::<ToggleMute, _>(0, cx, |state, _| {
 699    //         let style = titlebar
 700    //             .toggle_microphone_button
 701    //             .in_state(is_muted)
 702    //             .style_for(state);
 703    //         let image = Svg::new(icon)
 704    //             .with_color(style.color)
 705    //             .constrained()
 706    //             .with_width(style.icon_width)
 707    //             .aligned()
 708    //             .constrained()
 709    //             .with_width(style.button_width)
 710    //             .with_height(style.button_width)
 711    //             .contained()
 712    //             .with_style(style.container);
 713    //         if let Some(color) = style.container.background_color {
 714    //             image.with_background_color(color)
 715    //         } else {
 716    //             image
 717    //         }
 718    //     })
 719    //     .with_cursor_style(CursorStyle::PointingHand)
 720    //     .on_click(MouseButton::Left, move |_, _, cx| {
 721    //         toggle_mute(&Default::default(), cx)
 722    //     })
 723    //     .with_tooltip::<ToggleMute>(
 724    //         0,
 725    //         tooltip,
 726    //         Some(Box::new(ToggleMute)),
 727    //         theme.tooltip.clone(),
 728    //         cx,
 729    //     )
 730    //     .aligned()
 731    //     .into_any()
 732    // }
 733    // fn render_toggle_deafen(
 734    //     &self,
 735    //     theme: &Theme,
 736    //     room: &ModelHandle<Room>,
 737    //     cx: &mut ViewContext<Self>,
 738    // ) -> AnyElement<Self> {
 739    //     let icon;
 740    //     let tooltip;
 741    //     let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
 742    //     if is_deafened {
 743    //         icon = "icons/speaker-off.svg";
 744    //         tooltip = "Unmute speakers";
 745    //     } else {
 746    //         icon = "icons/speaker-loud.svg";
 747    //         tooltip = "Mute speakers";
 748    //     }
 749
 750    //     let titlebar = &theme.titlebar;
 751    //     MouseEventHandler::new::<ToggleDeafen, _>(0, cx, |state, _| {
 752    //         let style = titlebar
 753    //             .toggle_speakers_button
 754    //             .in_state(is_deafened)
 755    //             .style_for(state);
 756    //         Svg::new(icon)
 757    //             .with_color(style.color)
 758    //             .constrained()
 759    //             .with_width(style.icon_width)
 760    //             .aligned()
 761    //             .constrained()
 762    //             .with_width(style.button_width)
 763    //             .with_height(style.button_width)
 764    //             .contained()
 765    //             .with_style(style.container)
 766    //     })
 767    //     .with_cursor_style(CursorStyle::PointingHand)
 768    //     .on_click(MouseButton::Left, move |_, _, cx| {
 769    //         toggle_deafen(&Default::default(), cx)
 770    //     })
 771    //     .with_tooltip::<ToggleDeafen>(
 772    //         0,
 773    //         tooltip,
 774    //         Some(Box::new(ToggleDeafen)),
 775    //         theme.tooltip.clone(),
 776    //         cx,
 777    //     )
 778    //     .aligned()
 779    //     .into_any()
 780    // }
 781    // fn render_leave_call(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 782    //     let icon = "icons/exit.svg";
 783    //     let tooltip = "Leave call";
 784
 785    //     let titlebar = &theme.titlebar;
 786    //     MouseEventHandler::new::<LeaveCall, _>(0, cx, |state, _| {
 787    //         let style = titlebar.leave_call_button.style_for(state);
 788    //         Svg::new(icon)
 789    //             .with_color(style.color)
 790    //             .constrained()
 791    //             .with_width(style.icon_width)
 792    //             .aligned()
 793    //             .constrained()
 794    //             .with_width(style.button_width)
 795    //             .with_height(style.button_width)
 796    //             .contained()
 797    //             .with_style(style.container)
 798    //     })
 799    //     .with_cursor_style(CursorStyle::PointingHand)
 800    //     .on_click(MouseButton::Left, move |_, _, cx| {
 801    //         ActiveCall::global(cx)
 802    //             .update(cx, |call, cx| call.hang_up(cx))
 803    //             .detach_and_log_err(cx);
 804    //     })
 805    //     .with_tooltip::<LeaveCall>(
 806    //         0,
 807    //         tooltip,
 808    //         Some(Box::new(LeaveCall)),
 809    //         theme.tooltip.clone(),
 810    //         cx,
 811    //     )
 812    //     .aligned()
 813    //     .into_any()
 814    // }
 815    // fn render_in_call_share_unshare_button(
 816    //     &self,
 817    //     workspace: &ViewHandle<Workspace>,
 818    //     theme: &Theme,
 819    //     cx: &mut ViewContext<Self>,
 820    // ) -> Option<AnyElement<Self>> {
 821    //     let project = workspace.read(cx).project();
 822    //     if project.read(cx).is_remote() {
 823    //         return None;
 824    //     }
 825
 826    //     let is_shared = project.read(cx).is_shared();
 827    //     let label = if is_shared { "Stop Sharing" } else { "Share" };
 828    //     let tooltip = if is_shared {
 829    //         "Stop sharing project with call participants"
 830    //     } else {
 831    //         "Share project with call participants"
 832    //     };
 833
 834    //     let titlebar = &theme.titlebar;
 835
 836    //     enum ShareUnshare {}
 837    //     Some(
 838    //         Stack::new()
 839    //             .with_child(
 840    //                 MouseEventHandler::new::<ShareUnshare, _>(0, cx, |state, _| {
 841    //                     //TODO: Ensure this button has consistent width for both text variations
 842    //                     let style = titlebar.share_button.inactive_state().style_for(state);
 843    //                     Label::new(label, style.text.clone())
 844    //                         .contained()
 845    //                         .with_style(style.container)
 846    //                 })
 847    //                 .with_cursor_style(CursorStyle::PointingHand)
 848    //                 .on_click(MouseButton::Left, move |_, this, cx| {
 849    //                     if is_shared {
 850    //                         this.unshare_project(&Default::default(), cx);
 851    //                     } else {
 852    //                         this.share_project(&Default::default(), cx);
 853    //                     }
 854    //                 })
 855    //                 .with_tooltip::<ShareUnshare>(
 856    //                     0,
 857    //                     tooltip.to_owned(),
 858    //                     None,
 859    //                     theme.tooltip.clone(),
 860    //                     cx,
 861    //                 ),
 862    //             )
 863    //             .aligned()
 864    //             .contained()
 865    //             .with_margin_left(theme.titlebar.item_spacing)
 866    //             .into_any(),
 867    //     )
 868    // }
 869
 870    // fn render_user_menu_button(
 871    //     &self,
 872    //     theme: &Theme,
 873    //     avatar: Option<Arc<ImageData>>,
 874    //     cx: &mut ViewContext<Self>,
 875    // ) -> AnyElement<Self> {
 876    //     let tooltip = theme.tooltip.clone();
 877    //     let user_menu_button_style = if avatar.is_some() {
 878    //         &theme.titlebar.user_menu.user_menu_button_online
 879    //     } else {
 880    //         &theme.titlebar.user_menu.user_menu_button_offline
 881    //     };
 882
 883    //     let avatar_style = &user_menu_button_style.avatar;
 884    //     Stack::new()
 885    //         .with_child(
 886    //             MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
 887    //                 let style = user_menu_button_style
 888    //                     .user_menu
 889    //                     .inactive_state()
 890    //                     .style_for(state);
 891
 892    //                 let mut dropdown = Flex::row().align_children_center();
 893
 894    //                 if let Some(avatar_img) = avatar {
 895    //                     dropdown = dropdown.with_child(Self::render_face(
 896    //                         avatar_img,
 897    //                         *avatar_style,
 898    //                         Color::transparent_black(),
 899    //                         None,
 900    //                     ));
 901    //                 };
 902
 903    //                 dropdown
 904    //                     .with_child(
 905    //                         Svg::new("icons/caret_down.svg")
 906    //                             .with_color(user_menu_button_style.icon.color)
 907    //                             .constrained()
 908    //                             .with_width(user_menu_button_style.icon.width)
 909    //                             .contained()
 910    //                             .into_any(),
 911    //                     )
 912    //                     .aligned()
 913    //                     .constrained()
 914    //                     .with_height(style.width)
 915    //                     .contained()
 916    //                     .with_style(style.container)
 917    //                     .into_any()
 918    //             })
 919    //             .with_cursor_style(CursorStyle::PointingHand)
 920    //             .on_down(MouseButton::Left, move |_, this, cx| {
 921    //                 this.user_menu.update(cx, |menu, _| menu.delay_cancel());
 922    //             })
 923    //             .on_click(MouseButton::Left, move |_, this, cx| {
 924    //                 this.toggle_user_menu(&Default::default(), cx)
 925    //             })
 926    //             .with_tooltip::<ToggleUserMenu>(
 927    //                 0,
 928    //                 "Toggle User Menu".to_owned(),
 929    //                 Some(Box::new(ToggleUserMenu)),
 930    //                 tooltip,
 931    //                 cx,
 932    //             )
 933    //             .contained(),
 934    //         )
 935    //         .with_child(
 936    //             ChildView::new(&self.user_menu, cx)
 937    //                 .aligned()
 938    //                 .bottom()
 939    //                 .right(),
 940    //         )
 941    //         .into_any()
 942    // }
 943
 944    // fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 945    //     let titlebar = &theme.titlebar;
 946    //     MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
 947    //         let style = titlebar.sign_in_button.inactive_state().style_for(state);
 948    //         Label::new("Sign In", style.text.clone())
 949    //             .contained()
 950    //             .with_style(style.container)
 951    //     })
 952    //     .with_cursor_style(CursorStyle::PointingHand)
 953    //     .on_click(MouseButton::Left, move |_, this, cx| {
 954    //         let client = this.client.clone();
 955    //         cx.app_context()
 956    //             .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
 957    //             .detach_and_log_err(cx);
 958    //     })
 959    //     .into_any()
 960    // }
 961
 962    // fn render_collaborators(
 963    //     &self,
 964    //     workspace: &ViewHandle<Workspace>,
 965    //     theme: &Theme,
 966    //     room: &ModelHandle<Room>,
 967    //     cx: &mut ViewContext<Self>,
 968    // ) -> Vec<Container<Self>> {
 969    //     let mut participants = room
 970    //         .read(cx)
 971    //         .remote_participants()
 972    //         .values()
 973    //         .cloned()
 974    //         .collect::<Vec<_>>();
 975    //     participants.sort_by_cached_key(|p| p.user.github_login.clone());
 976
 977    //     participants
 978    //         .into_iter()
 979    //         .filter_map(|participant| {
 980    //             let project = workspace.read(cx).project().read(cx);
 981    //             let replica_id = project
 982    //                 .collaborators()
 983    //                 .get(&participant.peer_id)
 984    //                 .map(|collaborator| collaborator.replica_id);
 985    //             let user = participant.user.clone();
 986    //             Some(
 987    //                 Container::new(self.render_face_pile(
 988    //                     &user,
 989    //                     replica_id,
 990    //                     participant.peer_id,
 991    //                     Some(participant.location),
 992    //                     participant.muted,
 993    //                     participant.speaking,
 994    //                     workspace,
 995    //                     theme,
 996    //                     cx,
 997    //                 ))
 998    //                 .with_margin_right(theme.titlebar.face_pile_spacing),
 999    //             )
1000    //         })
1001    //         .collect()
1002    // }
1003
1004    // fn render_current_user(
1005    //     &self,
1006    //     workspace: &ViewHandle<Workspace>,
1007    //     theme: &Theme,
1008    //     user: &Arc<User>,
1009    //     peer_id: PeerId,
1010    //     muted: bool,
1011    //     speaking: bool,
1012    //     cx: &mut ViewContext<Self>,
1013    // ) -> AnyElement<Self> {
1014    //     let replica_id = workspace.read(cx).project().read(cx).replica_id();
1015
1016    //     Container::new(self.render_face_pile(
1017    //         user,
1018    //         Some(replica_id),
1019    //         peer_id,
1020    //         None,
1021    //         muted,
1022    //         speaking,
1023    //         workspace,
1024    //         theme,
1025    //         cx,
1026    //     ))
1027    //     .with_margin_right(theme.titlebar.item_spacing)
1028    //     .into_any()
1029    // }
1030
1031    // fn render_face_pile(
1032    //     &self,
1033    //     user: &User,
1034    //     _replica_id: Option<ReplicaId>,
1035    //     peer_id: PeerId,
1036    //     location: Option<ParticipantLocation>,
1037    //     muted: bool,
1038    //     speaking: bool,
1039    //     workspace: &ViewHandle<Workspace>,
1040    //     theme: &Theme,
1041    //     cx: &mut ViewContext<Self>,
1042    // ) -> AnyElement<Self> {
1043    //     let user_id = user.id;
1044    //     let project_id = workspace.read(cx).project().read(cx).remote_id();
1045    //     let room = ActiveCall::global(cx).read(cx).room().cloned();
1046    //     let self_peer_id = workspace.read(cx).client().peer_id();
1047    //     let self_following = workspace.read(cx).is_being_followed(peer_id);
1048    //     let self_following_initialized = self_following
1049    //         && room.as_ref().map_or(false, |room| match project_id {
1050    //             None => true,
1051    //             Some(project_id) => room
1052    //                 .read(cx)
1053    //                 .followers_for(peer_id, project_id)
1054    //                 .iter()
1055    //                 .any(|&follower| Some(follower) == self_peer_id),
1056    //         });
1057
1058    //     let leader_style = theme.titlebar.leader_avatar;
1059    //     let follower_style = theme.titlebar.follower_avatar;
1060
1061    //     let microphone_state = if muted {
1062    //         Some(theme.titlebar.muted)
1063    //     } else if speaking {
1064    //         Some(theme.titlebar.speaking)
1065    //     } else {
1066    //         None
1067    //     };
1068
1069    //     let mut background_color = theme
1070    //         .titlebar
1071    //         .container
1072    //         .background_color
1073    //         .unwrap_or_default();
1074
1075    //     let participant_index = self
1076    //         .user_store
1077    //         .read(cx)
1078    //         .participant_indices()
1079    //         .get(&user_id)
1080    //         .copied();
1081    //     if let Some(participant_index) = participant_index {
1082    //         if self_following_initialized {
1083    //             let selection = theme
1084    //                 .editor
1085    //                 .selection_style_for_room_participant(participant_index.0)
1086    //                 .selection;
1087    //             background_color = Color::blend(selection, background_color);
1088    //             background_color.a = 255;
1089    //         }
1090    //     }
1091
1092    //     enum TitlebarParticipant {}
1093
1094    //     let content = MouseEventHandler::new::<TitlebarParticipant, _>(
1095    //         peer_id.as_u64() as usize,
1096    //         cx,
1097    //         move |_, cx| {
1098    //             Stack::new()
1099    //                 .with_children(user.avatar.as_ref().map(|avatar| {
1100    //                     let face_pile = FacePile::new(theme.titlebar.follower_avatar_overlap)
1101    //                         .with_child(Self::render_face(
1102    //                             avatar.clone(),
1103    //                             Self::location_style(workspace, location, leader_style, cx),
1104    //                             background_color,
1105    //                             microphone_state,
1106    //                         ))
1107    //                         .with_children(
1108    //                             (|| {
1109    //                                 let project_id = project_id?;
1110    //                                 let room = room?.read(cx);
1111    //                                 let followers = room.followers_for(peer_id, project_id);
1112    //                                 Some(followers.into_iter().filter_map(|&follower| {
1113    //                                     if Some(follower) == self_peer_id {
1114    //                                         return None;
1115    //                                     }
1116    //                                     let participant =
1117    //                                         room.remote_participant_for_peer_id(follower)?;
1118    //                                     Some(Self::render_face(
1119    //                                         participant.user.avatar.clone()?,
1120    //                                         follower_style,
1121    //                                         background_color,
1122    //                                         None,
1123    //                                     ))
1124    //                                 }))
1125    //                             })()
1126    //                             .into_iter()
1127    //                             .flatten(),
1128    //                         )
1129    //                         .with_children(
1130    //                             self_following_initialized
1131    //                                 .then(|| self.user_store.read(cx).current_user())
1132    //                                 .and_then(|user| {
1133    //                                     Some(Self::render_face(
1134    //                                         user?.avatar.clone()?,
1135    //                                         follower_style,
1136    //                                         background_color,
1137    //                                         None,
1138    //                                     ))
1139    //                                 }),
1140    //                         );
1141
1142    //                     let mut container = face_pile
1143    //                         .contained()
1144    //                         .with_style(theme.titlebar.leader_selection);
1145
1146    //                     if let Some(participant_index) = participant_index {
1147    //                         if self_following_initialized {
1148    //                             let color = theme
1149    //                                 .editor
1150    //                                 .selection_style_for_room_participant(participant_index.0)
1151    //                                 .selection;
1152    //                             container = container.with_background_color(color);
1153    //                         }
1154    //                     }
1155
1156    //                     container
1157    //                 }))
1158    //                 .with_children((|| {
1159    //                     let participant_index = participant_index?;
1160    //                     let color = theme
1161    //                         .editor
1162    //                         .selection_style_for_room_participant(participant_index.0)
1163    //                         .cursor;
1164    //                     Some(
1165    //                         AvatarRibbon::new(color)
1166    //                             .constrained()
1167    //                             .with_width(theme.titlebar.avatar_ribbon.width)
1168    //                             .with_height(theme.titlebar.avatar_ribbon.height)
1169    //                             .aligned()
1170    //                             .bottom(),
1171    //                     )
1172    //                 })())
1173    //         },
1174    //     );
1175
1176    //     if Some(peer_id) == self_peer_id {
1177    //         return content.into_any();
1178    //     }
1179
1180    //     content
1181    //         .with_cursor_style(CursorStyle::PointingHand)
1182    //         .on_click(MouseButton::Left, move |_, this, cx| {
1183    //             let Some(workspace) = this.workspace.upgrade(cx) else {
1184    //                 return;
1185    //             };
1186    //             if let Some(task) =
1187    //                 workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
1188    //             {
1189    //                 task.detach_and_log_err(cx);
1190    //             }
1191    //         })
1192    //         .with_tooltip::<TitlebarParticipant>(
1193    //             peer_id.as_u64() as usize,
1194    //             format!("Follow {}", user.github_login),
1195    //             Some(Box::new(FollowNextCollaborator)),
1196    //             theme.tooltip.clone(),
1197    //             cx,
1198    //         )
1199    //         .into_any()
1200    // }
1201
1202    // fn location_style(
1203    //     workspace: &ViewHandle<Workspace>,
1204    //     location: Option<ParticipantLocation>,
1205    //     mut style: AvatarStyle,
1206    //     cx: &ViewContext<Self>,
1207    // ) -> AvatarStyle {
1208    //     if let Some(location) = location {
1209    //         if let ParticipantLocation::SharedProject { project_id } = location {
1210    //             if Some(project_id) != workspace.read(cx).project().read(cx).remote_id() {
1211    //                 style.image.grayscale = true;
1212    //             }
1213    //         } else {
1214    //             style.image.grayscale = true;
1215    //         }
1216    //     }
1217
1218    //     style
1219    // }
1220
1221    // fn render_face<V: 'static>(
1222    //     avatar: Arc<ImageData>,
1223    //     avatar_style: AvatarStyle,
1224    //     background_color: Color,
1225    //     microphone_state: Option<Color>,
1226    // ) -> AnyElement<V> {
1227    //     Image::from_data(avatar)
1228    //         .with_style(avatar_style.image)
1229    //         .aligned()
1230    //         .contained()
1231    //         .with_background_color(microphone_state.unwrap_or(background_color))
1232    //         .with_corner_radius(avatar_style.outer_corner_radius)
1233    //         .constrained()
1234    //         .with_width(avatar_style.outer_width)
1235    //         .with_height(avatar_style.outer_width)
1236    //         .aligned()
1237    //         .into_any()
1238    // }
1239
1240    // fn render_connection_status(
1241    //     &self,
1242    //     status: &client::Status,
1243    //     cx: &mut ViewContext<Self>,
1244    // ) -> Option<AnyElement<Self>> {
1245    //     enum ConnectionStatusButton {}
1246
1247    //     let theme = &theme::current(cx).clone();
1248    //     match status {
1249    //         client::Status::ConnectionError
1250    //         | client::Status::ConnectionLost
1251    //         | client::Status::Reauthenticating { .. }
1252    //         | client::Status::Reconnecting { .. }
1253    //         | client::Status::ReconnectionError { .. } => Some(
1254    //             Svg::new("icons/disconnected.svg")
1255    //                 .with_color(theme.titlebar.offline_icon.color)
1256    //                 .constrained()
1257    //                 .with_width(theme.titlebar.offline_icon.width)
1258    //                 .aligned()
1259    //                 .contained()
1260    //                 .with_style(theme.titlebar.offline_icon.container)
1261    //                 .into_any(),
1262    //         ),
1263    //         client::Status::UpgradeRequired => {
1264    //             let auto_updater = auto_update::AutoUpdater::get(cx);
1265    //             let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
1266    //                 Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
1267    //                 Some(AutoUpdateStatus::Installing)
1268    //                 | Some(AutoUpdateStatus::Downloading)
1269    //                 | Some(AutoUpdateStatus::Checking) => "Updating...",
1270    //                 Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
1271    //                     "Please update Zed to Collaborate"
1272    //                 }
1273    //             };
1274
1275    //             Some(
1276    //                 MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
1277    //                     Label::new(label, theme.titlebar.outdated_warning.text.clone())
1278    //                         .contained()
1279    //                         .with_style(theme.titlebar.outdated_warning.container)
1280    //                         .aligned()
1281    //                 })
1282    //                 .with_cursor_style(CursorStyle::PointingHand)
1283    //                 .on_click(MouseButton::Left, |_, _, cx| {
1284    //                     if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
1285    //                         if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
1286    //                             workspace::restart(&Default::default(), cx);
1287    //                             return;
1288    //                         }
1289    //                     }
1290    //                     auto_update::check(&Default::default(), cx);
1291    //                 })
1292    //                 .into_any(),
1293    //             )
1294    //         }
1295    //         _ => None,
1296    //     }
1297    // }
1298}
1299
1300// pub struct AvatarRibbon {
1301//     color: Color,
1302// }
1303
1304// impl AvatarRibbon {
1305//     pub fn new(color: Color) -> AvatarRibbon {
1306//         AvatarRibbon { color }
1307//     }
1308// }
1309
1310// impl Element<CollabTitlebarItem> for AvatarRibbon {
1311//     type LayoutState = ();
1312
1313//     type PaintState = ();
1314
1315//     fn layout(
1316//         &mut self,
1317//         constraint: gpui::SizeConstraint,
1318//         _: &mut CollabTitlebarItem,
1319//         _: &mut ViewContext<CollabTitlebarItem>,
1320//     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
1321//         (constraint.max, ())
1322//     }
1323
1324//     fn paint(
1325//         &mut self,
1326//         bounds: RectF,
1327//         _: RectF,
1328//         _: &mut Self::LayoutState,
1329//         _: &mut CollabTitlebarItem,
1330//         cx: &mut ViewContext<CollabTitlebarItem>,
1331//     ) -> Self::PaintState {
1332//         let mut path = PathBuilder::new();
1333//         path.reset(bounds.lower_left());
1334//         path.curve_to(
1335//             bounds.origin() + vec2f(bounds.height(), 0.),
1336//             bounds.origin(),
1337//         );
1338//         path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
1339//         path.curve_to(bounds.lower_right(), bounds.upper_right());
1340//         path.line_to(bounds.lower_left());
1341//         cx.scene().push_path(path.build(self.color, None));
1342//     }
1343
1344//     fn rect_for_text_range(
1345//         &self,
1346//         _: Range<usize>,
1347//         _: RectF,
1348//         _: RectF,
1349//         _: &Self::LayoutState,
1350//         _: &Self::PaintState,
1351//         _: &CollabTitlebarItem,
1352//         _: &ViewContext<CollabTitlebarItem>,
1353//     ) -> Option<RectF> {
1354//         None
1355//     }
1356
1357//     fn debug(
1358//         &self,
1359//         bounds: RectF,
1360//         _: &Self::LayoutState,
1361//         _: &Self::PaintState,
1362//         _: &CollabTitlebarItem,
1363//         _: &ViewContext<CollabTitlebarItem>,
1364//     ) -> gpui::json::Value {
1365//         json::json!({
1366//             "type": "AvatarRibbon",
1367//             "bounds": bounds.to_json(),
1368//             "color": self.color.to_json(),
1369//         })
1370//     }
1371// }