Add following UI in collab titlebar (#3544)
* [x] restructure titlebar to show facepiles for each collaborator and their followers * [x] allow following collaborators by clicking their avatars in the titlebar * [x] show grayscale avatar for collaborators not focused on this project * [x] show collaborators' microphone activity and muted status in the titlebar * [x] in facepile, show leader in front of followers
This commit is contained in:
commit
5e3d0a6d03
4 changed files with 313 additions and 1146 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,9 @@
|
|||
use gpui::{
|
||||
div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext,
|
||||
div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Styled,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, IntoElement)]
|
||||
pub struct FacePile {
|
||||
pub faces: Vec<AnyElement>,
|
||||
}
|
||||
|
@ -15,64 +16,15 @@ impl RenderOnce for FacePile {
|
|||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
||||
let isnt_last = ix < player_count - 1;
|
||||
|
||||
div().when(isnt_last, |div| div.neg_mr_1()).child(player)
|
||||
div()
|
||||
.z_index((player_count - ix) as u32)
|
||||
.when(isnt_last, |div| div.neg_mr_1())
|
||||
.child(player)
|
||||
});
|
||||
div().p_1().flex().items_center().children(player_list)
|
||||
}
|
||||
}
|
||||
|
||||
// impl Element for FacePile {
|
||||
// type State = ();
|
||||
// fn layout(
|
||||
// &mut self,
|
||||
// state: Option<Self::State>,
|
||||
// cx: &mut WindowContext,
|
||||
// ) -> (LayoutId, Self::State) {
|
||||
// let mut width = 0.;
|
||||
// let mut max_height = 0.;
|
||||
// let mut faces = Vec::with_capacity(self.faces.len());
|
||||
// for face in &mut self.faces {
|
||||
// let layout = face.layout(cx);
|
||||
// width += layout.x();
|
||||
// max_height = f32::max(max_height, layout.y());
|
||||
// faces.push(layout);
|
||||
// }
|
||||
// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
|
||||
// (cx.request_layout(&Style::default(), faces), ())
|
||||
// // (
|
||||
// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
|
||||
// // (),
|
||||
// // ))
|
||||
// }
|
||||
|
||||
// fn paint(
|
||||
// &mut self,
|
||||
// bounds: RectF,
|
||||
// visible_bounds: RectF,
|
||||
// _layout: &mut Self::LayoutState,
|
||||
// view: &mut V,
|
||||
// cx: &mut ViewContext<V>,
|
||||
// ) -> 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();
|
||||
// let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
|
||||
|
||||
// cx.scene().push_layer(None);
|
||||
// face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
|
||||
// cx.scene().pop_layer();
|
||||
// origin_x += self.overlap;
|
||||
// }
|
||||
|
||||
// ()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Extend<AnyElement> for FacePile {
|
||||
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
|
||||
self.faces.extend(children);
|
||||
|
|
|
@ -2839,3 +2839,9 @@ impl From<(&'static str, usize)> for ElementId {
|
|||
ElementId::NamedInteger(name.into(), id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&'static str, u64)> for ElementId {
|
||||
fn from((name, id): (&'static str, u64)) -> Self {
|
||||
ElementId::NamedInteger(name.into(), id as usize)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue