diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 03b04c8c6b..2f4923985f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -371,12 +371,7 @@ impl EditorElement { let content_origin = bounds.origin() + layout.text_offset; for (replica_id, selections) in &layout.selections { - let style_ix = *replica_id as usize % (style.guest_selections.len() + 1); - let style = if style_ix == 0 { - &style.selection - } else { - &style.guest_selections[style_ix - 1] - }; + let style = style.replica_selection_style(*replica_id); for selection in selections { if selection.start != selection.end { diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index 7f065e2b53..ce99437f3b 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -25,6 +25,11 @@ impl Align { self } + pub fn bottom(mut self) -> Self { + self.alignment.set_y(1.0); + self + } + pub fn left(mut self) -> Self { self.alignment.set_x(-1.0); self diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 50f5b2d044..7187310be7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -63,7 +63,7 @@ pub enum Event { Closed, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Collaborator { pub user: Arc, pub peer_id: PeerId, diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 431937cc7e..716ce4a46a 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -45,6 +45,7 @@ pub struct Titlebar { pub height: f32, pub title: TextStyle, pub avatar_width: f32, + pub avatar_ribbon: AvatarRibbon, pub offline_icon: OfflineIcon, pub icon_color: Color, pub avatar: ImageStyle, @@ -53,6 +54,14 @@ pub struct Titlebar { pub outdated_warning: ContainedText, } +#[derive(Clone, Deserialize, Default)] +pub struct AvatarRibbon { + #[serde(flatten)] + pub container: ContainerStyle, + pub width: f32, + pub height: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct OfflineIcon { #[serde(flatten)] @@ -276,6 +285,15 @@ impl EditorStyle { pub fn placeholder_text(&self) -> &TextStyle { self.placeholder_text.as_ref().unwrap_or(&self.text) } + + pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle { + let style_ix = replica_id as usize % (self.guest_selections.len() + 1); + if style_ix == 0 { + &self.selection + } else { + &self.guest_selections[style_ix - 1] + } + } } impl InputEditorStyle { diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 0f636c23eb..9c6d0d43c2 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -968,11 +968,17 @@ impl Workspace { Align::new( Flex::row() .with_children(self.render_collaborators(theme, cx)) - .with_child(self.render_avatar( - self.user_store.read(cx).current_user().as_ref(), - theme, - cx, - )) + .with_child( + self.render_avatar( + self.user_store.read(cx).current_user().as_ref(), + self.project + .read(cx) + .active_worktree() + .map(|worktree| worktree.read(cx).replica_id()), + theme, + cx, + ), + ) .with_children(self.render_connection_status()) .boxed(), ) @@ -991,14 +997,19 @@ impl Workspace { fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext) -> Vec { let mut elements = Vec::new(); if let Some(active_worktree) = self.project.read(cx).active_worktree() { - let users = active_worktree + let collaborators = active_worktree .read(cx) .collaborators() .values() - .map(|c| c.user.clone()) + .cloned() .collect::>(); - for user in users { - elements.push(self.render_avatar(Some(&user), theme, cx)); + for collaborator in collaborators { + elements.push(self.render_avatar( + Some(&collaborator.user), + Some(collaborator.replica_id), + theme, + cx, + )); } } elements @@ -1007,21 +1018,37 @@ impl Workspace { fn render_avatar( &self, user: Option<&Arc>, + replica_id: Option, theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { if let Some(avatar) = user.and_then(|user| user.avatar.clone()) { ConstrainedBox::new( - Align::new( - ConstrainedBox::new( - Image::new(avatar) - .with_style(theme.workspace.titlebar.avatar) + Stack::new() + .with_child( + ConstrainedBox::new( + Image::new(avatar) + .with_style(theme.workspace.titlebar.avatar) + .boxed(), + ) + .with_width(theme.workspace.titlebar.avatar_width) + .aligned() + .boxed(), + ) + .with_child( + Container::new(Empty::new().boxed()) + .with_style(theme.workspace.titlebar.avatar_ribbon.container) + .with_background_color(replica_id.map_or(Default::default(), |id| { + theme.editor.replica_selection_style(id).cursor + })) + .constrained() + .with_width(theme.workspace.titlebar.avatar_ribbon.width) + .with_height(theme.workspace.titlebar.avatar_ribbon.height) + .aligned() + .bottom() .boxed(), ) - .with_width(theme.workspace.titlebar.avatar_width) .boxed(), - ) - .boxed(), ) .with_width(theme.workspace.right_sidebar.width) .boxed() diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 40868262fb..7d25ee87fc 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -10,8 +10,9 @@ height = 32 border = { width = 1, bottom = true, color = "$border.0" } title = "$text.0" avatar_width = 18 -icon_color = "$text.2.color" avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } } +avatar_ribbon = { background = "#ff0000", height = 3, width = 12 } +icon_color = "$text.2.color" outdated_warning = { extends = "$text.2", size = 13 } [workspace.titlebar.sign_in_prompt]