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