collaborator_list_popover.rs

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