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