@@ -1,15 +1,14 @@
use crate::face_pile::FacePile;
-use call::{ActiveCall, Room};
+use call::{ActiveCall, ParticipantLocation, Room};
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
use gpui::{
- actions, canvas, div, point, px, rems, AppContext, Div, Element, InteractiveElement,
- IntoElement, Model, ParentElement, Path, Render, RenderOnce, Stateful,
- StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView,
- WindowBounds,
+ actions, canvas, div, point, px, rems, AppContext, Div, Element, Hsla, InteractiveElement,
+ IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled,
+ Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
};
use project::{Project, RepositoryEntry};
use std::sync::Arc;
-use theme::ActiveTheme;
+use theme::{ActiveTheme, PlayerColors};
use ui::{
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
IconButton, IconElement, KeyBinding, Tooltip,
@@ -43,11 +42,8 @@ pub fn init(cx: &mut AppContext) {
pub struct CollabTitlebarItem {
project: Model<Project>,
- #[allow(unused)] // todo!()
user_store: Model<UserStore>,
- #[allow(unused)] // todo!()
client: Arc<Client>,
- #[allow(unused)] // todo!()
workspace: WeakView<Workspace>,
//branch_popover: Option<ViewHandle<BranchList>>,
//project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
@@ -92,62 +88,64 @@ impl Render for CollabTitlebarItem {
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx))
.when_some(
- current_user.clone().zip(room.clone()).zip(project_id),
- |this, ((current_user, room), project_id)| {
- let remote_participants = room
- .read(cx)
- .remote_participants()
- .values()
- .map(|participant| {
- (
- participant.user.clone(),
- participant.participant_index,
- participant.peer_id,
- )
- })
- .collect::<Vec<_>>();
-
- this.children(
- self.render_collaborator(
- ¤t_user,
- client.peer_id().expect("todo!()"),
- &room,
- project_id,
- &remote_participants,
- cx,
- )
- .map(|pile| pile.render(cx)),
- )
+ current_user
+ .clone()
+ .zip(client.peer_id())
+ .zip(room.clone())
+ .zip(project_id),
+ |this, (((current_user, peer_id), room), project_id)| {
+ let player_colors = cx.theme().players();
+ let room = room.read(cx);
+ let mut remote_participants =
+ room.remote_participants().values().collect::<Vec<_>>();
+ remote_participants.sort_by_key(|p| p.participant_index.0);
+
+ this.children(self.render_collaborator(
+ ¤t_user,
+ peer_id,
+ ParticipantLocation::SharedProject { project_id },
+ room.is_speaking(),
+ room.is_muted(cx),
+ &room,
+ project_id,
+ ¤t_user,
+ ))
.children(
- remote_participants.iter().filter_map(
- |(user, participant_index, peer_id)| {
- let peer_id = *peer_id;
- let face_pile = self
- .render_collaborator(
- user,
- peer_id,
- &room,
- project_id,
- &remote_participants,
- cx,
- )?
- .render(cx);
- Some(
- v_stack()
- .id(("collaborator", user.id))
- .child(face_pile)
- .child(render_color_ribbon(*participant_index, cx))
- .cursor_pointer()
- .on_click(cx.listener(move |this, _, cx| {
+ remote_participants.iter().filter_map(|collaborator| {
+ // collaborator.is_
+
+ let face_pile = self.render_collaborator(
+ &collaborator.user,
+ collaborator.peer_id,
+ collaborator.location.clone(),
+ collaborator.speaking,
+ collaborator.muted,
+ &room,
+ project_id,
+ ¤t_user,
+ )?;
+
+ Some(
+ v_stack()
+ .id(("collaborator", collaborator.user.id))
+ .child(face_pile)
+ .child(render_color_ribbon(
+ collaborator.participant_index,
+ player_colors,
+ ))
+ .cursor_pointer()
+ .on_click({
+ let peer_id = collaborator.peer_id;
+ cx.listener(move |this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
workspace.follow(peer_id, cx);
})
.ok();
- })),
- )
- },
- ),
+ })
+ }),
+ )
+ }),
)
},
),
@@ -280,15 +278,8 @@ impl Render for CollabTitlebarItem {
}
}
-fn render_color_ribbon(
- participant_index: ParticipantIndex,
- cx: &mut WindowContext,
-) -> gpui::Canvas {
- let color = cx
- .theme()
- .players()
- .color_for_participant(participant_index.0)
- .cursor;
+fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
+ let color = colors.color_for_participant(participant_index.0).cursor;
canvas(move |bounds, cx| {
let mut path = Path::new(bounds.lower_left());
let height = bounds.size.height;
@@ -417,25 +408,45 @@ impl CollabTitlebarItem {
&self,
user: &Arc<User>,
peer_id: PeerId,
- room: &Model<Room>,
+ location: ParticipantLocation,
+ is_speaking: bool,
+ is_muted: bool,
+ room: &Room,
project_id: u64,
- collaborators: &[(Arc<User>, ParticipantIndex, PeerId)],
- cx: &mut WindowContext,
+ current_user: &Arc<User>,
) -> Option<FacePile> {
- let room = room.read(cx);
let followers = room.followers_for(peer_id, project_id);
-
let mut pile = FacePile::default();
pile.extend(
user.avatar
.clone()
- .map(|avatar| div().child(Avatar::data(avatar.clone())).into_any_element())
+ .map(|avatar| {
+ div()
+ .child(
+ Avatar::data(avatar.clone())
+ .grayscale(
+ location != ParticipantLocation::SharedProject { project_id },
+ )
+ .border_color(if is_speaking {
+ gpui::blue()
+ } else if is_muted {
+ gpui::red()
+ } else {
+ Hsla::default()
+ }),
+ )
+ .into_any_element()
+ })
.into_iter()
.chain(followers.iter().filter_map(|follower_peer_id| {
- let follower = collaborators
- .iter()
- .find(|(_, _, peer_id)| *peer_id == *follower_peer_id)?
- .0
+ let follower = room
+ .remote_participants()
+ .values()
+ .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
+ .or_else(|| {
+ (self.client.peer_id() == Some(*follower_peer_id))
+ .then_some(current_user)
+ })?
.clone();
follower
.avatar
@@ -1,7 +1,6 @@
-use std::sync::Arc;
-
use crate::prelude::*;
-use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
+use gpui::{img, Div, Hsla, ImageData, ImageSource, Img, IntoElement, Styled};
+use std::sync::Arc;
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
@@ -12,35 +11,39 @@ pub enum Shape {
#[derive(IntoElement)]
pub struct Avatar {
- src: ImageSource,
+ image: Img,
+ border_color: Option<Hsla>,
is_available: Option<bool>,
- shape: Shape,
}
impl RenderOnce for Avatar {
type Rendered = Div;
- fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let mut img = img(self.src);
-
- if self.shape == Shape::Circle {
- img = img.rounded_full();
- } else {
- img = img.rounded_md();
+ fn render(mut self, cx: &mut WindowContext) -> Self::Rendered {
+ if self.image.style().corner_radii.top_left.is_none() {
+ self = self.shape(Shape::Circle);
}
- let size = rems(1.0);
+ let size = cx.rem_size();
div()
- .size(size)
+ .size(size + px(2.))
+ .map(|mut div| {
+ div.style().corner_radii = self.image.style().corner_radii.clone();
+ div
+ })
+ .when_some(self.border_color, |this, color| {
+ this.border().border_color(color)
+ })
.child(
- img.size(size)
+ self.image
+ .size(size)
// todo!(Pull the avatar fallback background from the theme.)
.bg(gpui::red()),
)
.children(self.is_available.map(|is_free| {
// HACK: non-integer sizes result in oval indicators.
- let indicator_size = (size.0 * cx.rem_size() * 0.4).round();
+ let indicator_size = (size * 0.4).round();
div()
.absolute()
@@ -56,31 +59,39 @@ impl RenderOnce for Avatar {
impl Avatar {
pub fn uri(src: impl Into<SharedString>) -> Self {
- Self {
- src: src.into().into(),
- shape: Shape::Circle,
- is_available: None,
- }
+ Self::source(src.into().into())
}
+
pub fn data(src: Arc<ImageData>) -> Self {
- Self {
- src: src.into(),
- shape: Shape::Circle,
- is_available: None,
- }
+ Self::source(src.into())
}
pub fn source(src: ImageSource) -> Self {
Self {
- src,
- shape: Shape::Circle,
+ image: img(src),
is_available: None,
+ border_color: None,
}
}
+
pub fn shape(mut self, shape: Shape) -> Self {
- self.shape = shape;
+ self.image = match shape {
+ Shape::Circle => self.image.rounded_full(),
+ Shape::RoundedRectangle => self.image.rounded_md(),
+ };
+ self
+ }
+
+ pub fn grayscale(mut self, grayscale: bool) -> Self {
+ self.image = self.image.grayscale(grayscale);
self
}
+
+ pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
+ self.border_color = Some(color.into());
+ self
+ }
+
pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
self.is_available = is_available.into();
self