collaborator_list_popover.rs

  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}