@@ -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?;
@@ -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<ElementBox>,
+}
+
+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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "FacePile",
+ "bounds": bounds.to_json()
+ })
+ }
+}
+
+impl Extend<ElementBox> for FacePile {
+ fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+ self.faces.extend(children);
+ }
+}
@@ -119,7 +119,7 @@ export default function workspace(colorScheme: ColorScheme) {
width: 1,
},
},
- followerAvatarOverlap: 4,
+ followerAvatarOverlap: 6,
avatarRibbon: {
height: 3,
width: 12,