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