Associate collaborator avatars with "ribbons" corresponding to their cursor color

This commit is contained in:
Nathan Sobo 2021-11-28 13:25:05 -07:00
parent a5039cad65
commit fbc307cd5e
6 changed files with 70 additions and 24 deletions

View file

@ -371,12 +371,7 @@ impl EditorElement {
let content_origin = bounds.origin() + layout.text_offset; let content_origin = bounds.origin() + layout.text_offset;
for (replica_id, selections) in &layout.selections { for (replica_id, selections) in &layout.selections {
let style_ix = *replica_id as usize % (style.guest_selections.len() + 1); let style = style.replica_selection_style(*replica_id);
let style = if style_ix == 0 {
&style.selection
} else {
&style.guest_selections[style_ix - 1]
};
for selection in selections { for selection in selections {
if selection.start != selection.end { if selection.start != selection.end {

View file

@ -25,6 +25,11 @@ impl Align {
self self
} }
pub fn bottom(mut self) -> Self {
self.alignment.set_y(1.0);
self
}
pub fn left(mut self) -> Self { pub fn left(mut self) -> Self {
self.alignment.set_x(-1.0); self.alignment.set_x(-1.0);
self self

View file

@ -63,7 +63,7 @@ pub enum Event {
Closed, Closed,
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Collaborator { pub struct Collaborator {
pub user: Arc<User>, pub user: Arc<User>,
pub peer_id: PeerId, pub peer_id: PeerId,

View file

@ -45,6 +45,7 @@ pub struct Titlebar {
pub height: f32, pub height: f32,
pub title: TextStyle, pub title: TextStyle,
pub avatar_width: f32, pub avatar_width: f32,
pub avatar_ribbon: AvatarRibbon,
pub offline_icon: OfflineIcon, pub offline_icon: OfflineIcon,
pub icon_color: Color, pub icon_color: Color,
pub avatar: ImageStyle, pub avatar: ImageStyle,
@ -53,6 +54,14 @@ pub struct Titlebar {
pub outdated_warning: ContainedText, 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)] #[derive(Clone, Deserialize, Default)]
pub struct OfflineIcon { pub struct OfflineIcon {
#[serde(flatten)] #[serde(flatten)]
@ -276,6 +285,15 @@ impl EditorStyle {
pub fn placeholder_text(&self) -> &TextStyle { pub fn placeholder_text(&self) -> &TextStyle {
self.placeholder_text.as_ref().unwrap_or(&self.text) 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 { impl InputEditorStyle {

View file

@ -968,11 +968,17 @@ impl Workspace {
Align::new( Align::new(
Flex::row() Flex::row()
.with_children(self.render_collaborators(theme, cx)) .with_children(self.render_collaborators(theme, cx))
.with_child(self.render_avatar( .with_child(
self.render_avatar(
self.user_store.read(cx).current_user().as_ref(), self.user_store.read(cx).current_user().as_ref(),
self.project
.read(cx)
.active_worktree()
.map(|worktree| worktree.read(cx).replica_id()),
theme, theme,
cx, cx,
)) ),
)
.with_children(self.render_connection_status()) .with_children(self.render_connection_status())
.boxed(), .boxed(),
) )
@ -991,14 +997,19 @@ impl Workspace {
fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> { fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
let mut elements = Vec::new(); let mut elements = Vec::new();
if let Some(active_worktree) = self.project.read(cx).active_worktree() { if let Some(active_worktree) = self.project.read(cx).active_worktree() {
let users = active_worktree let collaborators = active_worktree
.read(cx) .read(cx)
.collaborators() .collaborators()
.values() .values()
.map(|c| c.user.clone()) .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for user in users { for collaborator in collaborators {
elements.push(self.render_avatar(Some(&user), theme, cx)); elements.push(self.render_avatar(
Some(&collaborator.user),
Some(collaborator.replica_id),
theme,
cx,
));
} }
} }
elements elements
@ -1007,18 +1018,34 @@ impl Workspace {
fn render_avatar( fn render_avatar(
&self, &self,
user: Option<&Arc<User>>, user: Option<&Arc<User>>,
replica_id: Option<u16>,
theme: &Theme, theme: &Theme,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
if let Some(avatar) = user.and_then(|user| user.avatar.clone()) { if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
ConstrainedBox::new( ConstrainedBox::new(
Align::new( Stack::new()
.with_child(
ConstrainedBox::new( ConstrainedBox::new(
Image::new(avatar) Image::new(avatar)
.with_style(theme.workspace.titlebar.avatar) .with_style(theme.workspace.titlebar.avatar)
.boxed(), .boxed(),
) )
.with_width(theme.workspace.titlebar.avatar_width) .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(), .boxed(),
) )
.boxed(), .boxed(),

View file

@ -10,8 +10,9 @@ height = 32
border = { width = 1, bottom = true, color = "$border.0" } border = { width = 1, bottom = true, color = "$border.0" }
title = "$text.0" title = "$text.0"
avatar_width = 18 avatar_width = 18
icon_color = "$text.2.color"
avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } } 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 } outdated_warning = { extends = "$text.2", size = 13 }
[workspace.titlebar.sign_in_prompt] [workspace.titlebar.sign_in_prompt]