Add Facepile
and PlayerStack
components
This commit is contained in:
parent
5e7954f152
commit
f33d41af63
9 changed files with 314 additions and 2 deletions
|
@ -2,6 +2,7 @@ pub mod assistant_panel;
|
||||||
pub mod breadcrumb;
|
pub mod breadcrumb;
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod chat_panel;
|
pub mod chat_panel;
|
||||||
|
pub mod facepile;
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
pub mod project_panel;
|
pub mod project_panel;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
35
crates/storybook2/src/stories/components/facepile.rs
Normal file
35
crates/storybook2/src/stories/components/facepile.rs
Normal 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))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ pub enum ComponentStory {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Buffer,
|
Buffer,
|
||||||
ChatPanel,
|
ChatPanel,
|
||||||
|
Facepile,
|
||||||
Panel,
|
Panel,
|
||||||
ProjectPanel,
|
ProjectPanel,
|
||||||
Tab,
|
Tab,
|
||||||
|
@ -61,6 +62,7 @@ impl ComponentStory {
|
||||||
Self::Buffer => components::buffer::BufferStory::new().into_any(),
|
Self::Buffer => components::buffer::BufferStory::new().into_any(),
|
||||||
Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
|
Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
|
||||||
Self::ChatPanel => components::chat_panel::ChatPanelStory::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::Panel => components::panel::PanelStory::new().into_any(),
|
||||||
Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
|
Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
|
||||||
Self::Tab => components::tab::TabStory::new().into_any(),
|
Self::Tab => components::tab::TabStory::new().into_any(),
|
||||||
|
|
|
@ -3,10 +3,12 @@ mod breadcrumb;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod chat_panel;
|
mod chat_panel;
|
||||||
mod editor_pane;
|
mod editor_pane;
|
||||||
|
mod facepile;
|
||||||
mod icon_button;
|
mod icon_button;
|
||||||
mod list;
|
mod list;
|
||||||
mod panel;
|
mod panel;
|
||||||
mod panes;
|
mod panes;
|
||||||
|
mod player_stack;
|
||||||
mod project_panel;
|
mod project_panel;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
mod tab;
|
mod tab;
|
||||||
|
@ -21,10 +23,12 @@ pub use breadcrumb::*;
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
pub use chat_panel::*;
|
pub use chat_panel::*;
|
||||||
pub use editor_pane::*;
|
pub use editor_pane::*;
|
||||||
|
pub use facepile::*;
|
||||||
pub use icon_button::*;
|
pub use icon_button::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
pub use panes::*;
|
pub use panes::*;
|
||||||
|
pub use player_stack::*;
|
||||||
pub use project_panel::*;
|
pub use project_panel::*;
|
||||||
pub use status_bar::*;
|
pub use status_bar::*;
|
||||||
pub use tab::*;
|
pub use tab::*;
|
||||||
|
|
33
crates/ui2/src/components/facepile.rs
Normal file
33
crates/ui2/src/components/facepile.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
72
crates/ui2/src/components/player_stack.rs
Normal file
72
crates/ui2/src/components/player_stack.rs
Normal 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()))
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ mod button;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod input;
|
mod input;
|
||||||
mod label;
|
mod label;
|
||||||
|
mod player;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod tool_divider;
|
mod tool_divider;
|
||||||
|
|
||||||
|
@ -11,5 +12,6 @@ pub use button::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
|
pub use player::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use tool_divider::*;
|
pub use tool_divider::*;
|
||||||
|
|
133
crates/ui2/src/elements/player.rs
Normal file
133
crates/ui2/src/elements/player.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
|
Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
|
||||||
HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Symbol, Tab,
|
HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Player, Symbol,
|
||||||
Theme, ToggleState,
|
Tab, Theme, ToggleState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn static_tabs_example<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
|
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)]
|
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>> {
|
pub fn static_project_panel_project_items<S: 'static + Send + Sync + Clone>() -> Vec<ListItem<S>> {
|
||||||
vec![
|
vec![
|
||||||
ListEntry::new(Label::new("zed"))
|
ListEntry::new(Label::new("zed"))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue