collab_titlebar_item.rs

  1use crate::face_pile::FacePile;
  2use call::{ActiveCall, ParticipantLocation, Room};
  3use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
  4use gpui::{
  5    actions, canvas, div, point, px, rems, AppContext, Div, Element, Hsla, InteractiveElement,
  6    IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled,
  7    Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
  8};
  9use project::{Project, RepositoryEntry};
 10use std::sync::Arc;
 11use theme::{ActiveTheme, PlayerColors};
 12use ui::{
 13    h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
 14    IconButton, IconElement, KeyBinding, Tooltip,
 15};
 16use util::ResultExt;
 17use workspace::{notifications::NotifyResultExt, Workspace};
 18
 19const MAX_PROJECT_NAME_LENGTH: usize = 40;
 20const MAX_BRANCH_NAME_LENGTH: usize = 40;
 21
 22actions!(
 23    ShareProject,
 24    UnshareProject,
 25    ToggleUserMenu,
 26    ToggleProjectMenu,
 27    SwitchBranch
 28);
 29
 30pub fn init(cx: &mut AppContext) {
 31    cx.observe_new_views(|workspace: &mut Workspace, cx| {
 32        let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
 33        workspace.set_titlebar_item(titlebar_item.into(), cx)
 34    })
 35    .detach();
 36    // cx.add_action(CollabTitlebarItem::share_project);
 37    // cx.add_action(CollabTitlebarItem::unshare_project);
 38    // cx.add_action(CollabTitlebarItem::toggle_user_menu);
 39    // cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
 40    // cx.add_action(CollabTitlebarItem::toggle_project_menu);
 41}
 42
 43pub struct CollabTitlebarItem {
 44    project: Model<Project>,
 45    user_store: Model<UserStore>,
 46    client: Arc<Client>,
 47    workspace: WeakView<Workspace>,
 48    //branch_popover: Option<ViewHandle<BranchList>>,
 49    //project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
 50    //user_menu: ViewHandle<ContextMenu>,
 51    _subscriptions: Vec<Subscription>,
 52}
 53
 54impl Render for CollabTitlebarItem {
 55    type Element = Stateful<Div>;
 56
 57    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
 58        let room = ActiveCall::global(cx).read(cx).room().cloned();
 59        let current_user = self.user_store.read(cx).current_user();
 60        let client = self.client.clone();
 61        let project_id = self.project.read(cx).remote_id();
 62
 63        h_stack()
 64            .id("titlebar")
 65            .justify_between()
 66            .w_full()
 67            .h(rems(1.75))
 68            // Set a non-scaling min-height here to ensure the titlebar is
 69            // always at least the height of the traffic lights.
 70            .min_h(px(32.))
 71            .when(
 72                !matches!(cx.window_bounds(), WindowBounds::Fullscreen),
 73                // Use pixels here instead of a rem-based size because the macOS traffic
 74                // lights are a static size, and don't scale with the rest of the UI.
 75                |s| s.pl(px(68.)),
 76            )
 77            .bg(cx.theme().colors().title_bar_background)
 78            .on_click(|event, cx| {
 79                if event.up.click_count == 2 {
 80                    cx.zoom_window();
 81                }
 82            })
 83            // left side
 84            .child(
 85                h_stack()
 86                    .gap_1()
 87                    .children(self.render_project_host(cx))
 88                    .child(self.render_project_name(cx))
 89                    .children(self.render_project_branch(cx))
 90                    .when_some(
 91                        current_user.clone().zip(client.peer_id()).zip(room.clone()),
 92                        |this, ((current_user, peer_id), room)| {
 93                            let player_colors = cx.theme().players();
 94                            let room = room.read(cx);
 95                            let mut remote_participants =
 96                                room.remote_participants().values().collect::<Vec<_>>();
 97                            remote_participants.sort_by_key(|p| p.participant_index.0);
 98
 99                            this.children(self.render_collaborator(
100                                &current_user,
101                                peer_id,
102                                true,
103                                room.is_speaking(),
104                                room.is_muted(cx),
105                                &room,
106                                project_id,
107                                &current_user,
108                            ))
109                            .children(
110                                remote_participants.iter().filter_map(|collaborator| {
111                                    let is_present = project_id.map_or(false, |project_id| {
112                                        collaborator.location
113                                            == ParticipantLocation::SharedProject { project_id }
114                                    });
115
116                                    let face_pile = self.render_collaborator(
117                                        &collaborator.user,
118                                        collaborator.peer_id,
119                                        is_present,
120                                        collaborator.speaking,
121                                        collaborator.muted,
122                                        &room,
123                                        project_id,
124                                        &current_user,
125                                    )?;
126
127                                    Some(
128                                        v_stack()
129                                            .id(("collaborator", collaborator.user.id))
130                                            .child(face_pile)
131                                            .child(render_color_ribbon(
132                                                collaborator.participant_index,
133                                                player_colors,
134                                            ))
135                                            .cursor_pointer()
136                                            .on_click({
137                                                let peer_id = collaborator.peer_id;
138                                                cx.listener(move |this, _, cx| {
139                                                    this.workspace
140                                                        .update(cx, |workspace, cx| {
141                                                            workspace.follow(peer_id, cx);
142                                                        })
143                                                        .ok();
144                                                })
145                                            })
146                                            .tooltip({
147                                                let login = collaborator.user.github_login.clone();
148                                                move |cx| {
149                                                    Tooltip::text(format!("Follow {login}"), cx)
150                                                }
151                                            }),
152                                    )
153                                }),
154                            )
155                        },
156                    ),
157            )
158            // right side
159            .child(
160                h_stack()
161                    .gap_1()
162                    .when_some(room, |this, room| {
163                        let room = room.read(cx);
164                        let is_shared = self.project.read(cx).is_shared();
165                        let is_muted = room.is_muted(cx);
166                        let is_deafened = room.is_deafened().unwrap_or(false);
167
168                        this.child(
169                            Button::new(
170                                "toggle_sharing",
171                                if is_shared { "Unshare" } else { "Share" },
172                            )
173                            .style(ButtonStyle::Subtle)
174                            .on_click(cx.listener(
175                                move |this, _, cx| {
176                                    if is_shared {
177                                        this.unshare_project(&Default::default(), cx);
178                                    } else {
179                                        this.share_project(&Default::default(), cx);
180                                    }
181                                },
182                            )),
183                        )
184                        .child(
185                            IconButton::new("leave-call", ui::Icon::Exit)
186                                .style(ButtonStyle::Subtle)
187                                .on_click(move |_, cx| {
188                                    ActiveCall::global(cx)
189                                        .update(cx, |call, cx| call.hang_up(cx))
190                                        .detach_and_log_err(cx);
191                                }),
192                        )
193                        .child(
194                            IconButton::new(
195                                "mute-microphone",
196                                if is_muted {
197                                    ui::Icon::MicMute
198                                } else {
199                                    ui::Icon::Mic
200                                },
201                            )
202                            .style(ButtonStyle::Subtle)
203                            .selected(is_muted)
204                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
205                        )
206                        .child(
207                            IconButton::new(
208                                "mute-sound",
209                                if is_deafened {
210                                    ui::Icon::AudioOff
211                                } else {
212                                    ui::Icon::AudioOn
213                                },
214                            )
215                            .style(ButtonStyle::Subtle)
216                            .selected(is_deafened.clone())
217                            .tooltip(move |cx| {
218                                Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
219                            })
220                            .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
221                        )
222                        .child(
223                            IconButton::new("screen-share", ui::Icon::Screen)
224                                .style(ButtonStyle::Subtle)
225                                .on_click(move |_, cx| {
226                                    crate::toggle_screen_sharing(&Default::default(), cx)
227                                }),
228                        )
229                    })
230                    .child(h_stack().px_1p5().map(|this| {
231                        if let Some(user) = current_user {
232                            this.when_some(user.avatar.clone(), |this, avatar| {
233                                // TODO: Finish implementing user menu popover
234                                //
235                                this.child(
236                                    popover_menu("user-menu")
237                                        .menu(|cx| {
238                                            ContextMenu::build(cx, |menu, _| menu.header("ADADA"))
239                                        })
240                                        .trigger(
241                                            ButtonLike::new("user-menu")
242                                                .child(
243                                                    h_stack()
244                                                        .gap_0p5()
245                                                        .child(Avatar::data(avatar))
246                                                        .child(
247                                                            IconElement::new(Icon::ChevronDown)
248                                                                .color(Color::Muted),
249                                                        ),
250                                                )
251                                                .style(ButtonStyle::Subtle)
252                                                .tooltip(move |cx| {
253                                                    Tooltip::text("Toggle User Menu", cx)
254                                                }),
255                                        )
256                                        .anchor(gpui::AnchorCorner::TopRight),
257                                )
258                                // this.child(
259                                //     ButtonLike::new("user-menu")
260                                //         .child(
261                                //             h_stack().gap_0p5().child(Avatar::data(avatar)).child(
262                                //                 IconElement::new(Icon::ChevronDown).color(Color::Muted),
263                                //             ),
264                                //         )
265                                //         .style(ButtonStyle::Subtle)
266                                //         .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
267                                // )
268                            })
269                        } else {
270                            this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| {
271                                let client = client.clone();
272                                cx.spawn(move |mut cx| async move {
273                                    client
274                                        .authenticate_and_connect(true, &cx)
275                                        .await
276                                        .notify_async_err(&mut cx);
277                                })
278                                .detach();
279                            }))
280                        }
281                    })),
282            )
283    }
284}
285
286fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
287    let color = colors.color_for_participant(participant_index.0).cursor;
288    canvas(move |bounds, cx| {
289        let mut path = Path::new(bounds.lower_left());
290        let height = bounds.size.height;
291        path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
292        path.line_to(bounds.upper_right() - point(height, px(0.)));
293        path.curve_to(bounds.lower_right(), bounds.upper_right());
294        path.line_to(bounds.lower_left());
295        cx.paint_path(path, color);
296    })
297    .h_1()
298    .w_full()
299}
300
301impl CollabTitlebarItem {
302    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
303        let project = workspace.project().clone();
304        let user_store = workspace.app_state().user_store.clone();
305        let client = workspace.app_state().client.clone();
306        let active_call = ActiveCall::global(cx);
307        let mut subscriptions = Vec::new();
308        subscriptions.push(
309            cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
310                cx.notify()
311            }),
312        );
313        subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
314        subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
315        subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
316        subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
317
318        Self {
319            workspace: workspace.weak_handle(),
320            project,
321            user_store,
322            client,
323            //         user_menu: cx.add_view(|cx| {
324            //             let view_id = cx.view_id();
325            //             let mut menu = ContextMenu::new(view_id, cx);
326            //             menu.set_position_mode(OverlayPositionMode::Local);
327            //             menu
328            //         }),
329            //         branch_popover: None,
330            //         project_popover: None,
331            _subscriptions: subscriptions,
332        }
333    }
334
335    // resolve if you are in a room -> render_project_owner
336    // render_project_owner -> resolve if you are in a room -> Option<foo>
337
338    pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
339        let host = self.project.read(cx).host()?;
340        let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
341        let participant_index = self
342            .user_store
343            .read(cx)
344            .participant_indices()
345            .get(&host.id)?;
346        Some(
347            div().border().border_color(gpui::red()).child(
348                Button::new("project_owner_trigger", host.github_login.clone())
349                    .color(Color::Player(participant_index.0))
350                    .style(ButtonStyle::Subtle)
351                    .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
352            ),
353        )
354    }
355
356    pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
357        let name = {
358            let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
359                let worktree = worktree.read(cx);
360                worktree.root_name()
361            });
362
363            names.next().unwrap_or("")
364        };
365
366        let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
367
368        div().border().border_color(gpui::red()).child(
369            Button::new("project_name_trigger", name)
370                .style(ButtonStyle::Subtle)
371                .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
372        )
373    }
374
375    pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
376        let entry = {
377            let mut names_and_branches =
378                self.project.read(cx).visible_worktrees(cx).map(|worktree| {
379                    let worktree = worktree.read(cx);
380                    worktree.root_git_entry()
381                });
382
383            names_and_branches.next().flatten()
384        };
385
386        let branch_name = entry
387            .as_ref()
388            .and_then(RepositoryEntry::branch)
389            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
390
391        Some(
392            div().border().border_color(gpui::red()).child(
393                Button::new("project_branch_trigger", branch_name)
394                    .style(ButtonStyle::Subtle)
395                    .tooltip(move |cx| {
396                        cx.build_view(|_| {
397                            Tooltip::new("Recent Branches")
398                                .key_binding(KeyBinding::new(gpui::KeyBinding::new(
399                                    "cmd-b",
400                                    // todo!() Replace with real action.
401                                    gpui::NoAction,
402                                    None,
403                                )))
404                                .meta("Local branches only")
405                        })
406                        .into()
407                    }),
408            ),
409        )
410    }
411
412    fn render_collaborator(
413        &self,
414        user: &Arc<User>,
415        peer_id: PeerId,
416        is_present: bool,
417        is_speaking: bool,
418        is_muted: bool,
419        room: &Room,
420        project_id: Option<u64>,
421        current_user: &Arc<User>,
422    ) -> Option<FacePile> {
423        let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
424        let mut pile = FacePile::default();
425        pile.extend(
426            user.avatar
427                .clone()
428                .map(|avatar| {
429                    div()
430                        .child(
431                            Avatar::data(avatar.clone())
432                                .grayscale(!is_present)
433                                .border_color(if is_speaking {
434                                    gpui::blue()
435                                } else if is_muted {
436                                    gpui::red()
437                                } else {
438                                    Hsla::default()
439                                }),
440                        )
441                        .into_any_element()
442                })
443                .into_iter()
444                .chain(followers.iter().filter_map(|follower_peer_id| {
445                    let follower = room
446                        .remote_participants()
447                        .values()
448                        .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
449                        .or_else(|| {
450                            (self.client.peer_id() == Some(*follower_peer_id))
451                                .then_some(current_user)
452                        })?
453                        .clone();
454                    follower
455                        .avatar
456                        .clone()
457                        .map(|avatar| div().child(Avatar::data(avatar.clone())).into_any_element())
458                })),
459        );
460        Some(pile)
461    }
462
463    fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
464        let project = if cx.is_window_active() {
465            Some(self.project.clone())
466        } else {
467            None
468        };
469        ActiveCall::global(cx)
470            .update(cx, |call, cx| call.set_location(project.as_ref(), cx))
471            .detach_and_log_err(cx);
472    }
473
474    fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
475        cx.notify();
476    }
477
478    fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
479        let active_call = ActiveCall::global(cx);
480        let project = self.project.clone();
481        active_call
482            .update(cx, |call, cx| call.share_project(project, cx))
483            .detach_and_log_err(cx);
484    }
485
486    fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
487        let active_call = ActiveCall::global(cx);
488        let project = self.project.clone();
489        active_call
490            .update(cx, |call, cx| call.unshare_project(project, cx))
491            .log_err();
492    }
493
494    // pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
495    //     self.user_menu.update(cx, |user_menu, cx| {
496    //         let items = if let Some(_) = self.user_store.read(cx).current_user() {
497    //             vec![
498    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
499    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
500    //                 ContextMenuItem::separator(),
501    //                 ContextMenuItem::action(
502    //                     "Share Feedback",
503    //                     feedback::feedback_editor::GiveFeedback,
504    //                 ),
505    //                 ContextMenuItem::action("Sign Out", SignOut),
506    //             ]
507    //         } else {
508    //             vec![
509    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
510    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
511    //                 ContextMenuItem::separator(),
512    //                 ContextMenuItem::action(
513    //                     "Share Feedback",
514    //                     feedback::feedback_editor::GiveFeedback,
515    //                 ),
516    //             ]
517    //         };
518    //         user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
519    //     });
520    // }
521
522    // fn render_branches_popover_host<'a>(
523    //     &'a self,
524    //     _theme: &'a theme::Titlebar,
525    //     cx: &'a mut ViewContext<Self>,
526    // ) -> Option<AnyElement<Self>> {
527    //     self.branch_popover.as_ref().map(|child| {
528    //         let theme = theme::current(cx).clone();
529    //         let child = ChildView::new(child, cx);
530    //         let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
531    //             child
532    //                 .flex(1., true)
533    //                 .contained()
534    //                 .constrained()
535    //                 .with_width(theme.titlebar.menu.width)
536    //                 .with_height(theme.titlebar.menu.height)
537    //         })
538    //         .on_click(MouseButton::Left, |_, _, _| {})
539    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
540    //             this.branch_popover.take();
541    //             cx.emit(());
542    //             cx.notify();
543    //         })
544    //         .contained()
545    //         .into_any();
546
547    //         Overlay::new(child)
548    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
549    //             .with_anchor_corner(AnchorCorner::TopLeft)
550    //             .with_z_index(999)
551    //             .aligned()
552    //             .bottom()
553    //             .left()
554    //             .into_any()
555    //     })
556    // }
557
558    // fn render_project_popover_host<'a>(
559    //     &'a self,
560    //     _theme: &'a theme::Titlebar,
561    //     cx: &'a mut ViewContext<Self>,
562    // ) -> Option<AnyElement<Self>> {
563    //     self.project_popover.as_ref().map(|child| {
564    //         let theme = theme::current(cx).clone();
565    //         let child = ChildView::new(child, cx);
566    //         let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
567    //             child
568    //                 .flex(1., true)
569    //                 .contained()
570    //                 .constrained()
571    //                 .with_width(theme.titlebar.menu.width)
572    //                 .with_height(theme.titlebar.menu.height)
573    //         })
574    //         .on_click(MouseButton::Left, |_, _, _| {})
575    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
576    //             this.project_popover.take();
577    //             cx.emit(());
578    //             cx.notify();
579    //         })
580    //         .into_any();
581
582    //         Overlay::new(child)
583    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
584    //             .with_anchor_corner(AnchorCorner::TopLeft)
585    //             .with_z_index(999)
586    //             .aligned()
587    //             .bottom()
588    //             .left()
589    //             .into_any()
590    //     })
591    // }
592
593    // pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
594    //     if self.branch_popover.take().is_none() {
595    //         if let Some(workspace) = self.workspace.upgrade(cx) {
596    //             let Some(view) =
597    //                 cx.add_option_view(|cx| build_branch_list(workspace, cx).log_err())
598    //             else {
599    //                 return;
600    //             };
601    //             cx.subscribe(&view, |this, _, event, cx| {
602    //                 match event {
603    //                     PickerEvent::Dismiss => {
604    //                         this.branch_popover = None;
605    //                     }
606    //                 }
607
608    //                 cx.notify();
609    //             })
610    //             .detach();
611    //             self.project_popover.take();
612    //             cx.focus(&view);
613    //             self.branch_popover = Some(view);
614    //         }
615    //     }
616
617    //     cx.notify();
618    // }
619
620    // pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
621    //     let workspace = self.workspace.clone();
622    //     if self.project_popover.take().is_none() {
623    //         cx.spawn(|this, mut cx| async move {
624    //             let workspaces = WORKSPACE_DB
625    //                 .recent_workspaces_on_disk()
626    //                 .await
627    //                 .unwrap_or_default()
628    //                 .into_iter()
629    //                 .map(|(_, location)| location)
630    //                 .collect();
631
632    //             let workspace = workspace.clone();
633    //             this.update(&mut cx, move |this, cx| {
634    //                 let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
635
636    //                 cx.subscribe(&view, |this, _, event, cx| {
637    //                     match event {
638    //                         PickerEvent::Dismiss => {
639    //                             this.project_popover = None;
640    //                         }
641    //                     }
642
643    //                     cx.notify();
644    //                 })
645    //                 .detach();
646    //                 cx.focus(&view);
647    //                 this.branch_popover.take();
648    //                 this.project_popover = Some(view);
649    //                 cx.notify();
650    //             })
651    //             .log_err();
652    //         })
653    //         .detach();
654    //     }
655    //     cx.notify();
656    // }
657
658    // fn render_user_menu_button(
659    //     &self,
660    //     theme: &Theme,
661    //     avatar: Option<Arc<ImageData>>,
662    //     cx: &mut ViewContext<Self>,
663    // ) -> AnyElement<Self> {
664    //     let tooltip = theme.tooltip.clone();
665    //     let user_menu_button_style = if avatar.is_some() {
666    //         &theme.titlebar.user_menu.user_menu_button_online
667    //     } else {
668    //         &theme.titlebar.user_menu.user_menu_button_offline
669    //     };
670
671    //     let avatar_style = &user_menu_button_style.avatar;
672    //     Stack::new()
673    //         .with_child(
674    //             MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
675    //                 let style = user_menu_button_style
676    //                     .user_menu
677    //                     .inactive_state()
678    //                     .style_for(state);
679
680    //                 let mut dropdown = Flex::row().align_children_center();
681
682    //                 if let Some(avatar_img) = avatar {
683    //                     dropdown = dropdown.with_child(Self::render_face(
684    //                         avatar_img,
685    //                         *avatar_style,
686    //                         Color::transparent_black(),
687    //                         None,
688    //                     ));
689    //                 };
690
691    //                 dropdown
692    //                     .with_child(
693    //                         Svg::new("icons/caret_down.svg")
694    //                             .with_color(user_menu_button_style.icon.color)
695    //                             .constrained()
696    //                             .with_width(user_menu_button_style.icon.width)
697    //                             .contained()
698    //                             .into_any(),
699    //                     )
700    //                     .aligned()
701    //                     .constrained()
702    //                     .with_height(style.width)
703    //                     .contained()
704    //                     .with_style(style.container)
705    //                     .into_any()
706    //             })
707    //             .with_cursor_style(CursorStyle::PointingHand)
708    //             .on_down(MouseButton::Left, move |_, this, cx| {
709    //                 this.user_menu.update(cx, |menu, _| menu.delay_cancel());
710    //             })
711    //             .on_click(MouseButton::Left, move |_, this, cx| {
712    //                 this.toggle_user_menu(&Default::default(), cx)
713    //             })
714    //             .with_tooltip::<ToggleUserMenu>(
715    //                 0,
716    //                 "Toggle User Menu".to_owned(),
717    //                 Some(Box::new(ToggleUserMenu)),
718    //                 tooltip,
719    //                 cx,
720    //             )
721    //             .contained(),
722    //         )
723    //         .with_child(
724    //             ChildView::new(&self.user_menu, cx)
725    //                 .aligned()
726    //                 .bottom()
727    //                 .right(),
728    //         )
729    //         .into_any()
730    // }
731
732    // fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
733    //     let titlebar = &theme.titlebar;
734    //     MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
735    //         let style = titlebar.sign_in_button.inactive_state().style_for(state);
736    //         Label::new("Sign In", style.text.clone())
737    //             .contained()
738    //             .with_style(style.container)
739    //     })
740    //     .with_cursor_style(CursorStyle::PointingHand)
741    //     .on_click(MouseButton::Left, move |_, this, cx| {
742    //         let client = this.client.clone();
743    //         cx.app_context()
744    //             .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
745    //             .detach_and_log_err(cx);
746    //     })
747    //     .into_any()
748    // }
749
750    // fn render_connection_status(
751    //     &self,
752    //     status: &client::Status,
753    //     cx: &mut ViewContext<Self>,
754    // ) -> Option<AnyElement<Self>> {
755    //     enum ConnectionStatusButton {}
756
757    //     let theme = &theme::current(cx).clone();
758    //     match status {
759    //         client::Status::ConnectionError
760    //         | client::Status::ConnectionLost
761    //         | client::Status::Reauthenticating { .. }
762    //         | client::Status::Reconnecting { .. }
763    //         | client::Status::ReconnectionError { .. } => Some(
764    //             Svg::new("icons/disconnected.svg")
765    //                 .with_color(theme.titlebar.offline_icon.color)
766    //                 .constrained()
767    //                 .with_width(theme.titlebar.offline_icon.width)
768    //                 .aligned()
769    //                 .contained()
770    //                 .with_style(theme.titlebar.offline_icon.container)
771    //                 .into_any(),
772    //         ),
773    //         client::Status::UpgradeRequired => {
774    //             let auto_updater = auto_update::AutoUpdater::get(cx);
775    //             let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
776    //                 Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
777    //                 Some(AutoUpdateStatus::Installing)
778    //                 | Some(AutoUpdateStatus::Downloading)
779    //                 | Some(AutoUpdateStatus::Checking) => "Updating...",
780    //                 Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
781    //                     "Please update Zed to Collaborate"
782    //                 }
783    //             };
784
785    //             Some(
786    //                 MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
787    //                     Label::new(label, theme.titlebar.outdated_warning.text.clone())
788    //                         .contained()
789    //                         .with_style(theme.titlebar.outdated_warning.container)
790    //                         .aligned()
791    //                 })
792    //                 .with_cursor_style(CursorStyle::PointingHand)
793    //                 .on_click(MouseButton::Left, |_, _, cx| {
794    //                     if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
795    //                         if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
796    //                             workspace::restart(&Default::default(), cx);
797    //                             return;
798    //                         }
799    //                     }
800    //                     auto_update::check(&Default::default(), cx);
801    //                 })
802    //                 .into_any(),
803    //             )
804    //         }
805    //         _ => None,
806    //     }
807    // }
808}