@@ -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<User>),
OutgoingRequest(Arc<User>),
Contact(Arc<Contact>),
@@ -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<Self>,
+ ) -> 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::<OpenSharedScreen>::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,
@@ -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)]
@@ -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<AppState>, 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<Self>) {
+ 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<Self>) -> 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::<SharedScreen>() {
- 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<Pane>,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<ViewHandle<SharedScreen>> {
+ 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::<SharedScreen>() {
+ 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<Self>) {
if !active {
for pane in &self.panes {
@@ -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" }),