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 ¤t_user,
101 peer_id,
102 true,
103 room.is_speaking(),
104 room.is_muted(cx),
105 &room,
106 project_id,
107 ¤t_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 ¤t_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}