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