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