crates/storybook/src/prelude.rs 🔗
@@ -5,7 +5,7 @@ pub enum ButtonVariant {
Filled,
}
-#[derive(Default, PartialEq)]
+#[derive(Default, PartialEq, Clone, Copy)]
pub enum Shape {
#[default]
Circle,
Nate Butler created
crates/storybook/src/prelude.rs | 2
crates/storybook/src/ui.rs | 27 +++----
crates/storybook/src/ui/component.rs | 2
crates/storybook/src/ui/component/facepile.rs | 31 ++++++++++
crates/storybook/src/ui/component/follow_group.rs | 52 +++++++++++++++++
crates/storybook/src/ui/element.rs | 1
crates/storybook/src/ui/element/avatar.rs | 2
crates/storybook/src/ui/element/indicator.rs | 32 ++++++++++
crates/storybook/src/ui/module/title_bar.rs | 12 +++
9 files changed, 142 insertions(+), 19 deletions(-)
@@ -5,7 +5,7 @@ pub enum ButtonVariant {
Filled,
}
-#[derive(Default, PartialEq)]
+#[derive(Default, PartialEq, Clone, Copy)]
pub enum Shape {
#[default]
Circle,
@@ -2,20 +2,17 @@ mod component;
mod element;
mod module;
-pub use component::tab::tab;
-pub use component::tab::Tab;
+pub use component::facepile::*;
+pub use component::follow_group::*;
+pub use component::tab::*;
-pub use module::chat_panel::chat_panel;
-pub use module::status_bar::status_bar;
-pub use module::status_bar::StatusBar;
-pub use module::tab_bar::tab_bar;
-pub use module::title_bar::title_bar;
+pub use module::chat_panel::*;
+pub use module::status_bar::*;
+pub use module::tab_bar::*;
+pub use module::title_bar::*;
-pub use element::avatar::avatar;
-pub use element::avatar::Avatar;
-pub use element::icon_button::icon_button;
-pub use element::icon_button::IconButton;
-pub use element::text_button::text_button;
-pub use element::text_button::TextButton;
-pub use element::tool_divider::tool_divider;
-pub use element::tool_divider::ToolDivider;
+pub use element::avatar::*;
+pub use element::icon_button::*;
+pub use element::indicator::*;
+pub use element::text_button::*;
+pub use element::tool_divider::*;
@@ -1 +1,3 @@
+pub(crate) mod facepile;
+pub(crate) mod follow_group;
pub(crate) mod tab;
@@ -0,0 +1,31 @@
+use crate::theme::theme;
+use crate::ui::Avatar;
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ParentElement, ViewContext};
+
+#[derive(Element)]
+pub struct Facepile {
+ players: Vec<Avatar>,
+}
+
+pub fn facepile(players: Vec<Avatar>) -> Facepile {
+ Facepile { players }
+}
+
+impl Facepile {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+ let player_list = self
+ .players
+ .iter()
+ .map(|player| div().right_1().child(player.clone()));
+
+ div()
+ .relative()
+ .p_1()
+ .flex()
+ .items_center()
+ .children(player_list)
+ }
+}
@@ -0,0 +1,52 @@
+use crate::theme::theme;
+use crate::ui::{facepile, indicator, Avatar};
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ParentElement, ViewContext};
+
+#[derive(Element)]
+pub struct FollowGroup {
+ player: usize,
+ players: Vec<Avatar>,
+}
+
+pub fn follow_group(players: Vec<Avatar>) -> FollowGroup {
+ FollowGroup { player: 0, players }
+}
+
+impl FollowGroup {
+ pub fn player(mut self, player: usize) -> Self {
+ self.player = player;
+ self
+ }
+
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+ let player_bg = theme.players[self.player].selection;
+
+ div()
+ .h_full()
+ .flex()
+ .flex_col()
+ .gap_px()
+ .justify_center()
+ .child(
+ div()
+ .flex()
+ .justify_center()
+ .w_full()
+ .child(indicator().player(self.player)),
+ )
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .justify_center()
+ .h_6()
+ .px_1()
+ .rounded_lg()
+ .fill(player_bg)
+ .child(facepile(self.players.clone())),
+ )
+ }
+}
@@ -1,4 +1,5 @@
pub(crate) mod avatar;
pub(crate) mod icon_button;
+pub(crate) mod indicator;
pub(crate) mod text_button;
pub(crate) mod tool_divider;
@@ -7,7 +7,7 @@ use gpui2::{Element, ViewContext};
pub type UnknownString = ArcCow<'static, str>;
-#[derive(Element)]
+#[derive(Element, Clone)]
pub struct Avatar {
src: ArcCow<'static, str>,
shape: Shape,
@@ -0,0 +1,32 @@
+use crate::theme::theme;
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ViewContext};
+
+#[derive(Element)]
+pub struct Indicator {
+ player: usize,
+}
+
+pub fn indicator() -> Indicator {
+ Indicator { player: 0 }
+}
+
+impl Indicator {
+ pub fn player(mut self, player: usize) -> Self {
+ self.player = player;
+ self
+ }
+
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+ let player_color = theme.players[self.player].cursor;
+
+ div()
+ .w_4()
+ .h_1()
+ .rounded_bl_sm()
+ .rounded_br_sm()
+ .fill(player_color)
+ }
+}
@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use crate::prelude::Shape;
use crate::theme::theme;
-use crate::ui::{avatar, icon_button, text_button, tool_divider};
+use crate::ui::{avatar, follow_group, icon_button, text_button, tool_divider};
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
@@ -21,6 +21,10 @@ pub fn title_bar<V: 'static>() -> TitleBar<V> {
impl<V: 'static> TitleBar<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
+ let player_list = vec![
+ avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
+ avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
+ ];
div()
.flex()
@@ -70,9 +74,13 @@ impl<V: 'static> TitleBar<V> {
.flex()
.items_center()
.gap_1()
+ .child(text_button("maxbrunsfeld"))
.child(text_button("zed"))
.child(text_button("nate/gpui2-ui-components")),
- ),
+ )
+ .child(follow_group(player_list.clone()).player(0))
+ .child(follow_group(player_list.clone()).player(1))
+ .child(follow_group(player_list.clone()).player(2)),
)
.child(
div()