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