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;
Marshall Bowers created
crates/storybook2/src/stories/components.rs | 1
crates/storybook2/src/stories/components/title_bar.rs | 26 +
crates/storybook2/src/story_selector.rs | 2
crates/ui2/src/components.rs | 2
crates/ui2/src/components/title_bar.rs | 119 +++++++
crates/ui2/src/components/workspace.rs | 14
crates/ui2/src/static_data.rs | 194 ++++++++++++
7 files changed, 349 insertions(+), 9 deletions(-)
@@ -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;
@@ -0,0 +1,26 @@
+use std::marker::PhantomData;
+
+use ui::prelude::*;
+use ui::TitleBar;
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct TitleBarStory<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> TitleBarStory<S> {
+ pub fn new() -> Self {
+ Self {
+ state_type: PhantomData,
+ }
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ Story::container(cx)
+ .child(Story::title_for::<_, TitleBar<S>>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(TitleBar::new(cx))
+ }
+}
@@ -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(),
@@ -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::*;
@@ -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<PlayerWithCallStatus>,
+ pub channel: Option<String>, // projects
+ // windows
+}
+
+#[derive(Element)]
+pub struct TitleBar<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+ /// If the window is active from the OS's perspective.
+ is_active: Arc<AtomicBool>,
+ livestream: Option<Livestream>,
+}
+
+impl<S: 'static + Send + Sync + Clone> TitleBar<S> {
+ pub fn new(cx: &mut ViewContext<S>) -> 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<Livestream>) -> Self {
+ self.livestream = livestream;
+ self
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ 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),
+ ),
+ ),
+ )
+ }
+}
@@ -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<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
.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()
@@ -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<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
@@ -130,6 +133,193 @@ pub fn static_players() -> Vec<Player> {
]
}
+#[derive(Debug)]
+pub struct PlayerData {
+ pub url: String,
+ pub name: String,
+}
+
+pub fn static_player_data() -> Vec<PlayerData> {
+ 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<PlayerData>) -> Vec<Player> {
+ 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<PlayerData>) -> Player {
+ Player::new(1, data[0].url.clone(), data[0].name.clone())
+}
+
+pub fn static_player_2(data: &Vec<PlayerData>) -> Player {
+ Player::new(2, data[1].url.clone(), data[1].name.clone())
+}
+
+pub fn static_player_3(data: &Vec<PlayerData>) -> Player {
+ Player::new(3, data[2].url.clone(), data[2].name.clone())
+}
+
+pub fn static_player_4(data: &Vec<PlayerData>) -> Player {
+ Player::new(4, data[3].url.clone(), data[3].name.clone())
+}
+
+pub fn static_player_5(data: &Vec<PlayerData>) -> Player {
+ Player::new(5, data[4].url.clone(), data[4].name.clone())
+}
+
+pub fn static_player_6(data: &Vec<PlayerData>) -> Player {
+ Player::new(6, data[5].url.clone(), data[5].name.clone())
+}
+
+pub fn static_player_7(data: &Vec<PlayerData>) -> Player {
+ Player::new(7, data[6].url.clone(), data[6].name.clone())
+}
+
+pub fn static_player_8(data: &Vec<PlayerData>) -> Player {
+ Player::new(8, data[7].url.clone(), data[7].name.clone())
+}
+
+pub fn static_player_9(data: &Vec<PlayerData>) -> Player {
+ Player::new(9, data[8].url.clone(), data[8].name.clone())
+}
+
+pub fn static_player_10(data: &Vec<PlayerData>) -> 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<Vec<Player>>,
+) -> 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<PlayerWithCallStatus> {
+ 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<PlayerWithCallStatus> {
+ 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<S: 'static + Send + Sync + Clone>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("zed"))