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