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