diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 48a1a0f263..a2414069c7 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,6 +1,7 @@ use crate::{ collaborator_list_popover, collaborator_list_popover::CollaboratorListPopover, - contact_notification::ContactNotification, contacts_popover, ToggleScreenSharing, + contact_notification::ContactNotification, contacts_popover, face_pile::FacePile, + ToggleScreenSharing, }; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore}; @@ -627,7 +628,7 @@ impl CollabTitlebarItem { let content = Stack::new() .with_children(user.avatar.as_ref().map(|avatar| { - let flex = Flex::row() + let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap) .with_child(Self::render_face(avatar.clone(), avatar_style.clone())) .with_children( (|| { @@ -652,16 +653,10 @@ impl CollabTitlebarItem { } })?; - Some( - Container::new(Self::render_face( - avatar.clone(), - theme.workspace.titlebar.follower_avatar.clone(), - )) - .with_margin_left( - -1.0 * theme.workspace.titlebar.follower_avatar_overlap, - ) - .boxed(), - ) + Some(Self::render_face( + avatar.clone(), + theme.workspace.titlebar.follower_avatar.clone(), + )) })) })() .into_iter() @@ -679,11 +674,11 @@ impl CollabTitlebarItem { }); if followed_by_self { let color = theme.editor.replica_selection_style(replica_id).selection; - return flex.contained().with_background_color(color).boxed(); + return face_pile.contained().with_background_color(color).boxed(); } } - flex.boxed() + face_pile.boxed() })) .with_children((|| { let replica_id = replica_id?; diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index d250ce5576..6abfec21f7 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -4,6 +4,7 @@ mod contact_finder; mod contact_list; mod contact_notification; mod contacts_popover; +mod face_pile; mod incoming_call_notification; mod notifications; mod project_shared_notification; diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs new file mode 100644 index 0000000000..a24cc46646 --- /dev/null +++ b/crates/collab_ui/src/face_pile.rs @@ -0,0 +1,99 @@ +use std::ops::Range; + +use gpui::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::ToJson, + serde_json::{self, json}, + Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext, +}; + +pub(crate) struct FacePile { + overlap: f32, + faces: Vec, +} + +impl FacePile { + pub fn new(overlap: f32) -> FacePile { + FacePile { + overlap, + faces: Vec::new(), + } + } +} + +impl Element for FacePile { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); + + let mut width = 0.; + for face in &mut self.faces { + width += face.layout(constraint, cx).x(); + } + width -= self.overlap * self.faces.len().saturating_sub(1) as f32; + + (Vector2F::new(width, constraint.max.y()), ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _layout: &mut Self::LayoutState, + cx: &mut PaintContext, + ) -> Self::PaintState { + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let origin_y = bounds.upper_right().y(); + let mut origin_x = bounds.upper_right().x(); + + 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); + origin_x += self.overlap; + } + + () + } + + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &DebugContext, + ) -> serde_json::Value { + json!({ + "type": "FacePile", + "bounds": bounds.to_json() + }) + } +} + +impl Extend for FacePile { + fn extend>(&mut self, children: T) { + self.faces.extend(children); + } +} diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 42f5540194..49884d7e30 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -119,7 +119,7 @@ export default function workspace(colorScheme: ColorScheme) { width: 1, }, }, - followerAvatarOverlap: 4, + followerAvatarOverlap: 6, avatarRibbon: { height: 3, width: 12,