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