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