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