Add Facepile and PlayerStack components

This commit is contained in:
Marshall Bowers 2023-10-07 12:02:42 -04:00
parent 5e7954f152
commit f33d41af63
9 changed files with 314 additions and 2 deletions

View file

@ -2,6 +2,7 @@ pub mod assistant_panel;
pub mod breadcrumb;
pub mod buffer;
pub mod chat_panel;
pub mod facepile;
pub mod panel;
pub mod project_panel;
pub mod tab;

View file

@ -0,0 +1,35 @@
use std::marker::PhantomData;
use ui::prelude::*;
use ui::{static_players, Facepile};
use crate::story::Story;
#[derive(Element)]
pub struct FacepileStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> FacepileStory<S> {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
}
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
let players = static_players();
Story::container(cx)
.child(Story::title_for::<_, Facepile<S>>(cx))
.child(Story::label(cx, "Default"))
.child(
div()
.flex()
.gap_3()
.child(Facepile::new(players.clone().into_iter().take(1)))
.child(Facepile::new(players.clone().into_iter().take(2)))
.child(Facepile::new(players.clone().into_iter().take(3))),
)
}
}

View file

@ -40,6 +40,7 @@ pub enum ComponentStory {
Breadcrumb,
Buffer,
ChatPanel,
Facepile,
Panel,
ProjectPanel,
Tab,
@ -61,6 +62,7 @@ impl ComponentStory {
Self::Buffer => components::buffer::BufferStory::new().into_any(),
Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
Self::ChatPanel => components::chat_panel::ChatPanelStory::new().into_any(),
Self::Facepile => components::facepile::FacepileStory::new().into_any(),
Self::Panel => components::panel::PanelStory::new().into_any(),
Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
Self::Tab => components::tab::TabStory::new().into_any(),

View file

@ -3,10 +3,12 @@ mod breadcrumb;
mod buffer;
mod chat_panel;
mod editor_pane;
mod facepile;
mod icon_button;
mod list;
mod panel;
mod panes;
mod player_stack;
mod project_panel;
mod status_bar;
mod tab;
@ -21,10 +23,12 @@ pub use breadcrumb::*;
pub use buffer::*;
pub use chat_panel::*;
pub use editor_pane::*;
pub use facepile::*;
pub use icon_button::*;
pub use list::*;
pub use panel::*;
pub use panes::*;
pub use player_stack::*;
pub use project_panel::*;
pub use status_bar::*;
pub use tab::*;

View file

@ -0,0 +1,33 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{theme, Avatar, Player};
#[derive(Element)]
pub struct Facepile<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
players: Vec<Player>,
}
impl<S: 'static + Send + Sync> Facepile<S> {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
state_type: PhantomData,
players: players.collect(),
}
}
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
let theme = theme(cx);
let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
// TODO: Blocked on negative margins.
// .when(isnt_last, |div| div.neg_mr_1())
.child(Avatar::new(player.avatar_src().to_string()))
});
div().p_1().flex().items_center().children(player_list)
}
}

View file

@ -0,0 +1,72 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{Avatar, Facepile, PlayerWithCallStatus};
#[derive(Element)]
pub struct PlayerStack<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
player_with_call_status: PlayerWithCallStatus,
}
impl<S: 'static + Send + Sync> PlayerStack<S> {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
state_type: PhantomData,
player_with_call_status,
}
}
fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
let system_color = SystemColor::new();
let player = self.player_with_call_status.get_player();
self.player_with_call_status.get_call_status();
let followers = self
.player_with_call_status
.get_call_status()
.followers
.as_ref()
.map(|followers| followers.clone());
// if we have no followers return a slightly different element
// if mic_status == muted add a red ring to avatar
div()
.h_full()
.flex()
.flex_col()
.gap_px()
.justify_center()
.child(
div().flex().justify_center().w_full().child(
div()
.w_4()
.h_0p5()
.rounded_sm()
.fill(player.cursor_color(cx)),
),
)
.child(
div()
.flex()
.items_center()
.justify_center()
.h_6()
.pl_1()
.rounded_lg()
.fill(if followers.is_none() {
system_color.transparent
} else {
player.selection_color(cx)
})
.child(Avatar::new(player.avatar_src().to_string()))
.children(followers.map(|followers| {
div()
// TODO: Blocked on negative margins.
// .neg_ml_2()
.child(Facepile::new(followers.into_iter()))
})),
)
}
}

View file

@ -3,6 +3,7 @@ mod button;
mod icon;
mod input;
mod label;
mod player;
mod stack;
mod tool_divider;
@ -11,5 +12,6 @@ pub use button::*;
pub use icon::*;
pub use input::*;
pub use label::*;
pub use player::*;
pub use stack::*;
pub use tool_divider::*;

View file

@ -0,0 +1,133 @@
use gpui3::{Hsla, ViewContext};
use crate::theme;
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum PlayerStatus {
#[default]
Offline,
Online,
InCall,
Away,
DoNotDisturb,
Invisible,
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum MicStatus {
Muted,
#[default]
Unmuted,
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum VideoStatus {
On,
#[default]
Off,
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum ScreenShareStatus {
Shared,
#[default]
NotShared,
}
#[derive(Clone)]
pub struct PlayerCallStatus {
pub mic_status: MicStatus,
/// Indicates if the player is currently speaking
/// And the intensity of the volume coming through
///
/// 0.0 - 1.0
pub voice_activity: f32,
pub video_status: VideoStatus,
pub screen_share_status: ScreenShareStatus,
pub in_current_project: bool,
pub disconnected: bool,
pub following: Option<Vec<Player>>,
pub followers: Option<Vec<Player>>,
}
impl PlayerCallStatus {
pub fn new() -> Self {
Self {
mic_status: MicStatus::default(),
voice_activity: 0.,
video_status: VideoStatus::default(),
screen_share_status: ScreenShareStatus::default(),
in_current_project: true,
disconnected: false,
following: None,
followers: None,
}
}
}
#[derive(PartialEq, Clone)]
pub struct Player {
index: usize,
avatar_src: String,
username: String,
status: PlayerStatus,
}
#[derive(Clone)]
pub struct PlayerWithCallStatus {
player: Player,
call_status: PlayerCallStatus,
}
impl PlayerWithCallStatus {
pub fn new(player: Player, call_status: PlayerCallStatus) -> Self {
Self {
player,
call_status,
}
}
pub fn get_player(&self) -> &Player {
&self.player
}
pub fn get_call_status(&self) -> &PlayerCallStatus {
&self.call_status
}
}
impl Player {
pub fn new(index: usize, avatar_src: String, username: String) -> Self {
Self {
index,
avatar_src,
username,
status: Default::default(),
}
}
pub fn set_status(mut self, status: PlayerStatus) -> Self {
self.status = status;
self
}
pub fn cursor_color<S: 'static>(&self, cx: &mut ViewContext<S>) -> Hsla {
let theme = theme(cx);
let index = self.index % 8;
theme.players[self.index].cursor
}
pub fn selection_color<S: 'static>(&self, cx: &mut ViewContext<S>) -> Hsla {
let theme = theme(cx);
let index = self.index % 8;
theme.players[self.index].selection
}
pub fn avatar_src(&self) -> &str {
&self.avatar_src
}
pub fn index(&self) -> usize {
self.index
}
}

View file

@ -3,8 +3,8 @@ use std::str::FromStr;
use crate::{
Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Symbol, Tab,
Theme, ToggleState,
HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Player, Symbol,
Tab, Theme, ToggleState,
};
pub fn static_tabs_example<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
@ -100,6 +100,36 @@ pub fn static_tabs_3<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
vec![Tab::new().git_status(GitStatus::Created).current(true)]
}
pub fn static_players() -> Vec<Player> {
vec![
Player::new(
0,
"https://avatars.githubusercontent.com/u/1714999?v=4".into(),
"nathansobo".into(),
),
Player::new(
1,
"https://avatars.githubusercontent.com/u/326587?v=4".into(),
"maxbrunsfeld".into(),
),
Player::new(
2,
"https://avatars.githubusercontent.com/u/482957?v=4".into(),
"as-cii".into(),
),
Player::new(
3,
"https://avatars.githubusercontent.com/u/1714999?v=4".into(),
"iamnbutler".into(),
),
Player::new(
4,
"https://avatars.githubusercontent.com/u/1486634?v=4".into(),
"maxdeviant".into(),
),
]
}
pub fn static_project_panel_project_items<S: 'static + Send + Sync + Clone>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("zed"))