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