From f99d70500caac62ade052c84af7144e171221722 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 24 Oct 2022 10:47:47 +0200 Subject: [PATCH] Allow opening shared screen via the contacts popover --- crates/collab_ui/src/contact_list.rs | 139 +++++++++++++++++++++++++-- crates/gpui/src/app.rs | 5 + crates/theme/src/theme.rs | 2 +- crates/workspace/src/workspace.rs | 53 +++++++--- styles/src/styleTree/contactList.ts | 5 + 5 files changed, 184 insertions(+), 20 deletions(-) diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 7a51cc83ec..d6d87393a6 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -17,7 +17,7 @@ use serde::Deserialize; use settings::Settings; use theme::IconButton; use util::ResultExt; -use workspace::JoinProject; +use workspace::{JoinProject, OpenSharedScreen}; impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]); impl_internal_actions!(contact_list, [ToggleExpanded, Call, LeaveCall]); @@ -67,6 +67,10 @@ enum ContactEntry { host_user_id: u64, is_last: bool, }, + ParticipantScreen { + peer_id: PeerId, + is_last: bool, + }, IncomingRequest(Arc), OutgoingRequest(Arc), Contact(Arc), @@ -97,6 +101,16 @@ impl PartialEq for ContactEntry { return project_id_1 == project_id_2; } } + ContactEntry::ParticipantScreen { + peer_id: peer_id_1, .. + } => { + if let ContactEntry::ParticipantScreen { + peer_id: peer_id_2, .. + } = other + { + return peer_id_1 == peer_id_2; + } + } ContactEntry::IncomingRequest(user_1) => { if let ContactEntry::IncomingRequest(user_2) = other { return user_1.id == user_2.id; @@ -216,6 +230,15 @@ impl ContactList { &theme.contact_list, cx, ), + ContactEntry::ParticipantScreen { peer_id, is_last } => { + Self::render_participant_screen( + *peer_id, + *is_last, + is_selected, + &theme.contact_list, + cx, + ) + } ContactEntry::IncomingRequest(user) => Self::render_contact_request( user.clone(), this.user_store.clone(), @@ -347,6 +370,9 @@ impl ContactList { follow_user_id: *host_user_id, }); } + ContactEntry::ParticipantScreen { peer_id, .. } => { + cx.dispatch_action(OpenSharedScreen { peer_id: *peer_id }); + } _ => {} } } @@ -430,11 +456,10 @@ impl ContactList { executor.clone(), )); for mat in matches { - let participant = &room.remote_participants()[&PeerId(mat.candidate_id as u32)]; + let peer_id = PeerId(mat.candidate_id as u32); + let participant = &room.remote_participants()[&peer_id]; participant_entries.push(ContactEntry::CallParticipant { - user: room.remote_participants()[&PeerId(mat.candidate_id as u32)] - .user - .clone(), + user: participant.user.clone(), is_pending: false, }); let mut projects = participant.projects.iter().peekable(); @@ -443,7 +468,13 @@ impl ContactList { project_id: project.id, worktree_root_names: project.worktree_root_names.clone(), host_user_id: participant.user.id, - is_last: projects.peek().is_none(), + is_last: projects.peek().is_none() && participant.tracks.is_empty(), + }); + } + if !participant.tracks.is_empty() { + participant_entries.push(ContactEntry::ParticipantScreen { + peer_id, + is_last: true, }); } } @@ -763,6 +794,102 @@ impl ContactList { .boxed() } + fn render_participant_screen( + peer_id: PeerId, + is_last: bool, + is_selected: bool, + theme: &theme::ContactList, + cx: &mut RenderContext, + ) -> ElementBox { + let font_cache = cx.font_cache(); + let host_avatar_height = theme + .contact_avatar + .width + .or(theme.contact_avatar.height) + .unwrap_or(0.); + let row = &theme.project_row.default; + let tree_branch = theme.tree_branch; + let line_height = row.name.text.line_height(font_cache); + let cap_height = row.name.text.cap_height(font_cache); + let baseline_offset = + row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.; + + MouseEventHandler::::new(peer_id.0 as usize, cx, |mouse_state, _| { + let tree_branch = *tree_branch.style_for(mouse_state, is_selected); + let row = theme.project_row.style_for(mouse_state, is_selected); + + Flex::row() + .with_child( + Stack::new() + .with_child( + Canvas::new(move |bounds, _, cx| { + let start_x = bounds.min_x() + (bounds.width() / 2.) + - (tree_branch.width / 2.); + let end_x = bounds.max_x(); + let start_y = bounds.min_y(); + let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.); + + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, start_y), + vec2f( + start_x + tree_branch.width, + if is_last { end_y } else { bounds.max_y() }, + ), + ), + background: Some(tree_branch.color), + border: gpui::Border::default(), + corner_radius: 0., + }); + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, end_y), + vec2f(end_x, end_y + tree_branch.width), + ), + background: Some(tree_branch.color), + border: gpui::Border::default(), + corner_radius: 0., + }); + }) + .boxed(), + ) + .constrained() + .with_width(host_avatar_height) + .boxed(), + ) + .with_child( + Svg::new("icons/disable_screen_sharing_12.svg") + .with_color(row.icon.color) + .constrained() + .with_width(row.icon.width) + .aligned() + .left() + .contained() + .with_style(row.icon.container) + .boxed(), + ) + .with_child( + Label::new("Screen Sharing".into(), row.name.text.clone()) + .aligned() + .left() + .contained() + .with_style(row.name.container) + .flex(1., false) + .boxed(), + ) + .constrained() + .with_height(theme.row_height) + .contained() + .with_style(row.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(OpenSharedScreen { peer_id }); + }) + .boxed() + } + fn render_header( section: Section, theme: &theme::ContactList, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a9020cf350..5cbc786b72 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3835,6 +3835,11 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.notify_view(self.window_id, self.view_id); } + pub fn dispatch_action(&mut self, action: impl Action) { + self.app + .dispatch_action_at(self.window_id, self.view_id, action) + } + pub fn dispatch_any_action(&mut self, action: Box) { self.app .dispatch_any_action_at(self.window_id, self.view_id, action) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3555f77126..db6609fa82 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -120,6 +120,7 @@ pub struct ContactList { pub struct ProjectRow { #[serde(flatten)] pub container: ContainerStyle, + pub icon: Icon, pub name: ContainedText, } @@ -381,7 +382,6 @@ pub struct Icon { pub container: ContainerStyle, pub color: Color, pub width: f32, - pub path: String, } #[derive(Deserialize, Clone, Copy, Default)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3faec363ab..a318852359 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -121,12 +121,18 @@ pub struct JoinProject { pub follow_user_id: u64, } +#[derive(Clone, PartialEq)] +pub struct OpenSharedScreen { + pub peer_id: PeerId, +} + impl_internal_actions!( workspace, [ OpenPaths, ToggleFollow, JoinProject, + OpenSharedScreen, RemoveWorktreeFromProject ] ); @@ -166,6 +172,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { cx.add_async_action(Workspace::follow_next_collaborator); cx.add_async_action(Workspace::close); cx.add_async_action(Workspace::save_all); + cx.add_action(Workspace::open_shared_screen); cx.add_action(Workspace::add_folder_to_project); cx.add_action(Workspace::remove_folder_from_project); cx.add_action( @@ -1788,6 +1795,15 @@ impl Workspace { item } + pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext) { + if let Some(shared_screen) = + self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx) + { + let pane = self.active_pane.clone(); + Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx); + } + } + pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { let result = self.panes.iter().find_map(|pane| { pane.read(cx) @@ -2534,20 +2550,10 @@ impl Workspace { } call::ParticipantLocation::UnsharedProject => {} call::ParticipantLocation::External => { - let track = participant.tracks.values().next()?.clone(); - let user = participant.user.clone(); - - 'outer: for (pane, _) in self.follower_states_by_leader.get(&leader_id)? { - for item in pane.read(cx).items_of_type::() { - if item.read(cx).peer_id == leader_id { - items_to_add.push((pane.clone(), Box::new(item))); - continue 'outer; - } + for (pane, _) in self.follower_states_by_leader.get(&leader_id)? { + if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + items_to_add.push((pane.clone(), Box::new(shared_screen))); } - - let shared_screen = - cx.add_view(|cx| SharedScreen::new(&track, leader_id, user.clone(), cx)); - items_to_add.push((pane.clone(), Box::new(shared_screen))); } } } @@ -2562,6 +2568,27 @@ impl Workspace { None } + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &ViewHandle, + cx: &mut ViewContext, + ) -> Option> { + let call = self.active_call()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participants().get(&peer_id)?; + let track = participant.tracks.values().next()?.clone(); + let user = participant.user.clone(); + + for item in pane.read(cx).items_of_type::() { + if item.read(cx).peer_id == peer_id { + return Some(item); + } + } + + Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + } + pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { if !active { for pane in &self.panes { diff --git a/styles/src/styleTree/contactList.ts b/styles/src/styleTree/contactList.ts index a58bf90fd1..5aede5d862 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/styleTree/contactList.ts @@ -166,6 +166,11 @@ export default function contactsPanel(colorScheme: ColorScheme) { projectRow: { ...projectRow, background: background(layer, "on"), + icon: { + margin: { left: nameMargin }, + color: foreground(layer, "variant"), + width: 12, + }, name: { ...projectRow.name, ...text(layer, "mono", { size: "sm" }),