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>) -> Element<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                .boxed()
 44        })
 45        .on_down_out(MouseButton::Left, move |_, _, cx| {
 46            cx.dispatch_action(ToggleCollaboratorList);
 47        })
 48        .boxed()
 49    }
 50
 51    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
 52        cx.emit(Event::Dismissed);
 53    }
 54}
 55
 56impl CollaboratorListPopover {
 57    pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
 58        let active_call = ActiveCall::global(cx);
 59
 60        let mut collaborators = user_store
 61            .read(cx)
 62            .current_user()
 63            .map(|u| Collaborator::SelfUser {
 64                username: u.github_login.clone(),
 65            })
 66            .into_iter()
 67            .collect::<Vec<_>>();
 68
 69        //TODO: What should the canonical sort here look like, consult contacts list implementation
 70        if let Some(room) = active_call.read(cx).room() {
 71            for participant in room.read(cx).remote_participants() {
 72                collaborators.push(Collaborator::RemoteUser {
 73                    username: participant.1.user.github_login.clone(),
 74                });
 75            }
 76        }
 77
 78        Self {
 79            list_state: ListState::new(
 80                collaborators.len(),
 81                Orientation::Top,
 82                0.,
 83                move |_, index, cx| match &collaborators[index] {
 84                    Collaborator::SelfUser { username } => render_collaborator_list_entry(
 85                        index,
 86                        username,
 87                        None::<NoOp>,
 88                        None,
 89                        Svg::new("icons/chevron_right_12.svg"),
 90                        NoOp,
 91                        "Leave call".to_owned(),
 92                        cx,
 93                    ),
 94
 95                    Collaborator::RemoteUser { username } => render_collaborator_list_entry(
 96                        index,
 97                        username,
 98                        Some(NoOp),
 99                        Some(format!("Follow {username}")),
100                        Svg::new("icons/x_mark_12.svg"),
101                        NoOp,
102                        format!("Remove {username} from call"),
103                        cx,
104                    ),
105                },
106            ),
107        }
108    }
109}
110
111fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
112    index: usize,
113    username: &str,
114    username_action: Option<UA>,
115    username_tooltip: Option<String>,
116    icon: Svg,
117    icon_action: IA,
118    icon_tooltip: String,
119    cx: &mut ViewContext<CollaboratorListPopover>,
120) -> Element<CollaboratorListPopover> {
121    enum Username {}
122    enum UsernameTooltip {}
123    enum Icon {}
124    enum IconTooltip {}
125
126    let theme = &cx.global::<Settings>().theme;
127    let username_theme = theme.contact_list.contact_username.text.clone();
128    let tooltip_theme = theme.tooltip.clone();
129
130    let username =
131        MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
132            Label::new(username.to_owned(), username_theme.clone()).boxed()
133        })
134        .on_click(MouseButton::Left, move |_, _, cx| {
135            if let Some(username_action) = username_action.clone() {
136                cx.dispatch_action(username_action);
137            }
138        });
139
140    Flex::row()
141        .with_child(if let Some(username_tooltip) = username_tooltip {
142            username
143                .with_tooltip::<UsernameTooltip>(
144                    index,
145                    username_tooltip,
146                    None,
147                    tooltip_theme.clone(),
148                    cx,
149                )
150                .boxed()
151        } else {
152            username.boxed()
153        })
154        .with_child(
155            MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
156                .on_click(MouseButton::Left, move |_, _, cx| {
157                    cx.dispatch_action(icon_action.clone())
158                })
159                .with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx)
160                .boxed(),
161        )
162        .boxed()
163}