Mainline GPUI2 UI work (#3062)
This PR mainlines the current state of new GPUI2-based UI from the `gpui2-ui` branch. Release Notes: - N/A --------- Co-authored-by: Nate Butler <iamnbutler@gmail.com> Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Nate <nate@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
e7ee8a95f6
commit
f26ca0866c
85 changed files with 4658 additions and 1623 deletions
|
@ -11,7 +11,9 @@ path = "src/storybook.rs"
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { version = "4.4", features = ["derive", "string"] }
|
||||
chrono = "0.4"
|
||||
gpui2 = { path = "../gpui2" }
|
||||
itertools = "0.11.0"
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
use gpui2::{
|
||||
elements::{div, div::ScrollState, img, svg},
|
||||
style::{StyleHelpers, Styleable},
|
||||
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use ui::{theme, Theme};
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct CollabPanelElement<V: 'static> {
|
||||
view_type: PhantomData<V>,
|
||||
scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
// When I improve child view rendering, I'd like to have V implement a trait that
|
||||
// provides the scroll state, among other things.
|
||||
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
|
||||
CollabPanelElement {
|
||||
view_type: PhantomData,
|
||||
scroll_state,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> CollabPanelElement<V> {
|
||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
// Panel
|
||||
div()
|
||||
.w_64()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.border_color(theme.middle.base.default.border)
|
||||
.border()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
// List Container
|
||||
.child(
|
||||
div()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.border_b()
|
||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
||||
// .group()
|
||||
// List Section Header
|
||||
.child(self.list_section_header("#CRDB", true, &theme))
|
||||
// List Item Large
|
||||
.child(self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
&theme,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CHANNELS", true, &theme)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.py_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.list_section_header("CONTACTS", true, &theme))
|
||||
.children(
|
||||
std::iter::repeat_with(|| {
|
||||
vec![
|
||||
self.list_item(
|
||||
"http://github.com/as-cii.png?s=50",
|
||||
"as-cii",
|
||||
&theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/nathansobo.png?s=50",
|
||||
"nathansobo",
|
||||
&theme,
|
||||
),
|
||||
self.list_item(
|
||||
"http://github.com/maxbrunsfeld.png?s=50",
|
||||
"maxbrunsfeld",
|
||||
&theme,
|
||||
),
|
||||
]
|
||||
})
|
||||
.take(10)
|
||||
.flatten(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.border_t()
|
||||
.border_color(theme.middle.variant.default.border)
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.middle.variant.default.foreground)
|
||||
.child("Find..."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_section_header(
|
||||
&self,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
expanded: bool,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
svg()
|
||||
.path(if expanded {
|
||||
"icons/caret_down.svg"
|
||||
} else {
|
||||
"icons/caret_up.svg"
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_item(
|
||||
&self,
|
||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
theme: &Theme,
|
||||
) -> impl Element<V> {
|
||||
div()
|
||||
.h_7()
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.variant.hovered.background)
|
||||
.active()
|
||||
.fill(theme.lowest.variant.pressed.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
img()
|
||||
.uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod components;
|
||||
pub mod elements;
|
||||
pub mod kitchen_sink;
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
pub mod assistant_panel;
|
||||
pub mod breadcrumb;
|
||||
pub mod buffer;
|
||||
pub mod chat_panel;
|
||||
pub mod collab_panel;
|
||||
pub mod context_menu;
|
||||
pub mod facepile;
|
||||
pub mod keybinding;
|
||||
pub mod palette;
|
||||
pub mod panel;
|
||||
pub mod project_panel;
|
||||
pub mod status_bar;
|
||||
pub mod tab;
|
||||
pub mod tab_bar;
|
||||
pub mod terminal;
|
||||
pub mod title_bar;
|
||||
pub mod toolbar;
|
||||
pub mod traffic_lights;
|
||||
|
|
16
crates/storybook/src/stories/components/assistant_panel.rs
Normal file
16
crates/storybook/src/stories/components/assistant_panel.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::AssistantPanel;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct AssistantPanelStory {}
|
||||
|
||||
impl AssistantPanelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, AssistantPanel<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(AssistantPanel::new())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||
use ui::breadcrumb;
|
||||
use ui::prelude::*;
|
||||
use ui::Breadcrumb;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
|
@ -8,9 +8,9 @@ pub struct BreadcrumbStory {}
|
|||
|
||||
impl BreadcrumbStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container()
|
||||
.child(Story::title_for::<_, ui::Breadcrumb>())
|
||||
.child(Story::label("Default"))
|
||||
.child(breadcrumb())
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Breadcrumb>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Breadcrumb::new())
|
||||
}
|
||||
}
|
||||
|
|
34
crates/storybook/src/stories/components/buffer.rs
Normal file
34
crates/storybook/src/stories/components/buffer.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use gpui2::geometry::rems;
|
||||
use ui::prelude::*;
|
||||
use ui::{
|
||||
empty_buffer_example, hello_world_rust_buffer_example,
|
||||
hello_world_rust_buffer_with_status_example, Buffer,
|
||||
};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct BufferStory {}
|
||||
|
||||
impl BufferStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Buffer<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
|
||||
.child(Story::label(cx, "Hello World (Rust)"))
|
||||
.child(
|
||||
div()
|
||||
.w(rems(64.))
|
||||
.h_96()
|
||||
.child(hello_world_rust_buffer_example(cx)),
|
||||
)
|
||||
.child(Story::label(cx, "Hello World (Rust) with Status"))
|
||||
.child(
|
||||
div()
|
||||
.w(rems(64.))
|
||||
.h_96()
|
||||
.child(hello_world_rust_buffer_with_status_example(cx)),
|
||||
)
|
||||
}
|
||||
}
|
34
crates/storybook/src/stories/components/chat_panel.rs
Normal file
34
crates/storybook/src/stories/components/chat_panel.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use chrono::DateTime;
|
||||
use ui::prelude::*;
|
||||
use ui::{ChatMessage, ChatPanel};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct ChatPanelStory {}
|
||||
|
||||
impl ChatPanelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ChatPanel<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(ChatPanel::new(ScrollState::default()))
|
||||
.child(Story::label(cx, "With Mesages"))
|
||||
.child(ChatPanel::new(ScrollState::default()).with_messages(vec![
|
||||
ChatMessage::new(
|
||||
"osiewicz".to_string(),
|
||||
"is this thing on?".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
ChatMessage::new(
|
||||
"maxdeviant".to_string(),
|
||||
"Reading you loud and clear!".to_string(),
|
||||
DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
|
||||
.unwrap()
|
||||
.naive_local(),
|
||||
),
|
||||
]))
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/collab_panel.rs
Normal file
16
crates/storybook/src/stories/components/collab_panel.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::CollabPanel;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct CollabPanelStory {}
|
||||
|
||||
impl CollabPanelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, CollabPanel<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(CollabPanel::new(ScrollState::default()))
|
||||
}
|
||||
}
|
21
crates/storybook/src/stories/components/context_menu.rs
Normal file
21
crates/storybook/src/stories/components/context_menu.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use ui::prelude::*;
|
||||
use ui::{ContextMenu, ContextMenuItem, Label};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct ContextMenuStory {}
|
||||
|
||||
impl ContextMenuStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
//.fill(theme.middle.base.default.background)
|
||||
.child(Story::title_for::<_, ContextMenu>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(ContextMenu::new([
|
||||
ContextMenuItem::header("Section header"),
|
||||
ContextMenuItem::Separator,
|
||||
ContextMenuItem::entry(Label::new("Some entry")),
|
||||
]))
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
use gpui2::elements::div;
|
||||
use gpui2::style::StyleHelpers;
|
||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||
use ui::prelude::*;
|
||||
use ui::{avatar, facepile, theme};
|
||||
use ui::{static_players, Facepile};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
|
@ -11,40 +8,18 @@ pub struct FacepileStory {}
|
|||
|
||||
impl FacepileStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
let players = static_players();
|
||||
|
||||
let avatars = vec![
|
||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
||||
avatar("https://avatars.githubusercontent.com/u/482957?v=4"),
|
||||
avatar("https://avatars.githubusercontent.com/u/1789?v=4"),
|
||||
];
|
||||
|
||||
Story::container()
|
||||
.child(Story::title_for::<_, ui::Facepile>())
|
||||
.child(Story::label("Default"))
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ui::Facepile>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.child(facepile(avatars.clone().into_iter().take(1)))
|
||||
.child(facepile(avatars.clone().into_iter().take(2)))
|
||||
.child(facepile(avatars.clone().into_iter().take(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))),
|
||||
)
|
||||
.child(Story::label("Rounded rectangle avatars"))
|
||||
.child({
|
||||
let shape = Shape::RoundedRectangle;
|
||||
|
||||
let avatars = avatars
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|avatar| avatar.shape(Shape::RoundedRectangle));
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.child(facepile(avatars.clone().take(1)))
|
||||
.child(facepile(avatars.clone().take(2)))
|
||||
.child(facepile(avatars.clone().take(3)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
64
crates/storybook/src/stories/components/keybinding.rs
Normal file
64
crates/storybook/src/stories/components/keybinding.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use itertools::Itertools;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
use ui::{Keybinding, ModifierKey, ModifierKeys};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct KeybindingStory {}
|
||||
|
||||
impl KeybindingStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let all_modifier_permutations = ModifierKey::iter().permutations(2);
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Keybinding>(cx))
|
||||
.child(Story::label(cx, "Single Key"))
|
||||
.child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
|
||||
.child(Story::label(cx, "Single Key with Modifier"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_3()
|
||||
.children(ModifierKey::iter().map(|modifier| {
|
||||
Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
|
||||
})),
|
||||
)
|
||||
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
|
||||
.child(
|
||||
div().flex().flex_col().children(
|
||||
all_modifier_permutations
|
||||
.chunks(4)
|
||||
.into_iter()
|
||||
.map(|chunk| {
|
||||
div()
|
||||
.flex()
|
||||
.gap_4()
|
||||
.py_3()
|
||||
.children(chunk.map(|permutation| {
|
||||
let mut modifiers = ModifierKeys::new();
|
||||
|
||||
for modifier in permutation {
|
||||
modifiers = modifiers.add(modifier);
|
||||
}
|
||||
|
||||
Keybinding::new("X".to_string(), modifiers)
|
||||
}))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(Story::label(cx, "Single Key with All Modifiers"))
|
||||
.child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
|
||||
.child(Story::label(cx, "Chord"))
|
||||
.child(Keybinding::new_chord(
|
||||
("A".to_string(), ModifierKeys::new()),
|
||||
("Z".to_string(), ModifierKeys::new()),
|
||||
))
|
||||
.child(Story::label(cx, "Chord with Modifier"))
|
||||
.child(Keybinding::new_chord(
|
||||
("A".to_string(), ModifierKeys::new().control(true)),
|
||||
("Z".to_string(), ModifierKeys::new().shift(true)),
|
||||
))
|
||||
}
|
||||
}
|
53
crates/storybook/src/stories/components/palette.rs
Normal file
53
crates/storybook/src/stories/components/palette.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use ui::prelude::*;
|
||||
use ui::{Keybinding, ModifierKeys, Palette, PaletteItem};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct PaletteStory {}
|
||||
|
||||
impl PaletteStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Palette<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Palette::new(ScrollState::default()))
|
||||
.child(Story::label(cx, "With Items"))
|
||||
.child(
|
||||
Palette::new(ScrollState::default())
|
||||
.placeholder("Execute a command...")
|
||||
.items(vec![
|
||||
PaletteItem::new("theme selector: toggle").keybinding(
|
||||
Keybinding::new_chord(
|
||||
("k".to_string(), ModifierKeys::new().command(true)),
|
||||
("t".to_string(), ModifierKeys::new().command(true)),
|
||||
),
|
||||
),
|
||||
PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new(
|
||||
"enter".to_string(),
|
||||
ModifierKeys::new().command(true),
|
||||
)),
|
||||
PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new(
|
||||
">".to_string(),
|
||||
ModifierKeys::new().command(true),
|
||||
)),
|
||||
PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new(
|
||||
"?".to_string(),
|
||||
ModifierKeys::new().command(true),
|
||||
)),
|
||||
PaletteItem::new("auto update: check"),
|
||||
PaletteItem::new("auto update: view release notes"),
|
||||
PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
|
||||
"b".to_string(),
|
||||
ModifierKeys::new().command(true).alt(true),
|
||||
)),
|
||||
PaletteItem::new("chat panel: toggle focus"),
|
||||
PaletteItem::new("cli: install"),
|
||||
PaletteItem::new("client: sign in"),
|
||||
PaletteItem::new("client: sign out"),
|
||||
PaletteItem::new("editor: cancel")
|
||||
.keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
24
crates/storybook/src/stories/components/panel.rs
Normal file
24
crates/storybook/src/stories/components/panel.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use ui::prelude::*;
|
||||
use ui::{Label, Panel};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct PanelStory {}
|
||||
|
||||
impl PanelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Panel<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Panel::new(
|
||||
ScrollState::default(),
|
||||
|_, _| {
|
||||
(0..100)
|
||||
.map(|ix| Label::new(format!("Item {}", ix + 1)).into_any())
|
||||
.collect()
|
||||
},
|
||||
Box::new(()),
|
||||
))
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/project_panel.rs
Normal file
16
crates/storybook/src/stories/components/project_panel.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::ProjectPanel;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct ProjectPanelStory {}
|
||||
|
||||
impl ProjectPanelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ProjectPanel<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(ProjectPanel::new(ScrollState::default()))
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/status_bar.rs
Normal file
16
crates/storybook/src/stories/components/status_bar.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::StatusBar;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct StatusBarStory {}
|
||||
|
||||
impl StatusBarStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, StatusBar<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(StatusBar::new())
|
||||
}
|
||||
}
|
91
crates/storybook/src/stories/components/tab.rs
Normal file
91
crates/storybook/src/stories/components/tab.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Tab};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct TabStory {}
|
||||
|
||||
impl TabStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let git_statuses = GitStatus::iter();
|
||||
let fs_statuses = FileSystemStatus::iter();
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Tab>(cx))
|
||||
.child(
|
||||
h_stack().child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Tab::new()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_stack().child(
|
||||
v_stack().gap_2().child(Story::label(cx, "Current")).child(
|
||||
h_stack()
|
||||
.gap_4()
|
||||
.child(Tab::new().title("Current".to_string()).current(true))
|
||||
.child(Tab::new().title("Not Current".to_string()).current(false)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_stack().child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "Titled"))
|
||||
.child(Tab::new().title("label".to_string())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_stack().child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "With Icon"))
|
||||
.child(
|
||||
Tab::new()
|
||||
.title("label".to_string())
|
||||
.icon(Some(ui::Icon::Envelope)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_stack().child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "Close Side"))
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_4()
|
||||
.child(
|
||||
Tab::new()
|
||||
.title("Left".to_string())
|
||||
.close_side(IconSide::Left),
|
||||
)
|
||||
.child(Tab::new().title("Right".to_string())),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "Git Status"))
|
||||
.child(h_stack().gap_4().children(git_statuses.map(|git_status| {
|
||||
Tab::new()
|
||||
.title(git_status.to_string())
|
||||
.git_status(git_status)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
v_stack()
|
||||
.gap_2()
|
||||
.child(Story::label(cx, "File System Status"))
|
||||
.child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
|
||||
Tab::new().title(fs_status.to_string()).fs_status(fs_status)
|
||||
}))),
|
||||
)
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/tab_bar.rs
Normal file
16
crates/storybook/src/stories/components/tab_bar.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::TabBar;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct TabBarStory {}
|
||||
|
||||
impl TabBarStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TabBar<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(TabBar::new(ScrollState::default()))
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/terminal.rs
Normal file
16
crates/storybook/src/stories/components/terminal.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::Terminal;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct TerminalStory {}
|
||||
|
||||
impl TerminalStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Terminal>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Terminal::new())
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/components/title_bar.rs
Normal file
16
crates/storybook/src/stories/components/title_bar.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::TitleBar;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct TitleBarStory {}
|
||||
|
||||
impl TitleBarStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TitleBar<V>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(TitleBar::new(cx))
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||
use ui::toolbar;
|
||||
use ui::prelude::*;
|
||||
use ui::Toolbar;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
|
@ -8,9 +8,9 @@ pub struct ToolbarStory {}
|
|||
|
||||
impl ToolbarStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container()
|
||||
.child(Story::title_for::<_, ui::Toolbar>())
|
||||
.child(Story::label("Default"))
|
||||
.child(toolbar())
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Toolbar>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Toolbar::new())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||
use ui::{theme, traffic_lights};
|
||||
use ui::prelude::*;
|
||||
use ui::TrafficLights;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
|
@ -8,11 +8,11 @@ pub struct TrafficLightsStory {}
|
|||
|
||||
impl TrafficLightsStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
Story::container()
|
||||
.child(Story::title_for::<_, ui::TrafficLights>())
|
||||
.child(Story::label("Default"))
|
||||
.child(traffic_lights())
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TrafficLights>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(TrafficLights::new())
|
||||
.child(Story::label(cx, "Unfocused"))
|
||||
.child(TrafficLights::new().window_has_focus(false))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
pub mod avatar;
|
||||
pub mod button;
|
||||
pub mod icon;
|
||||
pub mod input;
|
||||
pub mod label;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||
use ui::prelude::*;
|
||||
use ui::{avatar, theme};
|
||||
use ui::Avatar;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
|
@ -9,17 +8,15 @@ pub struct AvatarStory {}
|
|||
|
||||
impl AvatarStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
Story::container()
|
||||
.child(Story::title_for::<_, ui::Avatar>())
|
||||
.child(Story::label("Default"))
|
||||
.child(avatar(
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ui::Avatar>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Avatar::new(
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
))
|
||||
.child(Story::label("Rounded rectangle"))
|
||||
.child(Story::label(cx, "Rounded rectangle"))
|
||||
.child(
|
||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.shape(Shape::RoundedRectangle),
|
||||
)
|
||||
}
|
||||
|
|
192
crates/storybook/src/stories/elements/button.rs
Normal file
192
crates/storybook/src/stories/elements/button.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use gpui2::elements::div;
|
||||
use gpui2::geometry::rems;
|
||||
use gpui2::{Element, IntoElement, ViewContext};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Button, Icon, IconPosition, Label};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct ButtonStory {}
|
||||
|
||||
impl ButtonStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let states = InteractionState::iter();
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Button<V>>(cx))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_8()
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Ghost (Default)"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Ghost – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left)
|
||||
.state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Ghost – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right)
|
||||
.state(state),
|
||||
)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Filled"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Filled – Left Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left)
|
||||
.state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Filled – Right Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right)
|
||||
.state(state),
|
||||
)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label(cx, "Fixed With"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.state(state)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Fixed With – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label(cx, "Fixed With – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new(state.to_string())
|
||||
.color(ui::LabelColor::Muted)
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
}))),
|
||||
),
|
||||
)
|
||||
.child(Story::label(cx, "Button with `on_click`"))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
// NOTE: There currently appears to be a bug in GPUI2 where only the last event handler will fire.
|
||||
// So adding additional buttons with `on_click`s after this one will cause this `on_click` to not fire.
|
||||
.on_click(|_view, _cx| println!("Button clicked.")),
|
||||
)
|
||||
}
|
||||
}
|
19
crates/storybook/src/stories/elements/icon.rs
Normal file
19
crates/storybook/src/stories/elements/icon.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
use ui::{Icon, IconElement};
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct IconStory {}
|
||||
|
||||
impl IconStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let icons = Icon::iter();
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ui::IconElement>(cx))
|
||||
.child(Story::label(cx, "All Icons"))
|
||||
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
|
||||
}
|
||||
}
|
16
crates/storybook/src/stories/elements/input.rs
Normal file
16
crates/storybook/src/stories/elements/input.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use ui::prelude::*;
|
||||
use ui::Input;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct InputStory {}
|
||||
|
||||
impl InputStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Input>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(div().flex().child(Input::new("Search")))
|
||||
}
|
||||
}
|
18
crates/storybook/src/stories/elements/label.rs
Normal file
18
crates/storybook/src/stories/elements/label.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use ui::prelude::*;
|
||||
use ui::Label;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct LabelStory {}
|
||||
|
||||
impl LabelStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Label>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
.child(Label::new("Hello, world!"))
|
||||
.child(Story::label(cx, "Highlighted"))
|
||||
.child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12]))
|
||||
}
|
||||
}
|
46
crates/storybook/src/stories/kitchen_sink.rs
Normal file
46
crates/storybook/src/stories/kitchen_sink.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use ui::prelude::*;
|
||||
|
||||
use crate::story::Story;
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct KitchenSinkStory {}
|
||||
|
||||
impl KitchenSinkStory {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
Story::container(cx)
|
||||
.child(Story::title(cx, "Kitchen Sink"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll(ScrollState::default())
|
||||
.child(crate::stories::elements::avatar::AvatarStory::default())
|
||||
.child(crate::stories::elements::button::ButtonStory::default())
|
||||
.child(crate::stories::elements::icon::IconStory::default())
|
||||
.child(crate::stories::elements::input::InputStory::default())
|
||||
.child(crate::stories::elements::label::LabelStory::default())
|
||||
.child(
|
||||
crate::stories::components::assistant_panel::AssistantPanelStory::default(),
|
||||
)
|
||||
.child(crate::stories::components::breadcrumb::BreadcrumbStory::default())
|
||||
.child(crate::stories::components::buffer::BufferStory::default())
|
||||
.child(crate::stories::components::chat_panel::ChatPanelStory::default())
|
||||
.child(crate::stories::components::collab_panel::CollabPanelStory::default())
|
||||
.child(crate::stories::components::facepile::FacepileStory::default())
|
||||
.child(crate::stories::components::keybinding::KeybindingStory::default())
|
||||
.child(crate::stories::components::palette::PaletteStory::default())
|
||||
.child(crate::stories::components::panel::PanelStory::default())
|
||||
.child(crate::stories::components::project_panel::ProjectPanelStory::default())
|
||||
.child(crate::stories::components::status_bar::StatusBarStory::default())
|
||||
.child(crate::stories::components::tab::TabStory::default())
|
||||
.child(crate::stories::components::tab_bar::TabBarStory::default())
|
||||
.child(crate::stories::components::terminal::TerminalStory::default())
|
||||
.child(crate::stories::components::title_bar::TitleBarStory::default())
|
||||
.child(crate::stories::components::toolbar::ToolbarStory::default())
|
||||
.child(
|
||||
crate::stories::components::traffic_lights::TrafficLightsStory::default(),
|
||||
)
|
||||
.child(crate::stories::components::context_menu::ContextMenuStory::default()),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
use gpui2::elements::div;
|
||||
use gpui2::style::StyleHelpers;
|
||||
use gpui2::{rgb, Element, Hsla, ParentElement};
|
||||
use gpui2::elements::div::Div;
|
||||
use ui::prelude::*;
|
||||
use ui::theme;
|
||||
|
||||
pub struct Story {}
|
||||
|
||||
impl Story {
|
||||
pub fn container<V: 'static>() -> div::Div<V> {
|
||||
pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
|
@ -13,26 +15,30 @@ impl Story {
|
|||
.pt_2()
|
||||
.px_4()
|
||||
.font("Zed Mono Extended")
|
||||
.fill(rgb::<Hsla>(0x282c34))
|
||||
.fill(theme.lowest.base.default.background)
|
||||
}
|
||||
|
||||
pub fn title<V: 'static>(title: &str) -> impl Element<V> {
|
||||
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Element<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.text_xl()
|
||||
.text_color(rgb::<Hsla>(0xffffff))
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.child(title.to_owned())
|
||||
}
|
||||
|
||||
pub fn title_for<V: 'static, T>() -> impl Element<V> {
|
||||
Self::title(std::any::type_name::<T>())
|
||||
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||
Self::title(cx, std::any::type_name::<T>())
|
||||
}
|
||||
|
||||
pub fn label<V: 'static>(label: &str) -> impl Element<V> {
|
||||
pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Element<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.mt_4()
|
||||
.mb_2()
|
||||
.text_xs()
|
||||
.text_color(rgb::<Hsla>(0xffffff))
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.child(label.to_owned())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,97 @@
|
|||
use std::{str::FromStr, sync::OnceLock};
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use clap::builder::PossibleValue;
|
||||
use clap::ValueEnum;
|
||||
use gpui2::{AnyElement, Element};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
||||
|
||||
#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ElementStory {
|
||||
Avatar,
|
||||
Button,
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
impl ElementStory {
|
||||
pub fn story<V: 'static>(&self) -> AnyElement<V> {
|
||||
use crate::stories::elements;
|
||||
|
||||
match self {
|
||||
Self::Avatar => elements::avatar::AvatarStory::default().into_any(),
|
||||
Self::Button => elements::button::ButtonStory::default().into_any(),
|
||||
Self::Icon => elements::icon::IconStory::default().into_any(),
|
||||
Self::Input => elements::input::InputStory::default().into_any(),
|
||||
Self::Label => elements::label::LabelStory::default().into_any(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ComponentStory {
|
||||
AssistantPanel,
|
||||
Breadcrumb,
|
||||
Buffer,
|
||||
ContextMenu,
|
||||
ChatPanel,
|
||||
CollabPanel,
|
||||
Facepile,
|
||||
Keybinding,
|
||||
Palette,
|
||||
Panel,
|
||||
ProjectPanel,
|
||||
StatusBar,
|
||||
Tab,
|
||||
TabBar,
|
||||
Terminal,
|
||||
TitleBar,
|
||||
Toolbar,
|
||||
TrafficLights,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
impl ComponentStory {
|
||||
pub fn story<V: 'static>(&self) -> AnyElement<V> {
|
||||
use crate::stories::components;
|
||||
|
||||
match self {
|
||||
Self::AssistantPanel => {
|
||||
components::assistant_panel::AssistantPanelStory::default().into_any()
|
||||
}
|
||||
Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::default().into_any(),
|
||||
Self::Buffer => components::buffer::BufferStory::default().into_any(),
|
||||
Self::ContextMenu => components::context_menu::ContextMenuStory::default().into_any(),
|
||||
Self::ChatPanel => components::chat_panel::ChatPanelStory::default().into_any(),
|
||||
Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(),
|
||||
Self::Facepile => components::facepile::FacepileStory::default().into_any(),
|
||||
Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(),
|
||||
Self::Palette => components::palette::PaletteStory::default().into_any(),
|
||||
Self::Panel => components::panel::PanelStory::default().into_any(),
|
||||
Self::ProjectPanel => {
|
||||
components::project_panel::ProjectPanelStory::default().into_any()
|
||||
}
|
||||
Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(),
|
||||
Self::Tab => components::tab::TabStory::default().into_any(),
|
||||
Self::TabBar => components::tab_bar::TabBarStory::default().into_any(),
|
||||
Self::Terminal => components::terminal::TerminalStory::default().into_any(),
|
||||
Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(),
|
||||
Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(),
|
||||
Self::TrafficLights => {
|
||||
components::traffic_lights::TrafficLightsStory::default().into_any()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum StorySelector {
|
||||
Element(ElementStory),
|
||||
Component(ComponentStory),
|
||||
KitchenSink,
|
||||
}
|
||||
|
||||
impl FromStr for StorySelector {
|
||||
|
@ -32,6 +100,10 @@ impl FromStr for StorySelector {
|
|||
fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let story = raw_story_name.to_ascii_lowercase();
|
||||
|
||||
if story == "kitchen_sink" {
|
||||
return Ok(Self::KitchenSink);
|
||||
}
|
||||
|
||||
if let Some((_, story)) = story.split_once("elements/") {
|
||||
let element_story = ElementStory::from_str(story)
|
||||
.with_context(|| format!("story not found for element '{story}'"))?;
|
||||
|
@ -50,25 +122,49 @@ impl FromStr for StorySelector {
|
|||
}
|
||||
}
|
||||
|
||||
impl StorySelector {
|
||||
pub fn story<V: 'static>(&self) -> Vec<AnyElement<V>> {
|
||||
match self {
|
||||
Self::Element(element_story) => vec![element_story.story()],
|
||||
Self::Component(component_story) => vec![component_story.story()],
|
||||
Self::KitchenSink => all_story_selectors()
|
||||
.into_iter()
|
||||
// Exclude the kitchen sink to prevent `story` from recursively
|
||||
// calling itself for all eternity.
|
||||
.filter(|selector| **selector != Self::KitchenSink)
|
||||
.flat_map(|selector| selector.story())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of all stories available in the storybook.
|
||||
static ALL_STORIES: OnceLock<Vec<StorySelector>> = OnceLock::new();
|
||||
static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new();
|
||||
|
||||
fn all_story_selectors<'a>() -> &'a [StorySelector] {
|
||||
let stories = ALL_STORY_SELECTORS.get_or_init(|| {
|
||||
let element_stories = ElementStory::iter().map(StorySelector::Element);
|
||||
let component_stories = ComponentStory::iter().map(StorySelector::Component);
|
||||
|
||||
element_stories
|
||||
.chain(component_stories)
|
||||
.chain(std::iter::once(StorySelector::KitchenSink))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
stories
|
||||
}
|
||||
|
||||
impl ValueEnum for StorySelector {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
let stories = ALL_STORIES.get_or_init(|| {
|
||||
let element_stories = ElementStory::iter().map(Self::Element);
|
||||
let component_stories = ComponentStory::iter().map(Self::Component);
|
||||
|
||||
element_stories.chain(component_stories).collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
stories
|
||||
all_story_selectors()
|
||||
}
|
||||
|
||||
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
||||
let value = match self {
|
||||
Self::Element(story) => format!("elements/{story}"),
|
||||
Self::Component(story) => format!("components/{story}"),
|
||||
Self::KitchenSink => "kitchen_sink".to_string(),
|
||||
};
|
||||
|
||||
Some(PossibleValue::new(value))
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
mod collab_panel;
|
||||
mod stories;
|
||||
mod story;
|
||||
mod story_selector;
|
||||
mod workspace;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::theme as legacy_theme;
|
||||
use clap::Parser;
|
||||
use gpui2::{serde_json, vec2f, view, Element, IntoElement, RectF, ViewContext, WindowBounds};
|
||||
use legacy_theme::ThemeSettings;
|
||||
use gpui2::{
|
||||
serde_json, vec2f, view, Element, IntoElement, ParentElement, RectF, ViewContext, WindowBounds,
|
||||
};
|
||||
use legacy_theme::{ThemeRegistry, ThemeSettings};
|
||||
use log::LevelFilter;
|
||||
use settings::{default_settings, SettingsStore};
|
||||
use simplelog::SimpleLogger;
|
||||
use stories::components::breadcrumb::BreadcrumbStory;
|
||||
use stories::components::facepile::FacepileStory;
|
||||
use stories::components::toolbar::ToolbarStory;
|
||||
use stories::components::traffic_lights::TrafficLightsStory;
|
||||
use stories::elements::avatar::AvatarStory;
|
||||
use ui::{ElementExt, Theme};
|
||||
use ui::prelude::*;
|
||||
use ui::{ElementExt, Theme, WorkspaceElement};
|
||||
|
||||
use crate::story_selector::{ComponentStory, ElementStory, StorySelector};
|
||||
use crate::story_selector::StorySelector;
|
||||
|
||||
gpui2::actions! {
|
||||
storybook,
|
||||
|
@ -32,6 +30,12 @@ gpui2::actions! {
|
|||
struct Args {
|
||||
#[arg(value_enum)]
|
||||
story: Option<StorySelector>,
|
||||
|
||||
/// The name of the theme to use in the storybook.
|
||||
///
|
||||
/// If not provided, the default theme will be used.
|
||||
#[arg(long)]
|
||||
theme: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -48,31 +52,60 @@ fn main() {
|
|||
legacy_theme::init(Assets, cx);
|
||||
// load_embedded_fonts(cx.platform().as_ref());
|
||||
|
||||
let theme_registry = cx.global::<Arc<ThemeRegistry>>();
|
||||
|
||||
let theme_override = args
|
||||
.theme
|
||||
.and_then(|theme| {
|
||||
theme_registry
|
||||
.list_names(true)
|
||||
.find(|known_theme| theme == *known_theme)
|
||||
})
|
||||
.and_then(|theme_name| theme_registry.get(&theme_name).ok());
|
||||
|
||||
cx.add_window(
|
||||
gpui2::WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1600., 900.))),
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))),
|
||||
center: true,
|
||||
..Default::default()
|
||||
},
|
||||
|cx| match args.story {
|
||||
Some(StorySelector::Element(ElementStory::Avatar)) => {
|
||||
view(|cx| render_story(&mut ViewContext::new(cx), AvatarStory::default()))
|
||||
}
|
||||
Some(StorySelector::Component(ComponentStory::Breadcrumb)) => {
|
||||
view(|cx| render_story(&mut ViewContext::new(cx), BreadcrumbStory::default()))
|
||||
}
|
||||
Some(StorySelector::Component(ComponentStory::Facepile)) => {
|
||||
view(|cx| render_story(&mut ViewContext::new(cx), FacepileStory::default()))
|
||||
}
|
||||
Some(StorySelector::Component(ComponentStory::Toolbar)) => {
|
||||
view(|cx| render_story(&mut ViewContext::new(cx), ToolbarStory::default()))
|
||||
}
|
||||
Some(StorySelector::Component(ComponentStory::TrafficLights)) => view(|cx| {
|
||||
render_story(&mut ViewContext::new(cx), TrafficLightsStory::default())
|
||||
// HACK: Special-case the kitchen sink to fix scrolling.
|
||||
// There is something about going through `children_any` that messes
|
||||
// with the scroll interactions.
|
||||
Some(StorySelector::KitchenSink) => view(move |cx| {
|
||||
render_story(
|
||||
&mut ViewContext::new(cx),
|
||||
theme_override.clone(),
|
||||
crate::stories::kitchen_sink::KitchenSinkStory::default(),
|
||||
)
|
||||
}),
|
||||
None => {
|
||||
view(|cx| render_story(&mut ViewContext::new(cx), WorkspaceElement::default()))
|
||||
// HACK: Special-case the panel story to fix scrolling.
|
||||
// There is something about going through `children_any` that messes
|
||||
// with the scroll interactions.
|
||||
Some(StorySelector::Component(story_selector::ComponentStory::Panel)) => {
|
||||
view(move |cx| {
|
||||
render_story(
|
||||
&mut ViewContext::new(cx),
|
||||
theme_override.clone(),
|
||||
crate::stories::components::panel::PanelStory::default(),
|
||||
)
|
||||
})
|
||||
}
|
||||
Some(selector) => view(move |cx| {
|
||||
render_story(
|
||||
&mut ViewContext::new(cx),
|
||||
theme_override.clone(),
|
||||
div().children_any(selector.story()),
|
||||
)
|
||||
}),
|
||||
None => view(move |cx| {
|
||||
render_story(
|
||||
&mut ViewContext::new(cx),
|
||||
theme_override.clone(),
|
||||
WorkspaceElement::default(),
|
||||
)
|
||||
}),
|
||||
},
|
||||
);
|
||||
cx.platform().activate(true);
|
||||
|
@ -81,23 +114,32 @@ fn main() {
|
|||
|
||||
fn render_story<V: 'static, S: IntoElement<V>>(
|
||||
cx: &mut ViewContext<V>,
|
||||
theme_override: Option<Arc<legacy_theme::Theme>>,
|
||||
story: S,
|
||||
) -> impl Element<V> {
|
||||
story.into_element().themed(current_theme(cx))
|
||||
let theme = current_theme(cx, theme_override);
|
||||
|
||||
story.into_element().themed(theme)
|
||||
}
|
||||
|
||||
fn current_theme<V: 'static>(
|
||||
cx: &mut ViewContext<V>,
|
||||
theme_override: Option<Arc<legacy_theme::Theme>>,
|
||||
) -> Theme {
|
||||
let legacy_theme =
|
||||
theme_override.unwrap_or_else(|| settings::get::<ThemeSettings>(cx).theme.clone());
|
||||
|
||||
let new_theme: Theme = serde_json::from_value(legacy_theme.base_theme.clone()).unwrap();
|
||||
|
||||
add_base_theme_to_legacy_theme(&legacy_theme, new_theme)
|
||||
}
|
||||
|
||||
// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
|
||||
fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
|
||||
settings::get::<ThemeSettings>(cx)
|
||||
.theme
|
||||
fn add_base_theme_to_legacy_theme(legacy_theme: &legacy_theme::Theme, new_theme: Theme) -> Theme {
|
||||
legacy_theme
|
||||
.deserialized_base_theme
|
||||
.lock()
|
||||
.get_or_insert_with(|| {
|
||||
let theme: Theme =
|
||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
||||
.unwrap();
|
||||
Box::new(theme)
|
||||
})
|
||||
.get_or_insert_with(|| Box::new(new_theme))
|
||||
.downcast_ref::<Theme>()
|
||||
.unwrap()
|
||||
.clone()
|
||||
|
@ -106,7 +148,6 @@ fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
|
|||
use anyhow::{anyhow, Result};
|
||||
use gpui2::AssetSource;
|
||||
use rust_embed::RustEmbed;
|
||||
use workspace::WorkspaceElement;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
use gpui2::{
|
||||
elements::{div, div::ScrollState},
|
||||
style::StyleHelpers,
|
||||
Element, IntoElement, ParentElement, ViewContext,
|
||||
};
|
||||
use ui::{chat_panel, project_panel, status_bar, tab_bar, theme, title_bar, toolbar};
|
||||
|
||||
#[derive(Element, Default)]
|
||||
pub struct WorkspaceElement {
|
||||
left_scroll_state: ScrollState,
|
||||
right_scroll_state: ScrollState,
|
||||
tab_bar_scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
impl WorkspaceElement {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font("Zed Sans Extended")
|
||||
.gap_0()
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.child(title_bar())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.overflow_hidden()
|
||||
.child(project_panel(self.left_scroll_state.clone()))
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.fill(theme.highest.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.flex_1()
|
||||
.child(tab_bar(self.tab_bar_scroll_state.clone()))
|
||||
.child(toolbar()),
|
||||
),
|
||||
)
|
||||
.child(chat_panel(self.right_scroll_state.clone())),
|
||||
)
|
||||
.child(status_bar())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue