diff --git a/crates/storybook2/src/stories/components.rs b/crates/storybook2/src/stories/components.rs index 78f1982effdd79a9cb842edaca96ba03f3e1d7e6..2ab3749c489287df30953854f4f7b6071f3ba74d 100644 --- a/crates/storybook2/src/stories/components.rs +++ b/crates/storybook2/src/stories/components.rs @@ -8,6 +8,7 @@ pub mod project_panel; pub mod tab; pub mod tab_bar; pub mod terminal; +pub mod title_bar; pub mod toolbar; pub mod traffic_lights; pub mod workspace; diff --git a/crates/storybook2/src/stories/components/title_bar.rs b/crates/storybook2/src/stories/components/title_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..b0cf2cd7711de64e0bff5eae8dd747b91b44da8b --- /dev/null +++ b/crates/storybook2/src/stories/components/title_bar.rs @@ -0,0 +1,26 @@ +use std::marker::PhantomData; + +use ui::prelude::*; +use ui::TitleBar; + +use crate::story::Story; + +#[derive(Element)] +pub struct TitleBarStory { + state_type: PhantomData, +} + +impl TitleBarStory { + pub fn new() -> Self { + Self { + state_type: PhantomData, + } + } + + fn render(&mut self, cx: &mut ViewContext) -> impl Element { + Story::container(cx) + .child(Story::title_for::<_, TitleBar>(cx)) + .child(Story::label(cx, "Default")) + .child(TitleBar::new(cx)) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 0ae51bf47f6e95af0ebcbd7ff4afac04744b97f7..646ba0dbd9db84c5071ba6af4fa4f352f288ccfa 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -46,6 +46,7 @@ pub enum ComponentStory { Tab, TabBar, Terminal, + TitleBar, Toolbar, TrafficLights, Workspace, @@ -68,6 +69,7 @@ impl ComponentStory { Self::Tab => components::tab::TabStory::new().into_any(), Self::TabBar => components::tab_bar::TabBarStory::new().into_any(), Self::Terminal => components::terminal::TerminalStory::new().into_any(), + Self::TitleBar => components::title_bar::TitleBarStory::new().into_any(), Self::Toolbar => components::toolbar::ToolbarStory::new().into_any(), Self::TrafficLights => components::traffic_lights::TrafficLightsStory::new().into_any(), Self::Workspace => components::workspace::WorkspaceStory::new().into_any(), diff --git a/crates/ui2/src/components.rs b/crates/ui2/src/components.rs index f0fa249b75f3d2035a232f541f7c73ee187e07dc..454b2e571e63f9974b932fd6ca6c1d33ef97aee1 100644 --- a/crates/ui2/src/components.rs +++ b/crates/ui2/src/components.rs @@ -14,6 +14,7 @@ mod status_bar; mod tab; mod tab_bar; mod terminal; +mod title_bar; mod toolbar; mod traffic_lights; mod workspace; @@ -34,6 +35,7 @@ pub use status_bar::*; pub use tab::*; pub use tab_bar::*; pub use terminal::*; +pub use title_bar::*; pub use toolbar::*; pub use traffic_lights::*; pub use workspace::*; diff --git a/crates/ui2/src/components/title_bar.rs b/crates/ui2/src/components/title_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..79c0b538905da4010f0a84a6e590bbf4fe36aae6 --- /dev/null +++ b/crates/ui2/src/components/title_bar.rs @@ -0,0 +1,119 @@ +use std::marker::PhantomData; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use crate::prelude::*; +use crate::{ + theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, PlayerWithCallStatus, + ToolDivider, TrafficLights, +}; + +#[derive(Clone)] +pub struct Livestream { + pub players: Vec, + pub channel: Option, // projects + // windows +} + +#[derive(Element)] +pub struct TitleBar { + state_type: PhantomData, + /// If the window is active from the OS's perspective. + is_active: Arc, + livestream: Option, +} + +impl TitleBar { + pub fn new(cx: &mut ViewContext) -> Self { + let is_active = Arc::new(AtomicBool::new(true)); + let active = is_active.clone(); + + // cx.observe_window_activation(move |_, is_active, cx| { + // active.store(is_active, std::sync::atomic::Ordering::SeqCst); + // cx.notify(); + // }) + // .detach(); + + Self { + state_type: PhantomData, + is_active, + livestream: None, + } + } + + pub fn set_livestream(mut self, livestream: Option) -> Self { + self.livestream = livestream; + self + } + + fn render(&mut self, cx: &mut ViewContext) -> impl Element { + let theme = theme(cx); + // let has_focus = cx.window_is_active(); + let has_focus = true; + + let player_list = if let Some(livestream) = &self.livestream { + livestream.players.clone().into_iter() + } else { + vec![].into_iter() + }; + + div() + .flex() + .items_center() + .justify_between() + .w_full() + .h_8() + .fill(theme.lowest.base.default.background) + .child( + div() + .flex() + .items_center() + .h_full() + .gap_4() + .px_2() + .child(TrafficLights::new().window_has_focus(has_focus)) + // === Project Info === // + .child( + div() + .flex() + .items_center() + .gap_1() + .child(Button::new("zed")) + .child(Button::new("nate/gpui2-ui-components")), + ) + .children(player_list.map(|p| PlayerStack::new(p))) + .child(IconButton::new(Icon::Plus)), + ) + .child( + div() + .flex() + .items_center() + .child( + div() + .px_2() + .flex() + .items_center() + .gap_1() + .child(IconButton::new(Icon::FolderX)) + .child(IconButton::new(Icon::Close)), + ) + .child(ToolDivider::new()) + .child( + div() + .px_2() + .flex() + .items_center() + .gap_1() + .child(IconButton::new(Icon::Mic)) + .child(IconButton::new(Icon::AudioOn)) + .child(IconButton::new(Icon::Screen).color(IconColor::Accent)), + ) + .child( + div().px_2().flex().items_center().child( + Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") + .shape(Shape::RoundedRectangle), + ), + ), + ) + } +} diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index 3f109ddf5e1ac858fe22997a54db4797165e625c..b74654ca8be84b349cf5273bf514152ae5766fa4 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -6,9 +6,9 @@ use gpui3::{relative, rems, Size}; use crate::prelude::*; use crate::{ - hello_world_rust_editor_with_status_example, theme, v_stack, ChatMessage, ChatPanel, - EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, - StatusBar, Terminal, + hello_world_rust_editor_with_status_example, random_players_with_call_status, theme, v_stack, + ChatMessage, ChatPanel, EditorPane, Livestream, Pane, PaneGroup, Panel, PanelAllowedSides, + PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, }; #[derive(Element)] @@ -102,10 +102,10 @@ impl WorkspaceElement { .items_start() .text_color(theme.lowest.base.default.foreground) .fill(theme.lowest.base.default.background) - // .child(TitleBar::new(cx).set_livestream(Some(Livestream { - // players: random_players_with_call_status(7), - // channel: Some("gpui2-ui".to_string()), - // }))) + .child(TitleBar::new(cx).set_livestream(Some(Livestream { + players: random_players_with_call_status(7), + channel: Some("gpui2-ui".to_string()), + }))) .child( div() .flex_1() diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 8b83434d8704a0bdc919d7dcf51e21bd3cb01fb3..36b98817697fe58f58a1bf88c2d64f5495370a59 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -1,10 +1,13 @@ use std::path::PathBuf; use std::str::FromStr; +use rand::Rng; + use crate::{ Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor, - HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Player, Symbol, - Tab, Theme, ToggleState, + HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListItem, Livestream, + MicStatus, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, + Theme, ToggleState, VideoStatus, }; pub fn static_tabs_example() -> Vec> { @@ -130,6 +133,193 @@ pub fn static_players() -> Vec { ] } +#[derive(Debug)] +pub struct PlayerData { + pub url: String, + pub name: String, +} + +pub fn static_player_data() -> Vec { + vec![ + PlayerData { + url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(), + name: "iamnbutler".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/326587?v=4".into(), + name: "maxbrunsfeld".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/482957?v=4".into(), + name: "as-cii".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/1789?v=4".into(), + name: "nathansobo".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), + name: "ForLoveOfCats".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(), + name: "SomeoneToIgnore".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(), + name: "JosephTLyons".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(), + name: "osiewicz".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(), + name: "KCaverly".into(), + }, + PlayerData { + url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), + name: "maxdeviant".into(), + }, + ] +} + +pub fn create_static_players(player_data: Vec) -> Vec { + let mut players = Vec::new(); + for data in player_data { + players.push(Player::new(players.len(), data.url, data.name)); + } + players +} + +pub fn static_player_1(data: &Vec) -> Player { + Player::new(1, data[0].url.clone(), data[0].name.clone()) +} + +pub fn static_player_2(data: &Vec) -> Player { + Player::new(2, data[1].url.clone(), data[1].name.clone()) +} + +pub fn static_player_3(data: &Vec) -> Player { + Player::new(3, data[2].url.clone(), data[2].name.clone()) +} + +pub fn static_player_4(data: &Vec) -> Player { + Player::new(4, data[3].url.clone(), data[3].name.clone()) +} + +pub fn static_player_5(data: &Vec) -> Player { + Player::new(5, data[4].url.clone(), data[4].name.clone()) +} + +pub fn static_player_6(data: &Vec) -> Player { + Player::new(6, data[5].url.clone(), data[5].name.clone()) +} + +pub fn static_player_7(data: &Vec) -> Player { + Player::new(7, data[6].url.clone(), data[6].name.clone()) +} + +pub fn static_player_8(data: &Vec) -> Player { + Player::new(8, data[7].url.clone(), data[7].name.clone()) +} + +pub fn static_player_9(data: &Vec) -> Player { + Player::new(9, data[8].url.clone(), data[8].name.clone()) +} + +pub fn static_player_10(data: &Vec) -> Player { + Player::new(10, data[9].url.clone(), data[9].name.clone()) +} + +pub fn static_livestream() -> Livestream { + Livestream { + players: random_players_with_call_status(7), + channel: Some("gpui2-ui".to_string()), + } +} + +pub fn populate_player_call_status( + player: Player, + followers: Option>, +) -> PlayerCallStatus { + let mut rng = rand::thread_rng(); + let in_current_project: bool = rng.gen(); + let disconnected: bool = rng.gen(); + let voice_activity: f32 = rng.gen(); + let mic_status = if rng.gen_bool(0.5) { + MicStatus::Muted + } else { + MicStatus::Unmuted + }; + let video_status = if rng.gen_bool(0.5) { + VideoStatus::On + } else { + VideoStatus::Off + }; + let screen_share_status = if rng.gen_bool(0.5) { + ScreenShareStatus::Shared + } else { + ScreenShareStatus::NotShared + }; + PlayerCallStatus { + mic_status, + voice_activity, + video_status, + screen_share_status, + in_current_project, + disconnected, + following: None, + followers, + } +} + +pub fn random_players_with_call_status(number_of_players: usize) -> Vec { + let players = create_static_players(static_player_data()); + let mut player_status = vec![]; + for i in 0..number_of_players { + let followers = if i == 0 { + Some(vec![ + players[1].clone(), + players[3].clone(), + players[5].clone(), + players[6].clone(), + ]) + } else if i == 1 { + Some(vec![players[2].clone(), players[6].clone()]) + } else { + None + }; + let call_status = populate_player_call_status(players[i].clone(), followers); + player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status)); + } + player_status +} + +pub fn static_players_with_call_status() -> Vec { + let players = static_players(); + let mut player_0_status = PlayerCallStatus::new(); + let player_1_status = PlayerCallStatus::new(); + let player_2_status = PlayerCallStatus::new(); + let mut player_3_status = PlayerCallStatus::new(); + let mut player_4_status = PlayerCallStatus::new(); + + player_0_status.screen_share_status = ScreenShareStatus::Shared; + player_0_status.followers = Some(vec![players[1].clone(), players[3].clone()]); + + player_3_status.voice_activity = 0.5; + player_4_status.mic_status = MicStatus::Muted; + player_4_status.in_current_project = false; + + vec![ + PlayerWithCallStatus::new(players[0].clone(), player_0_status), + PlayerWithCallStatus::new(players[1].clone(), player_1_status), + PlayerWithCallStatus::new(players[2].clone(), player_2_status), + PlayerWithCallStatus::new(players[3].clone(), player_3_status), + PlayerWithCallStatus::new(players[4].clone(), player_4_status), + ] +} + pub fn static_project_panel_project_items() -> Vec> { vec![ ListEntry::new(Label::new("zed"))