Add mockup accurate avatar background

Julia and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

crates/collab_ui/src/collab_titlebar_item.rs | 57 ++++++++++++++++-----
crates/collab_ui/src/face_pile.rs            |  4 +
crates/theme/src/theme.rs                    |  3 
styles/src/styleTree/workspace.ts            | 22 +++-----
4 files changed, 56 insertions(+), 30 deletions(-)

Detailed changes

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -610,6 +610,16 @@ impl CollabTitlebarItem {
     ) -> ElementBox {
         let room = ActiveCall::global(cx).read(cx).room();
         let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
+        let followed_by_self = room
+            .map(|room| {
+                is_being_followed
+                    && room
+                        .read(cx)
+                        .followers_for(peer_id)
+                        .iter()
+                        .any(|&follower| Some(follower) == workspace.read(cx).client().peer_id())
+            })
+            .unwrap_or(false);
 
         let avatar_style;
         if let Some(location) = location {
@@ -626,10 +636,28 @@ impl CollabTitlebarItem {
             avatar_style = &theme.workspace.titlebar.avatar;
         }
 
+        let mut background_color = theme
+            .workspace
+            .titlebar
+            .container
+            .background_color
+            .unwrap_or_default();
+        if let Some(replica_id) = replica_id {
+            if followed_by_self {
+                let selection = dbg!(theme.editor.replica_selection_style(replica_id).selection);
+                background_color = dbg!(Color::blend(selection, background_color));
+                background_color.a = 255;
+            }
+        }
+
         let content = Stack::new()
             .with_children(user.avatar.as_ref().map(|avatar| {
                 let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap)
-                    .with_child(Self::render_face(avatar.clone(), avatar_style.clone()))
+                    .with_child(Self::render_face(
+                        avatar.clone(),
+                        avatar_style.clone(),
+                        background_color,
+                    ))
                     .with_children(
                         (|| {
                             let room = room?.read(cx);
@@ -656,6 +684,7 @@ impl CollabTitlebarItem {
                                 Some(Self::render_face(
                                     avatar.clone(),
                                     theme.workspace.titlebar.follower_avatar.clone(),
+                                    background_color,
                                 ))
                             }))
                         })()
@@ -663,15 +692,7 @@ impl CollabTitlebarItem {
                         .flatten(),
                     );
 
-                if let (Some(replica_id), Some(room)) = (replica_id, room) {
-                    let followed_by_self = is_being_followed
-                        && room
-                            .read(cx)
-                            .followers_for(peer_id)
-                            .iter()
-                            .any(|&follower| {
-                                Some(follower) == workspace.read(cx).client().peer_id()
-                            });
+                if let Some(replica_id) = replica_id {
                     if followed_by_self {
                         let color = theme.editor.replica_selection_style(replica_id).selection;
                         return face_pile.contained().with_background_color(color).boxed();
@@ -744,14 +765,20 @@ impl CollabTitlebarItem {
         }
     }
 
-    fn render_face(avatar: Arc<ImageData>, avatar_style: AvatarStyle) -> ElementBox {
+    fn render_face(
+        avatar: Arc<ImageData>,
+        avatar_style: AvatarStyle,
+        background_color: Color,
+    ) -> ElementBox {
         Image::new(avatar)
             .with_style(avatar_style.image)
-            .constrained()
-            .with_width(avatar_style.width)
+            .aligned()
             .contained()
-            .with_background_color(Color::white())
-            .with_corner_radius(avatar_style.image.corner_radius)
+            .with_background_color(background_color)
+            .with_corner_radius(avatar_style.outer_corner_radius)
+            .constrained()
+            .with_width(avatar_style.outer_width)
+            .with_height(avatar_style.outer_width)
             .aligned()
             .boxed()
     }

crates/collab_ui/src/face_pile.rs 🔗

@@ -59,7 +59,9 @@ impl Element for FacePile {
         for face in self.faces.iter_mut().rev() {
             let size = face.size();
             origin_x -= size.x();
-            face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
+            cx.paint_layer(None, |cx| {
+                face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
+            });
             origin_x += self.overlap;
         }
 

crates/theme/src/theme.rs 🔗

@@ -93,7 +93,8 @@ pub struct Titlebar {
 pub struct AvatarStyle {
     #[serde(flatten)]
     pub image: ImageStyle,
-    pub width: f32,
+    pub outer_width: f32,
+    pub outer_corner_radius: f32,
 }
 
 #[derive(Deserialize, Default)]

styles/src/styleTree/workspace.ts 🔗

@@ -41,7 +41,9 @@ export default function workspace(colorScheme: ColorScheme) {
     },
   };
   const avatarWidth = 18;
+  const avatarOuterWidth = avatarWidth + 4;
   const followerAvatarWidth = 14;
+  const followerAvatarOuterWidth = followerAvatarWidth + 4;
 
   return {
     background: background(layer),
@@ -96,30 +98,24 @@ export default function workspace(colorScheme: ColorScheme) {
       // Collaborators
       avatar: {
         width: avatarWidth,
+        outerWidth: avatarOuterWidth,
         cornerRadius: avatarWidth / 2,
-        border: {
-          color: "#00000088",
-          width: 1,
-        },
+        outerCornerRadius: avatarOuterWidth / 2,
       },
       inactiveAvatar: {
         width: avatarWidth,
+        outerWidth: avatarOuterWidth,
         cornerRadius: avatarWidth / 2,
-        border: {
-          color: "#00000088",
-          width: 1,
-        },
+        outerCornerRadius: avatarOuterWidth / 2,
         grayscale: true,
       },
       followerAvatar: {
         width: followerAvatarWidth,
+        outerWidth: followerAvatarOuterWidth,
         cornerRadius: followerAvatarWidth / 2,
-        border: {
-          color: "#00000088",
-          width: 1,
-        },
+        outerCornerRadius: followerAvatarOuterWidth / 2,
       },
-      followerAvatarOverlap: 6,
+      followerAvatarOverlap: 8,
       avatarRibbon: {
         height: 3,
         width: 12,