1use call::ActiveCall;
2use client::UserStore;
3use gpui::Action;
4use gpui::{
5 actions, elements::*, Entity, ModelHandle, MouseButton, RenderContext, View, ViewContext,
6};
7use settings::Settings;
8
9use crate::collab_titlebar_item::ToggleCollaboratorList;
10
11pub(crate) enum Event {
12 Dismissed,
13}
14
15enum Collaborator {
16 SelfUser { username: String },
17 RemoteUser { username: String },
18}
19
20actions!(collaborator_list_popover, [NoOp]);
21
22pub(crate) struct CollaboratorListPopover {
23 list_state: ListState,
24}
25
26impl Entity for CollaboratorListPopover {
27 type Event = Event;
28}
29
30impl View for CollaboratorListPopover {
31 fn ui_name() -> &'static str {
32 "CollaboratorListPopover"
33 }
34
35 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
36 let theme = cx.global::<Settings>().theme.clone();
37
38 MouseEventHandler::<Self>::new(0, cx, |_, _| {
39 List::new(self.list_state.clone())
40 .contained()
41 .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
42 .constrained()
43 .with_width(theme.contacts_popover.width)
44 .with_height(theme.contacts_popover.height)
45 .boxed()
46 })
47 .on_down_out(MouseButton::Left, move |_, cx| {
48 cx.dispatch_action(ToggleCollaboratorList);
49 })
50 .boxed()
51 }
52
53 fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
54 cx.emit(Event::Dismissed);
55 }
56}
57
58impl CollaboratorListPopover {
59 pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
60 let active_call = ActiveCall::global(cx);
61
62 let mut collaborators = user_store
63 .read(cx)
64 .current_user()
65 .map(|u| Collaborator::SelfUser {
66 username: u.github_login.clone(),
67 })
68 .into_iter()
69 .collect::<Vec<_>>();
70
71 //TODO: What should the canonical sort here look like, consult contacts list implementation
72 if let Some(room) = active_call.read(cx).room() {
73 for participant in room.read(cx).remote_participants() {
74 collaborators.push(Collaborator::RemoteUser {
75 username: participant.1.user.github_login.clone(),
76 });
77 }
78 }
79
80 Self {
81 list_state: ListState::new(
82 collaborators.len(),
83 Orientation::Top,
84 0.,
85 cx,
86 move |_, index, cx| match &collaborators[index] {
87 Collaborator::SelfUser { username } => render_collaborator_list_entry(
88 index,
89 username,
90 None::<NoOp>,
91 None,
92 Svg::new("icons/chevron_right_12.svg"),
93 NoOp,
94 "Leave call".to_owned(),
95 cx,
96 ),
97
98 Collaborator::RemoteUser { username } => render_collaborator_list_entry(
99 index,
100 username,
101 Some(NoOp),
102 Some(format!("Follow {username}")),
103 Svg::new("icons/x_mark_12.svg"),
104 NoOp,
105 format!("Remove {username} from call"),
106 cx,
107 ),
108 },
109 ),
110 }
111 }
112}
113
114fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
115 index: usize,
116 username: &str,
117 username_action: Option<UA>,
118 username_tooltip: Option<String>,
119 icon: Svg,
120 icon_action: IA,
121 icon_tooltip: String,
122 cx: &mut RenderContext<CollaboratorListPopover>,
123) -> ElementBox {
124 enum Username {}
125 enum UsernameTooltip {}
126 enum Icon {}
127 enum IconTooltip {}
128
129 let theme = &cx.global::<Settings>().theme;
130 let username_theme = theme.contact_list.contact_username.text.clone();
131 let tooltip_theme = theme.tooltip.clone();
132
133 let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
134 Label::new(username.to_owned(), username_theme.clone()).boxed()
135 })
136 .on_click(MouseButton::Left, move |_, cx| {
137 if let Some(username_action) = username_action.clone() {
138 cx.dispatch_action(username_action);
139 }
140 });
141
142 Flex::row()
143 .with_child(if let Some(username_tooltip) = username_tooltip {
144 username
145 .with_tooltip::<UsernameTooltip, _>(
146 index,
147 username_tooltip,
148 None,
149 tooltip_theme.clone(),
150 cx,
151 )
152 .boxed()
153 } else {
154 username.boxed()
155 })
156 .with_child(
157 MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
158 .on_click(MouseButton::Left, move |_, cx| {
159 cx.dispatch_action(icon_action.clone())
160 })
161 .with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
162 .boxed(),
163 )
164 .boxed()
165}