diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index cc82acac5e7118018b6636c8d81fa1adb38b997d..c46861b5ab659224b3462b71c7c6ddaab725719a 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,6 +1,6 @@ use crate::{contact_notification::ContactNotification, contacts_popover}; use call::{ActiveCall, ParticipantLocation}; -use client::{Authenticate, ContactEventKind, PeerId, UserStore}; +use client::{Authenticate, ContactEventKind, PeerId, User, UserStore}; use clock::ReplicaId; use contacts_popover::ContactsPopover; use gpui::{ @@ -9,13 +9,13 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::vec2f, PathBuilder}, json::{self, ToJson}, - Border, CursorStyle, Entity, ImageData, ModelHandle, MouseButton, MutableAppContext, - RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, + Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; -use std::{ops::Range, sync::Arc}; +use std::ops::Range; use theme::Theme; -use workspace::{FollowNextCollaborator, ToggleFollow, Workspace}; +use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace}; actions!( contacts_titlebar_item, @@ -282,29 +282,27 @@ impl CollabTitlebarItem { let active_call = ActiveCall::global(cx); if let Some(room) = active_call.read(cx).room().cloned() { let project = workspace.read(cx).project().read(cx); - let project_id = project.remote_id(); - let mut collaborators = project - .collaborators() - .values() - .cloned() + let mut participants = room + .read(cx) + .remote_participants() + .iter() + .map(|(peer_id, collaborator)| (*peer_id, collaborator.clone())) .collect::>(); - collaborators.sort_by_key(|collaborator| collaborator.replica_id); - collaborators + participants + .sort_by_key(|(peer_id, _)| Some(project.collaborators().get(peer_id)?.replica_id)); + participants .into_iter() - .filter_map(|collaborator| { - let participant = room - .read(cx) - .remote_participants() - .get(&collaborator.peer_id)?; + .filter_map(|(peer_id, participant)| { + let project = workspace.read(cx).project().read(cx); + let replica_id = project + .collaborators() + .get(&peer_id) + .map(|collaborator| collaborator.replica_id); let user = participant.user.clone(); - let is_active = project_id.map_or(false, |project_id| { - participant.location == ParticipantLocation::Project { project_id } - }); Some(self.render_avatar( - user.avatar.clone()?, - collaborator.replica_id, - Some((collaborator.peer_id, &user.github_login)), - is_active, + &user, + replica_id, + Some((peer_id, &user.github_login, participant.location)), workspace, theme, cx, @@ -325,8 +323,8 @@ impl CollabTitlebarItem { let user = workspace.read(cx).user_store().read(cx).current_user(); let replica_id = workspace.read(cx).project().read(cx).replica_id(); let status = *workspace.read(cx).client().status().borrow(); - if let Some(avatar) = user.and_then(|user| user.avatar.clone()) { - Some(self.render_avatar(avatar, replica_id, None, true, workspace, theme, cx)) + if let Some(user) = user { + Some(self.render_avatar(&user, Some(replica_id), None, workspace, theme, cx)) } else if matches!(status, client::Status::UpgradeRequired) { None } else { @@ -352,72 +350,105 @@ impl CollabTitlebarItem { fn render_avatar( &self, - avatar: Arc, - replica_id: ReplicaId, - peer: Option<(PeerId, &str)>, - is_active: bool, + user: &User, + replica_id: Option, + peer: Option<(PeerId, &str, ParticipantLocation)>, workspace: &ViewHandle, theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { - let replica_color = theme.editor.replica_selection_style(replica_id).cursor; - let is_followed = peer.map_or(false, |(peer_id, _)| { + let is_followed = peer.map_or(false, |(peer_id, _, _)| { workspace.read(cx).is_following(peer_id) }); - let mut avatar_style; - if is_active { - avatar_style = theme.workspace.titlebar.avatar; + let mut avatar_style; + if let Some((_, _, location)) = peer.as_ref() { + if let ParticipantLocation::Project { project_id } = *location { + if Some(project_id) == workspace.read(cx).project().read(cx).remote_id() { + avatar_style = theme.workspace.titlebar.avatar; + } else { + avatar_style = theme.workspace.titlebar.inactive_avatar; + } + } else { + avatar_style = theme.workspace.titlebar.inactive_avatar; + } } else { - avatar_style = theme.workspace.titlebar.inactive_avatar; + avatar_style = theme.workspace.titlebar.avatar; } - if is_followed { - avatar_style.border = Border::all(1.0, replica_color); + let mut replica_color = None; + if let Some(replica_id) = replica_id { + let color = theme.editor.replica_selection_style(replica_id).cursor; + replica_color = Some(color); + if is_followed { + avatar_style.border = Border::all(1.0, color); + } } let content = Stack::new() - .with_child( - Image::new(avatar) + .with_children(user.avatar.as_ref().map(|avatar| { + Image::new(avatar.clone()) .with_style(avatar_style) .constrained() .with_width(theme.workspace.titlebar.avatar_width) .aligned() - .boxed(), - ) - .with_child( + .boxed() + })) + .with_children(replica_color.map(|replica_color| { AvatarRibbon::new(replica_color) .constrained() .with_width(theme.workspace.titlebar.avatar_ribbon.width) .with_height(theme.workspace.titlebar.avatar_ribbon.height) .aligned() .bottom() - .boxed(), - ) + .boxed() + })) .constrained() .with_width(theme.workspace.titlebar.avatar_width) .contained() .with_margin_left(theme.workspace.titlebar.avatar_margin) .boxed(); - if let Some((peer_id, peer_github_login)) = peer { - MouseEventHandler::::new(replica_id.into(), cx, move |_, _| content) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_action(ToggleFollow(peer_id)) - }) - .with_tooltip::( - peer_id.0 as usize, - if is_followed { - format!("Unfollow {}", peer_github_login) - } else { - format!("Follow {}", peer_github_login) - }, - Some(Box::new(FollowNextCollaborator)), - theme.tooltip.clone(), - cx, - ) - .boxed() + if let Some((peer_id, peer_github_login, location)) = peer { + if let Some(replica_id) = replica_id { + MouseEventHandler::::new(replica_id.into(), cx, move |_, _| content) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleFollow(peer_id)) + }) + .with_tooltip::( + peer_id.0 as usize, + if is_followed { + format!("Unfollow {}", peer_github_login) + } else { + format!("Follow {}", peer_github_login) + }, + Some(Box::new(FollowNextCollaborator)), + theme.tooltip.clone(), + cx, + ) + .boxed() + } else if let ParticipantLocation::Project { project_id } = location { + let user_id = user.id; + MouseEventHandler::::new(peer_id.0 as usize, cx, move |_, _| content) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(JoinProject { + project_id, + follow_user_id: user_id, + }) + }) + .with_tooltip::( + peer_id.0 as usize, + format!("Follow {} into external project", peer_github_login), + Some(Box::new(FollowNextCollaborator)), + theme.tooltip.clone(), + cx, + ) + .boxed() + } else { + content + } } else { content } diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index e0387e39bb1cadd340635ff0c0259d6486e49bad..7e231e5e293f8660b5dc60b9e8a6ebcf32264429 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -27,6 +27,8 @@ pub struct ImageStyle { pub height: Option, #[serde(default)] pub width: Option, + #[serde(default)] + pub grayscale: bool, } impl Image { @@ -74,6 +76,7 @@ impl Element for Image { bounds, border: self.style.border, corner_radius: self.style.corner_radius, + grayscale: self.style.grayscale, data: self.data.clone(), }); } diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index ea094e998c112b4b875f129b2aef0c5d67fd8002..6a70ff41f0a975cefa0fa2b3f9e28cda3dad61f3 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -747,6 +747,7 @@ impl Renderer { border_left: border_width * (image.border.left as usize as f32), border_color: image.border.color.to_uchar4(), corner_radius, + grayscale: image.grayscale as u8, }); } @@ -769,6 +770,7 @@ impl Renderer { border_left: 0., border_color: Default::default(), corner_radius: 0., + grayscale: false as u8, }); } else { log::warn!("could not render glyph with id {}", image_glyph.id); diff --git a/crates/gpui/src/platform/mac/shaders/shaders.h b/crates/gpui/src/platform/mac/shaders/shaders.h index 29be2c9e1e789ade67c4c05b53e8300779cdc884..6e0ed1a5f1b5ef89182e5fb46518b0801d5a9cb0 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.h +++ b/crates/gpui/src/platform/mac/shaders/shaders.h @@ -90,6 +90,7 @@ typedef struct { float border_left; vector_uchar4 border_color; float corner_radius; + uint8_t grayscale; } GPUIImage; typedef enum { diff --git a/crates/gpui/src/platform/mac/shaders/shaders.metal b/crates/gpui/src/platform/mac/shaders/shaders.metal index 795026e747e4ffa1f0083737c7d6b2158f79a1b9..397e7647c47107b5176880a0e0fd1ba2adbaecda 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders/shaders.metal @@ -44,6 +44,7 @@ struct QuadFragmentInput { float border_left; float4 border_color; float corner_radius; + uchar grayscale; // only used in image shader }; float4 quad_sdf(QuadFragmentInput input) { @@ -110,6 +111,7 @@ vertex QuadFragmentInput quad_vertex( quad.border_left, coloru_to_colorf(quad.border_color), quad.corner_radius, + 0, }; } @@ -251,6 +253,7 @@ vertex QuadFragmentInput image_vertex( image.border_left, coloru_to_colorf(image.border_color), image.corner_radius, + image.grayscale, }; } @@ -260,6 +263,13 @@ fragment float4 image_fragment( ) { constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear); input.background_color = atlas.sample(atlas_sampler, input.atlas_position); + if (input.grayscale) { + float grayscale = + 0.2126 * input.background_color.r + + 0.7152 * input.background_color.g + + 0.0722 * input.background_color.b; + input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a); + } return quad_sdf(input); } @@ -289,6 +299,7 @@ vertex QuadFragmentInput surface_vertex( 0., float4(0.), 0., + 0, }; } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index e676147fa90e72d0f9f36cd02e2356423f310be5..4ef17a3f8f5d384723ed1d94a13019bb495e4d0e 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -172,6 +172,7 @@ pub struct Image { pub bounds: RectF, pub border: Border, pub corner_radius: f32, + pub grayscale: bool, pub data: Arc, } diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 4deee046b4298ae3b98bda8b0d87d7c4ab766ef8..cfbda49056da6635b6966bca5722b3a39d4a4e89 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -36,6 +36,7 @@ export default function workspace(theme: Theme) { border: border(theme, "primary"), }, }; + const avatarWidth = 18; return { background: backgroundColor(theme, 300), @@ -80,7 +81,7 @@ export default function workspace(theme: Theme) { }, statusBar: statusBar(theme), titlebar: { - avatarWidth: 18, + avatarWidth, avatarMargin: 8, height: 33, background: backgroundColor(theme, 100), @@ -90,15 +91,19 @@ export default function workspace(theme: Theme) { }, title: text(theme, "sans", "primary"), avatar: { - cornerRadius: 10, + cornerRadius: avatarWidth / 2, border: { color: "#00000088", width: 1, }, }, inactiveAvatar: { - cornerRadius: 10, - opacity: 0.65, + cornerRadius: avatarWidth / 2, + border: { + color: "#00000088", + width: 1, + }, + grayscale: true, }, avatarRibbon: { height: 3,