collaborator_list_popover.rs

  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}