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
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -7398,8 +7398,10 @@ name = "storybook"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"clap 4.4.4",
|
"clap 4.4.4",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -8631,9 +8633,12 @@ name = "ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
|
"smallvec",
|
||||||
|
"strum",
|
||||||
"theme",
|
"theme",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,31 @@ pub trait ParentElement<V: 'static> {
|
||||||
);
|
);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: This is a temporary hack to get children working for the purposes
|
||||||
|
// of building UI on top of the current version of gpui2.
|
||||||
|
//
|
||||||
|
// We'll (hopefully) be moving away from this in the future.
|
||||||
|
fn children_any<I>(mut self, children: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = AnyElement<V>>,
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.children_mut().extend(children.into_iter());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: This is a temporary hack to get children working for the purposes
|
||||||
|
// of building UI on top of the current version of gpui2.
|
||||||
|
//
|
||||||
|
// We'll (hopefully) be moving away from this in the future.
|
||||||
|
fn child_any(mut self, children: AnyElement<V>) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.children_mut().push(children);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoElement<V: 'static> {
|
pub trait IntoElement<V: 'static> {
|
||||||
|
|
|
@ -11,7 +11,9 @@ path = "src/storybook.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap = { version = "4.4", features = ["derive", "string"] }
|
clap = { version = "4.4", features = ["derive", "string"] }
|
||||||
|
chrono = "0.4"
|
||||||
gpui2 = { path = "../gpui2" }
|
gpui2 = { path = "../gpui2" }
|
||||||
|
itertools = "0.11.0"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
rust-embed.workspace = true
|
rust-embed.workspace = true
|
||||||
serde.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 components;
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
|
pub mod kitchen_sink;
|
||||||
|
|
|
@ -1,4 +1,18 @@
|
||||||
|
pub mod assistant_panel;
|
||||||
pub mod breadcrumb;
|
pub mod breadcrumb;
|
||||||
|
pub mod buffer;
|
||||||
|
pub mod chat_panel;
|
||||||
|
pub mod collab_panel;
|
||||||
|
pub mod context_menu;
|
||||||
pub mod facepile;
|
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 toolbar;
|
||||||
pub mod traffic_lights;
|
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::prelude::*;
|
||||||
use ui::breadcrumb;
|
use ui::Breadcrumb;
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ pub struct BreadcrumbStory {}
|
||||||
|
|
||||||
impl BreadcrumbStory {
|
impl BreadcrumbStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
Story::container()
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ui::Breadcrumb>())
|
.child(Story::title_for::<_, Breadcrumb>(cx))
|
||||||
.child(Story::label("Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(breadcrumb())
|
.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::prelude::*;
|
||||||
use ui::{avatar, facepile, theme};
|
use ui::{static_players, Facepile};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -11,40 +8,18 @@ pub struct FacepileStory {}
|
||||||
|
|
||||||
impl FacepileStory {
|
impl FacepileStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
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![
|
Story::container(cx)
|
||||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
.child(Story::title_for::<_, ui::Facepile>(cx))
|
||||||
avatar("https://avatars.githubusercontent.com/u/482957?v=4"),
|
.child(Story::label(cx, "Default"))
|
||||||
avatar("https://avatars.githubusercontent.com/u/1789?v=4"),
|
|
||||||
];
|
|
||||||
|
|
||||||
Story::container()
|
|
||||||
.child(Story::title_for::<_, ui::Facepile>())
|
|
||||||
.child(Story::label("Default"))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(facepile(avatars.clone().into_iter().take(1)))
|
.child(Facepile::new(players.clone().into_iter().take(1)))
|
||||||
.child(facepile(avatars.clone().into_iter().take(2)))
|
.child(Facepile::new(players.clone().into_iter().take(2)))
|
||||||
.child(facepile(avatars.clone().into_iter().take(3))),
|
.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::prelude::*;
|
||||||
use ui::toolbar;
|
use ui::Toolbar;
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ pub struct ToolbarStory {}
|
||||||
|
|
||||||
impl ToolbarStory {
|
impl ToolbarStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
Story::container()
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ui::Toolbar>())
|
.child(Story::title_for::<_, Toolbar>(cx))
|
||||||
.child(Story::label("Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(toolbar())
|
.child(Toolbar::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
use ui::prelude::*;
|
||||||
use ui::{theme, traffic_lights};
|
use ui::TrafficLights;
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@ pub struct TrafficLightsStory {}
|
||||||
|
|
||||||
impl TrafficLightsStory {
|
impl TrafficLightsStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
Story::container(cx)
|
||||||
|
.child(Story::title_for::<_, TrafficLights>(cx))
|
||||||
Story::container()
|
.child(Story::label(cx, "Default"))
|
||||||
.child(Story::title_for::<_, ui::TrafficLights>())
|
.child(TrafficLights::new())
|
||||||
.child(Story::label("Default"))
|
.child(Story::label(cx, "Unfocused"))
|
||||||
.child(traffic_lights())
|
.child(TrafficLights::new().window_has_focus(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
pub mod avatar;
|
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::prelude::*;
|
||||||
use ui::{avatar, theme};
|
use ui::Avatar;
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -9,17 +8,15 @@ pub struct AvatarStory {}
|
||||||
|
|
||||||
impl AvatarStory {
|
impl AvatarStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
Story::container(cx)
|
||||||
|
.child(Story::title_for::<_, ui::Avatar>(cx))
|
||||||
Story::container()
|
.child(Story::label(cx, "Default"))
|
||||||
.child(Story::title_for::<_, ui::Avatar>())
|
.child(Avatar::new(
|
||||||
.child(Story::label("Default"))
|
|
||||||
.child(avatar(
|
|
||||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||||
))
|
))
|
||||||
.child(Story::label("Rounded rectangle"))
|
.child(Story::label(cx, "Rounded rectangle"))
|
||||||
.child(
|
.child(
|
||||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
|
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||||
.shape(Shape::RoundedRectangle),
|
.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::elements::div::Div;
|
||||||
use gpui2::style::StyleHelpers;
|
use ui::prelude::*;
|
||||||
use gpui2::{rgb, Element, Hsla, ParentElement};
|
use ui::theme;
|
||||||
|
|
||||||
pub struct Story {}
|
pub struct Story {}
|
||||||
|
|
||||||
impl 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()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -13,26 +15,30 @@ impl Story {
|
||||||
.pt_2()
|
.pt_2()
|
||||||
.px_4()
|
.px_4()
|
||||||
.font("Zed Mono Extended")
|
.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()
|
div()
|
||||||
.text_xl()
|
.text_xl()
|
||||||
.text_color(rgb::<Hsla>(0xffffff))
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
.child(title.to_owned())
|
.child(title.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title_for<V: 'static, T>() -> impl Element<V> {
|
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||||
Self::title(std::any::type_name::<T>())
|
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()
|
div()
|
||||||
.mt_4()
|
.mt_4()
|
||||||
.mb_2()
|
.mb_2()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(rgb::<Hsla>(0xffffff))
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
.child(label.to_owned())
|
.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 anyhow::{anyhow, Context};
|
||||||
use clap::builder::PossibleValue;
|
use clap::builder::PossibleValue;
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
use gpui2::{AnyElement, Element};
|
||||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
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")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ElementStory {
|
pub enum ElementStory {
|
||||||
Avatar,
|
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")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ComponentStory {
|
pub enum ComponentStory {
|
||||||
|
AssistantPanel,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
|
Buffer,
|
||||||
|
ContextMenu,
|
||||||
|
ChatPanel,
|
||||||
|
CollabPanel,
|
||||||
Facepile,
|
Facepile,
|
||||||
|
Keybinding,
|
||||||
|
Palette,
|
||||||
|
Panel,
|
||||||
|
ProjectPanel,
|
||||||
|
StatusBar,
|
||||||
|
Tab,
|
||||||
|
TabBar,
|
||||||
|
Terminal,
|
||||||
|
TitleBar,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
TrafficLights,
|
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 {
|
pub enum StorySelector {
|
||||||
Element(ElementStory),
|
Element(ElementStory),
|
||||||
Component(ComponentStory),
|
Component(ComponentStory),
|
||||||
|
KitchenSink,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for StorySelector {
|
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> {
|
fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
let story = raw_story_name.to_ascii_lowercase();
|
let story = raw_story_name.to_ascii_lowercase();
|
||||||
|
|
||||||
|
if story == "kitchen_sink" {
|
||||||
|
return Ok(Self::KitchenSink);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((_, story)) = story.split_once("elements/") {
|
if let Some((_, story)) = story.split_once("elements/") {
|
||||||
let element_story = ElementStory::from_str(story)
|
let element_story = ElementStory::from_str(story)
|
||||||
.with_context(|| format!("story not found for element '{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.
|
/// 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 {
|
impl ValueEnum for StorySelector {
|
||||||
fn value_variants<'a>() -> &'a [Self] {
|
fn value_variants<'a>() -> &'a [Self] {
|
||||||
let stories = ALL_STORIES.get_or_init(|| {
|
all_story_selectors()
|
||||||
let element_stories = ElementStory::iter().map(Self::Element);
|
|
||||||
let component_stories = ComponentStory::iter().map(Self::Component);
|
|
||||||
|
|
||||||
element_stories.chain(component_stories).collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
stories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
||||||
let value = match self {
|
let value = match self {
|
||||||
Self::Element(story) => format!("elements/{story}"),
|
Self::Element(story) => format!("elements/{story}"),
|
||||||
Self::Component(story) => format!("components/{story}"),
|
Self::Component(story) => format!("components/{story}"),
|
||||||
|
Self::KitchenSink => "kitchen_sink".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(PossibleValue::new(value))
|
Some(PossibleValue::new(value))
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
mod collab_panel;
|
|
||||||
mod stories;
|
mod stories;
|
||||||
mod story;
|
mod story;
|
||||||
mod story_selector;
|
mod story_selector;
|
||||||
mod workspace;
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ::theme as legacy_theme;
|
use ::theme as legacy_theme;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use gpui2::{serde_json, vec2f, view, Element, IntoElement, RectF, ViewContext, WindowBounds};
|
use gpui2::{
|
||||||
use legacy_theme::ThemeSettings;
|
serde_json, vec2f, view, Element, IntoElement, ParentElement, RectF, ViewContext, WindowBounds,
|
||||||
|
};
|
||||||
|
use legacy_theme::{ThemeRegistry, ThemeSettings};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use settings::{default_settings, SettingsStore};
|
use settings::{default_settings, SettingsStore};
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
use stories::components::breadcrumb::BreadcrumbStory;
|
use ui::prelude::*;
|
||||||
use stories::components::facepile::FacepileStory;
|
use ui::{ElementExt, Theme, WorkspaceElement};
|
||||||
use stories::components::toolbar::ToolbarStory;
|
|
||||||
use stories::components::traffic_lights::TrafficLightsStory;
|
|
||||||
use stories::elements::avatar::AvatarStory;
|
|
||||||
use ui::{ElementExt, Theme};
|
|
||||||
|
|
||||||
use crate::story_selector::{ComponentStory, ElementStory, StorySelector};
|
use crate::story_selector::StorySelector;
|
||||||
|
|
||||||
gpui2::actions! {
|
gpui2::actions! {
|
||||||
storybook,
|
storybook,
|
||||||
|
@ -32,6 +30,12 @@ gpui2::actions! {
|
||||||
struct Args {
|
struct Args {
|
||||||
#[arg(value_enum)]
|
#[arg(value_enum)]
|
||||||
story: Option<StorySelector>,
|
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() {
|
fn main() {
|
||||||
|
@ -48,31 +52,60 @@ fn main() {
|
||||||
legacy_theme::init(Assets, cx);
|
legacy_theme::init(Assets, cx);
|
||||||
// load_embedded_fonts(cx.platform().as_ref());
|
// 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(
|
cx.add_window(
|
||||||
gpui2::WindowOptions {
|
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,
|
center: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|cx| match args.story {
|
|cx| match args.story {
|
||||||
Some(StorySelector::Element(ElementStory::Avatar)) => {
|
// HACK: Special-case the kitchen sink to fix scrolling.
|
||||||
view(|cx| render_story(&mut ViewContext::new(cx), AvatarStory::default()))
|
// There is something about going through `children_any` that messes
|
||||||
}
|
// with the scroll interactions.
|
||||||
Some(StorySelector::Component(ComponentStory::Breadcrumb)) => {
|
Some(StorySelector::KitchenSink) => view(move |cx| {
|
||||||
view(|cx| render_story(&mut ViewContext::new(cx), BreadcrumbStory::default()))
|
render_story(
|
||||||
}
|
&mut ViewContext::new(cx),
|
||||||
Some(StorySelector::Component(ComponentStory::Facepile)) => {
|
theme_override.clone(),
|
||||||
view(|cx| render_story(&mut ViewContext::new(cx), FacepileStory::default()))
|
crate::stories::kitchen_sink::KitchenSinkStory::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())
|
|
||||||
}),
|
}),
|
||||||
None => {
|
// HACK: Special-case the panel story to fix scrolling.
|
||||||
view(|cx| render_story(&mut ViewContext::new(cx), WorkspaceElement::default()))
|
// 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);
|
cx.platform().activate(true);
|
||||||
|
@ -81,23 +114,32 @@ fn main() {
|
||||||
|
|
||||||
fn render_story<V: 'static, S: IntoElement<V>>(
|
fn render_story<V: 'static, S: IntoElement<V>>(
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
|
theme_override: Option<Arc<legacy_theme::Theme>>,
|
||||||
story: S,
|
story: S,
|
||||||
) -> impl Element<V> {
|
) -> 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.
|
// 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 {
|
fn add_base_theme_to_legacy_theme(legacy_theme: &legacy_theme::Theme, new_theme: Theme) -> Theme {
|
||||||
settings::get::<ThemeSettings>(cx)
|
legacy_theme
|
||||||
.theme
|
|
||||||
.deserialized_base_theme
|
.deserialized_base_theme
|
||||||
.lock()
|
.lock()
|
||||||
.get_or_insert_with(|| {
|
.get_or_insert_with(|| Box::new(new_theme))
|
||||||
let theme: Theme =
|
|
||||||
serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
|
|
||||||
.unwrap();
|
|
||||||
Box::new(theme)
|
|
||||||
})
|
|
||||||
.downcast_ref::<Theme>()
|
.downcast_ref::<Theme>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone()
|
.clone()
|
||||||
|
@ -106,7 +148,6 @@ fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use gpui2::AssetSource;
|
use gpui2::AssetSource;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use workspace::WorkspaceElement;
|
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "../../assets"]
|
#[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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,10 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
chrono = "0.4"
|
||||||
gpui2 = { path = "../gpui2" }
|
gpui2 = { path = "../gpui2" }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
|
smallvec.workspace = true
|
||||||
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
|
|
13
crates/ui/docs/_project.md
Normal file
13
crates/ui/docs/_project.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
## Project Plan
|
||||||
|
|
||||||
|
- Port existing UI to GPUI2
|
||||||
|
- Update UI in places that GPUI1 was limiting us*
|
||||||
|
- Understand the needs &/|| struggles the engineers have been having with building UI in the past and address as many of those as possible as we go
|
||||||
|
- Ship a simple, straightforward system with documentation that is easy to use to build UI
|
||||||
|
|
||||||
|
## Component Classification
|
||||||
|
|
||||||
|
To simplify the understanding of components and minimize unnecessary cognitive load, let's categorize components into two types:
|
||||||
|
|
||||||
|
- An element refers to a standalone component that doesn't import any other 'ui' components.
|
||||||
|
- A component indicates a component that utilizes or imports other 'ui' components.
|
7
crates/ui/src/children.rs
Normal file
7
crates/ui/src/children.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
use gpui2::{AnyElement, ViewContext};
|
||||||
|
|
||||||
|
pub type HackyChildren<V> = fn(&mut ViewContext<V>, &dyn Any) -> Vec<AnyElement<V>>;
|
||||||
|
|
||||||
|
pub type HackyChildrenPayload = Box<dyn Any>;
|
|
@ -1,142 +1,153 @@
|
||||||
|
mod assistant_panel;
|
||||||
mod breadcrumb;
|
mod breadcrumb;
|
||||||
|
mod buffer;
|
||||||
mod chat_panel;
|
mod chat_panel;
|
||||||
mod collab_panel;
|
mod collab_panel;
|
||||||
mod command_palette;
|
mod command_palette;
|
||||||
|
mod context_menu;
|
||||||
|
mod editor;
|
||||||
mod facepile;
|
mod facepile;
|
||||||
mod follow_group;
|
|
||||||
mod icon_button;
|
mod icon_button;
|
||||||
|
mod keybinding;
|
||||||
mod list;
|
mod list;
|
||||||
mod list_item;
|
|
||||||
mod list_section_header;
|
|
||||||
mod palette;
|
mod palette;
|
||||||
mod palette_item;
|
mod panel;
|
||||||
|
mod panes;
|
||||||
|
mod player_stack;
|
||||||
mod project_panel;
|
mod project_panel;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
mod tab;
|
mod tab;
|
||||||
mod tab_bar;
|
mod tab_bar;
|
||||||
|
mod terminal;
|
||||||
mod title_bar;
|
mod title_bar;
|
||||||
mod toolbar;
|
mod toolbar;
|
||||||
mod traffic_lights;
|
mod traffic_lights;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
|
pub use assistant_panel::*;
|
||||||
pub use breadcrumb::*;
|
pub use breadcrumb::*;
|
||||||
|
pub use buffer::*;
|
||||||
pub use chat_panel::*;
|
pub use chat_panel::*;
|
||||||
pub use collab_panel::*;
|
pub use collab_panel::*;
|
||||||
pub use command_palette::*;
|
pub use command_palette::*;
|
||||||
|
pub use context_menu::*;
|
||||||
|
pub use editor::*;
|
||||||
pub use facepile::*;
|
pub use facepile::*;
|
||||||
pub use follow_group::*;
|
|
||||||
pub use icon_button::*;
|
pub use icon_button::*;
|
||||||
|
pub use keybinding::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use list_item::*;
|
|
||||||
pub use list_section_header::*;
|
|
||||||
pub use palette::*;
|
pub use palette::*;
|
||||||
pub use palette_item::*;
|
pub use panel::*;
|
||||||
|
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::*;
|
||||||
pub use tab_bar::*;
|
pub use tab_bar::*;
|
||||||
|
pub use terminal::*;
|
||||||
pub use title_bar::*;
|
pub use title_bar::*;
|
||||||
pub use toolbar::*;
|
pub use toolbar::*;
|
||||||
pub use traffic_lights::*;
|
pub use traffic_lights::*;
|
||||||
pub use workspace::*;
|
pub use workspace::*;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
// Nate: Commenting this out for now, unsure if we need it.
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui2::elements::div;
|
// use std::marker::PhantomData;
|
||||||
use gpui2::interactive::Interactive;
|
// use std::rc::Rc;
|
||||||
use gpui2::platform::MouseButton;
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
struct ButtonHandlers<V, D> {
|
// use gpui2::elements::div;
|
||||||
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
// use gpui2::interactive::Interactive;
|
||||||
}
|
// use gpui2::platform::MouseButton;
|
||||||
|
// use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext};
|
||||||
|
|
||||||
impl<V, D> Default for ButtonHandlers<V, D> {
|
// struct ButtonHandlers<V, D> {
|
||||||
fn default() -> Self {
|
// click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
||||||
Self { click: None }
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Element)]
|
// impl<V, D> Default for ButtonHandlers<V, D> {
|
||||||
pub struct Button<V: 'static, D: 'static> {
|
// fn default() -> Self {
|
||||||
handlers: ButtonHandlers<V, D>,
|
// Self { click: None }
|
||||||
label: Option<ArcCow<'static, str>>,
|
// }
|
||||||
icon: Option<ArcCow<'static, str>>,
|
// }
|
||||||
data: Rc<D>,
|
|
||||||
view_type: PhantomData<V>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Impl block for buttons without data.
|
// #[derive(Element)]
|
||||||
// See below for an impl block for any button.
|
// pub struct Button<V: 'static, D: 'static> {
|
||||||
impl<V: 'static> Button<V, ()> {
|
// handlers: ButtonHandlers<V, D>,
|
||||||
fn new() -> Self {
|
// label: Option<ArcCow<'static, str>>,
|
||||||
Self {
|
// icon: Option<ArcCow<'static, str>>,
|
||||||
handlers: ButtonHandlers::default(),
|
// data: Rc<D>,
|
||||||
label: None,
|
// view_type: PhantomData<V>,
|
||||||
icon: None,
|
// }
|
||||||
data: Rc::new(()),
|
|
||||||
view_type: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
|
// // Impl block for buttons without data.
|
||||||
Button {
|
// // See below for an impl block for any button.
|
||||||
handlers: ButtonHandlers::default(),
|
// impl<V: 'static> Button<V, ()> {
|
||||||
label: self.label,
|
// fn new() -> Self {
|
||||||
icon: self.icon,
|
// Self {
|
||||||
data: Rc::new(data),
|
// handlers: ButtonHandlers::default(),
|
||||||
view_type: PhantomData,
|
// label: None,
|
||||||
}
|
// icon: None,
|
||||||
}
|
// data: Rc::new(()),
|
||||||
}
|
// view_type: PhantomData,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Impl block for button regardless of its data type.
|
// pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
|
||||||
impl<V: 'static, D: 'static> Button<V, D> {
|
// Button {
|
||||||
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
|
// handlers: ButtonHandlers::default(),
|
||||||
self.label = Some(label.into());
|
// label: self.label,
|
||||||
self
|
// icon: self.icon,
|
||||||
}
|
// data: Rc::new(data),
|
||||||
|
// view_type: PhantomData,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
|
// // Impl block for button regardless of its data type.
|
||||||
self.icon = Some(icon.into());
|
// impl<V: 'static, D: 'static> Button<V, D> {
|
||||||
self
|
// pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
|
||||||
}
|
// self.label = Some(label.into());
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn on_click(
|
// pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
|
||||||
mut self,
|
// self.icon = Some(icon.into());
|
||||||
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
|
// self
|
||||||
) -> Self {
|
// }
|
||||||
self.handlers.click = Some(Rc::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn button<V>() -> Button<V, ()> {
|
// pub fn on_click(
|
||||||
Button::new()
|
// mut self,
|
||||||
}
|
// handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
|
||||||
|
// ) -> Self {
|
||||||
|
// self.handlers.click = Some(Rc::new(handler));
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
impl<V: 'static, D: 'static> Button<V, D> {
|
// pub fn button<V>() -> Button<V, ()> {
|
||||||
fn render(
|
// Button::new()
|
||||||
&mut self,
|
// }
|
||||||
view: &mut V,
|
|
||||||
cx: &mut ViewContext<V>,
|
|
||||||
) -> impl IntoElement<V> + Interactive<V> {
|
|
||||||
// let colors = &cx.theme::<Theme>().colors;
|
|
||||||
|
|
||||||
let button = div()
|
// impl<V: 'static, D: 'static> Button<V, D> {
|
||||||
// .fill(colors.error(0.5))
|
// fn render(
|
||||||
.h_4()
|
// &mut self,
|
||||||
.children(self.label.clone());
|
// view: &mut V,
|
||||||
|
// cx: &mut ViewContext<V>,
|
||||||
|
// ) -> impl IntoElement<V> + Interactive<V> {
|
||||||
|
// // let colors = &cx.theme::<Theme>().colors;
|
||||||
|
|
||||||
if let Some(handler) = self.handlers.click.clone() {
|
// let button = div()
|
||||||
let data = self.data.clone();
|
// // .fill(colors.error(0.5))
|
||||||
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
|
// .h_4()
|
||||||
handler(view, data.as_ref(), cx)
|
// .children(self.label.clone());
|
||||||
})
|
|
||||||
} else {
|
// if let Some(handler) = self.handlers.click.clone() {
|
||||||
button
|
// let data = self.data.clone();
|
||||||
}
|
// button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
|
||||||
}
|
// handler(view, data.as_ref(), cx)
|
||||||
}
|
// })
|
||||||
|
// } else {
|
||||||
|
// button
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
91
crates/ui/src/components/assistant_panel.rs
Normal file
91
crates/ui/src/components/assistant_panel.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use gpui2::geometry::rems;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::{Icon, IconButton, Label, Panel, PanelSide};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct AssistantPanel<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
current_side: PanelSide,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> AssistantPanel<V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state: ScrollState::default(),
|
||||||
|
current_side: PanelSide::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn side(mut self, side: PanelSide) -> Self {
|
||||||
|
self.current_side = side;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
struct PanelPayload {
|
||||||
|
pub scroll_state: ScrollState,
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::new(
|
||||||
|
self.scroll_state.clone(),
|
||||||
|
|_, payload| {
|
||||||
|
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
|
||||||
|
|
||||||
|
vec![div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.h_full()
|
||||||
|
.px_2()
|
||||||
|
.gap_2()
|
||||||
|
// Header
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.justify_between()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.child(IconButton::new(Icon::Menu))
|
||||||
|
.child(Label::new("New Conversation")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_px()
|
||||||
|
.child(IconButton::new(Icon::SplitMessage))
|
||||||
|
.child(IconButton::new(Icon::Quote))
|
||||||
|
.child(IconButton::new(Icon::MagicWand))
|
||||||
|
.child(IconButton::new(Icon::Plus))
|
||||||
|
.child(IconButton::new(Icon::Maximize)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Chat Body
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.overflow_y_scroll(payload.scroll_state.clone())
|
||||||
|
.child(Label::new("Is this thing on?")),
|
||||||
|
)
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(PanelPayload {
|
||||||
|
scroll_state: self.scroll_state.clone(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.side(self.current_side)
|
||||||
|
.width(rems(32.))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,19 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
use crate::{h_stack, theme};
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Breadcrumb {}
|
pub struct Breadcrumb {}
|
||||||
|
|
||||||
pub fn breadcrumb() -> Breadcrumb {
|
|
||||||
Breadcrumb {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Breadcrumb {
|
impl Breadcrumb {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
div()
|
h_stack()
|
||||||
.px_1()
|
.px_1()
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
// TODO: Read font from theme (or settings?).
|
// TODO: Read font from theme (or settings?).
|
||||||
.font("Zed Mono Extended")
|
.font("Zed Mono Extended")
|
||||||
.text_sm()
|
.text_sm()
|
||||||
|
|
229
crates/ui/src/components/buffer.rs
Normal file
229
crates/ui/src/components/buffer.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use gpui2::{Hsla, WindowContext};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{h_stack, theme, v_stack, Icon, IconElement};
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
|
pub struct PlayerCursor {
|
||||||
|
color: Hsla,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone)]
|
||||||
|
pub struct HighlightedText {
|
||||||
|
pub text: String,
|
||||||
|
pub color: Hsla,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone)]
|
||||||
|
pub struct HighlightedLine {
|
||||||
|
pub highlighted_texts: Vec<HighlightedText>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone)]
|
||||||
|
pub struct BufferRow {
|
||||||
|
pub line_number: usize,
|
||||||
|
pub code_action: bool,
|
||||||
|
pub current: bool,
|
||||||
|
pub line: Option<HighlightedLine>,
|
||||||
|
pub cursors: Option<Vec<PlayerCursor>>,
|
||||||
|
pub status: GitStatus,
|
||||||
|
pub show_line_number: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BufferRows {
|
||||||
|
pub show_line_numbers: bool,
|
||||||
|
pub rows: Vec<BufferRow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BufferRows {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
show_line_numbers: true,
|
||||||
|
rows: vec![BufferRow {
|
||||||
|
line_number: 1,
|
||||||
|
code_action: false,
|
||||||
|
current: true,
|
||||||
|
line: None,
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number: true,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferRow {
|
||||||
|
pub fn new(line_number: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
line_number,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: None,
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_line(mut self, line: Option<HighlightedLine>) -> Self {
|
||||||
|
self.line = line;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursors(mut self, cursors: Option<Vec<PlayerCursor>>) -> Self {
|
||||||
|
self.cursors = cursors;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self {
|
||||||
|
if let Some(cursors) = &mut self.cursors {
|
||||||
|
cursors.push(cursor);
|
||||||
|
} else {
|
||||||
|
self.cursors = Some(vec![cursor]);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_status(mut self, status: GitStatus) -> Self {
|
||||||
|
self.status = status;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_show_line_number(mut self, show_line_number: bool) -> Self {
|
||||||
|
self.show_line_number = show_line_number;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_code_action(mut self, code_action: bool) -> Self {
|
||||||
|
self.code_action = code_action;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current(mut self, current: bool) -> Self {
|
||||||
|
self.current = current;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Buffer<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
rows: Option<BufferRows>,
|
||||||
|
readonly: bool,
|
||||||
|
language: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> Buffer<V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state: ScrollState::default(),
|
||||||
|
rows: Some(BufferRows::default()),
|
||||||
|
readonly: false,
|
||||||
|
language: None,
|
||||||
|
title: Some("untitled".to_string()),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) {
|
||||||
|
self.scroll_state = scroll_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
|
||||||
|
self.title = title.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_path<P: Into<Option<String>>>(mut self, path: P) -> Self {
|
||||||
|
self.path = path.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_readonly(mut self, readonly: bool) -> Self {
|
||||||
|
self.readonly = readonly;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_rows<R: Into<Option<BufferRows>>>(mut self, rows: R) -> Self {
|
||||||
|
self.rows = rows.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_language<L: Into<Option<String>>>(mut self, language: L) -> Self {
|
||||||
|
self.language = language.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
let line_background = if row.current {
|
||||||
|
theme.middle.base.default.background
|
||||||
|
} else {
|
||||||
|
system_color.transparent
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_number_color = if row.current {
|
||||||
|
HighlightColor::Default.hsla(cx)
|
||||||
|
} else {
|
||||||
|
HighlightColor::Comment.hsla(cx)
|
||||||
|
};
|
||||||
|
|
||||||
|
h_stack()
|
||||||
|
.fill(line_background)
|
||||||
|
.gap_2()
|
||||||
|
.px_2()
|
||||||
|
.child(h_stack().w_4().h_full().px_1().when(row.code_action, |c| {
|
||||||
|
div().child(IconElement::new(Icon::Bolt))
|
||||||
|
}))
|
||||||
|
.when(row.show_line_number, |this| {
|
||||||
|
this.child(
|
||||||
|
h_stack().justify_end().px_1().w_4().child(
|
||||||
|
div()
|
||||||
|
.text_color(line_number_color)
|
||||||
|
.child(row.line_number.to_string()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(div().mx_1().w_1().h_full().fill(row.status.hsla(cx)))
|
||||||
|
.children(row.line.map(|line| {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.children(line.highlighted_texts.iter().map(|highlighted_text| {
|
||||||
|
div()
|
||||||
|
.text_color(highlighted_text.color)
|
||||||
|
.child(highlighted_text.text.clone())
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_rows(&self, cx: &WindowContext) -> Vec<impl IntoElement<V>> {
|
||||||
|
match &self.rows {
|
||||||
|
Some(rows) => rows
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.map(|row| Self::render_row(row.clone(), cx))
|
||||||
|
.collect(),
|
||||||
|
None => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let rows = self.render_rows(cx);
|
||||||
|
v_stack()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
|
.fill(theme.highest.base.default.background)
|
||||||
|
.children(rows)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +1,127 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use gpui2::elements::div::ScrollState;
|
use chrono::NaiveDateTime;
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use crate::{icon_button, IconAsset};
|
use crate::{Icon, IconButton, Input, Label, LabelColor, Panel, PanelSide};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct ChatPanel<V: 'static> {
|
pub struct ChatPanel<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
}
|
current_side: PanelSide,
|
||||||
|
messages: Vec<ChatMessage>,
|
||||||
pub fn chat_panel<V: 'static>(scroll_state: ScrollState) -> ChatPanel<V> {
|
|
||||||
ChatPanel {
|
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> ChatPanel<V> {
|
impl<V: 'static> ChatPanel<V> {
|
||||||
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
current_side: PanelSide::default(),
|
||||||
|
messages: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn side(mut self, side: PanelSide) -> Self {
|
||||||
|
self.current_side = side;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
|
||||||
|
self.messages = messages;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
div()
|
struct PanelPayload {
|
||||||
.h_full()
|
pub scroll_state: ScrollState,
|
||||||
.flex()
|
pub messages: Vec<ChatMessage>,
|
||||||
// Header
|
}
|
||||||
.child(
|
|
||||||
div()
|
Panel::new(
|
||||||
.px_2()
|
self.scroll_state.clone(),
|
||||||
.flex()
|
|_, payload| {
|
||||||
.gap_2()
|
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
|
||||||
// Nav Buttons
|
|
||||||
.child("#gpui2"),
|
vec![div()
|
||||||
)
|
|
||||||
// Chat Body
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_full()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.h_full()
|
||||||
.child("body"),
|
|
||||||
)
|
|
||||||
// Composer
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
.px_2()
|
||||||
.flex()
|
|
||||||
.gap_2()
|
.gap_2()
|
||||||
// Nav Buttons
|
// Header
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.justify_between()
|
||||||
.gap_px()
|
.gap_2()
|
||||||
.child(icon_button().icon(IconAsset::Plus))
|
.child(div().flex().child(Label::new("#design")))
|
||||||
.child(icon_button().icon(IconAsset::Split)),
|
.child(
|
||||||
),
|
div()
|
||||||
)
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_px()
|
||||||
|
.child(IconButton::new(Icon::File))
|
||||||
|
.child(IconButton::new(Icon::AudioOn)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Chat Body
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.overflow_y_scroll(payload.scroll_state.clone())
|
||||||
|
.children(payload.messages.clone()),
|
||||||
|
)
|
||||||
|
// Composer
|
||||||
|
.child(div().flex().gap_2().child(Input::new("Message #design")))
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(PanelPayload {
|
||||||
|
scroll_state: self.scroll_state.clone(),
|
||||||
|
messages: self.messages.clone(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.side(self.current_side)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct ChatMessage {
|
||||||
|
author: String,
|
||||||
|
text: String,
|
||||||
|
sent_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatMessage {
|
||||||
|
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
|
||||||
|
Self {
|
||||||
|
author,
|
||||||
|
text,
|
||||||
|
sent_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(Label::new(self.author.clone()))
|
||||||
|
.child(
|
||||||
|
Label::new(self.sent_at.format("%m/%d/%Y").to_string())
|
||||||
|
.color(LabelColor::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(div().child(Label::new(self.text.clone())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +1,85 @@
|
||||||
use crate::theme::{theme, Theme};
|
|
||||||
use gpui2::{
|
|
||||||
elements::{div, div::ScrollState, img, svg},
|
|
||||||
style::{StyleHelpers, Styleable},
|
|
||||||
ArcCow, Element, IntoElement, ParentElement, ViewContext,
|
|
||||||
};
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use gpui2::elements::{img, svg};
|
||||||
|
use gpui2::ArcCow;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::theme::{theme, Theme};
|
||||||
|
use crate::{
|
||||||
|
static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List,
|
||||||
|
ListHeader, ToggleState,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct CollabPanelElement<V: 'static> {
|
pub struct CollabPanel<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// When I improve child view rendering, I'd like to have V implement a trait that
|
impl<V: 'static> CollabPanel<V> {
|
||||||
// provides the scroll state, among other things.
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
|
Self {
|
||||||
CollabPanelElement {
|
view_type: PhantomData,
|
||||||
view_type: PhantomData,
|
scroll_state,
|
||||||
scroll_state,
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> CollabPanelElement<V> {
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
// Panel
|
v_stack()
|
||||||
div()
|
|
||||||
.w_64()
|
.w_64()
|
||||||
.h_full()
|
.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)
|
.fill(theme.middle.base.default.background)
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_stack()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
// List Container
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.fill(theme.lowest.base.default.background)
|
.fill(theme.lowest.base.default.background)
|
||||||
.pb_1()
|
.pb_1()
|
||||||
.border_color(theme.lowest.base.default.border)
|
.border_color(theme.lowest.base.default.border)
|
||||||
.border_b()
|
.border_b()
|
||||||
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
|
.child(
|
||||||
// .group()
|
List::new(static_collab_panel_current_call())
|
||||||
// List Section Header
|
.header(
|
||||||
.child(self.list_section_header("#CRDB", true, &theme))
|
ListHeader::new("CRDB")
|
||||||
// List Item Large
|
.left_icon(Icon::Hash.into())
|
||||||
.child(self.list_item(
|
.set_toggle(ToggleState::Toggled),
|
||||||
"http://github.com/maxbrunsfeld.png?s=50",
|
)
|
||||||
"maxbrunsfeld",
|
.set_toggle(ToggleState::Toggled),
|
||||||
&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(3)
|
|
||||||
.flatten(),
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_stack().py_1().child(
|
||||||
|
List::new(static_collab_panel_channels())
|
||||||
|
.header(
|
||||||
|
ListHeader::new("CHANNELS").set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
.empty_message("No channels yet. Add a channel to get started.")
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_stack().py_1().child(
|
||||||
|
List::new(static_collab_panel_current_call())
|
||||||
|
.header(
|
||||||
|
ListHeader::new("CONTACTS – ONLINE")
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_stack().py_1().child(
|
||||||
|
List::new(static_collab_panel_current_call())
|
||||||
|
.header(
|
||||||
|
ListHeader::new("CONTACTS – OFFLINE")
|
||||||
|
.set_toggle(ToggleState::NotToggled),
|
||||||
|
)
|
||||||
|
.set_toggle(ToggleState::NotToggled),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::{elements::div::ScrollState, ViewContext};
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement};
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{example_editor_actions, palette, OrderMethod};
|
use crate::prelude::*;
|
||||||
|
use crate::{example_editor_actions, OrderMethod, Palette};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct CommandPalette<V: 'static> {
|
pub struct CommandPalette<V: 'static> {
|
||||||
|
@ -11,17 +9,17 @@ pub struct CommandPalette<V: 'static> {
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command_palette<V: 'static>(scroll_state: ScrollState) -> CommandPalette<V> {
|
|
||||||
CommandPalette {
|
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> CommandPalette<V> {
|
impl<V: 'static> CommandPalette<V> {
|
||||||
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
div().child(
|
div().child(
|
||||||
palette(self.scroll_state.clone())
|
Palette::new(self.scroll_state.clone())
|
||||||
.items(example_editor_actions())
|
.items(example_editor_actions())
|
||||||
.placeholder("Execute a command...")
|
.placeholder("Execute a command...")
|
||||||
.empty_string("No items found.")
|
.empty_string("No items found.")
|
||||||
|
|
65
crates/ui/src/components/context_menu.rs
Normal file
65
crates/ui/src/components/context_menu.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::{
|
||||||
|
v_stack, Label, List, ListEntry, ListItem, ListItemVariant, ListSeparator, ListSubHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ContextMenuItem {
|
||||||
|
Header(&'static str),
|
||||||
|
Entry(Label),
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextMenuItem {
|
||||||
|
fn to_list_item(self) -> ListItem {
|
||||||
|
match self {
|
||||||
|
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
|
||||||
|
ContextMenuItem::Entry(label) => {
|
||||||
|
ListEntry::new(label).variant(ListItemVariant::Inset).into()
|
||||||
|
}
|
||||||
|
ContextMenuItem::Separator => ListSeparator::new().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn header(label: &'static str) -> Self {
|
||||||
|
Self::Header(label)
|
||||||
|
}
|
||||||
|
pub fn separator() -> Self {
|
||||||
|
Self::Separator
|
||||||
|
}
|
||||||
|
pub fn entry(label: Label) -> Self {
|
||||||
|
Self::Entry(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct ContextMenu {
|
||||||
|
items: Vec<ContextMenuItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextMenu {
|
||||||
|
pub fn new(items: impl IntoIterator<Item = ContextMenuItem>) -> Self {
|
||||||
|
Self {
|
||||||
|
items: items.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render<V: 'static>(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
v_stack()
|
||||||
|
.flex()
|
||||||
|
.fill(theme.lowest.base.default.background)
|
||||||
|
.border()
|
||||||
|
.border_color(theme.lowest.base.default.border)
|
||||||
|
.child(
|
||||||
|
List::new(
|
||||||
|
self.items
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(ContextMenuItem::to_list_item)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
//div().p_1().children(self.items.clone())
|
||||||
|
}
|
||||||
|
}
|
25
crates/ui/src/components/editor.rs
Normal file
25
crates/ui/src/components/editor.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{Buffer, Toolbar};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
struct Editor<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
toolbar: Toolbar,
|
||||||
|
buffer: Buffer<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> Editor<V> {
|
||||||
|
pub fn new(toolbar: Toolbar, buffer: Buffer<V>) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
toolbar,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
div().child(self.toolbar.clone())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,27 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
use crate::{theme, Avatar, Player};
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::{theme, Avatar};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Facepile {
|
pub struct Facepile {
|
||||||
players: Vec<Avatar>,
|
players: Vec<Player>,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn facepile<P: Iterator<Item = Avatar>>(players: P) -> Facepile {
|
|
||||||
Facepile {
|
|
||||||
players: players.collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Facepile {
|
impl Facepile {
|
||||||
|
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
|
||||||
|
Self {
|
||||||
|
players: players.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let player_count = self.players.len();
|
let player_count = self.players.len();
|
||||||
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
|
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
|
||||||
let isnt_last = ix < player_count - 1;
|
let isnt_last = ix < player_count - 1;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.when(isnt_last, |div| div.neg_mr_1())
|
.when(isnt_last, |div| div.neg_mr_1())
|
||||||
.child(player.clone())
|
.child(Avatar::new(player.avatar_src().to_string()))
|
||||||
});
|
});
|
||||||
div().p_1().flex().items_center().children(player_list)
|
div().p_1().flex().items_center().children(player_list)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::{facepile, indicator, theme, Avatar};
|
|
||||||
|
|
||||||
#[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().into_iter())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +1,16 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
use crate::{theme, Icon, IconColor, IconElement};
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::{icon, theme, IconColor};
|
|
||||||
use crate::{prelude::*, IconAsset};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct IconButton {
|
pub struct IconButton {
|
||||||
icon: IconAsset,
|
icon: Icon,
|
||||||
color: IconColor,
|
color: IconColor,
|
||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
state: InteractionState,
|
state: InteractionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon_button() -> IconButton {
|
|
||||||
IconButton {
|
|
||||||
icon: IconAsset::default(),
|
|
||||||
color: IconColor::default(),
|
|
||||||
variant: ButtonVariant::default(),
|
|
||||||
state: InteractionState::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IconButton {
|
impl IconButton {
|
||||||
pub fn new(icon: IconAsset) -> Self {
|
pub fn new(icon: Icon) -> Self {
|
||||||
Self {
|
Self {
|
||||||
icon,
|
icon,
|
||||||
color: IconColor::default(),
|
color: IconColor::default(),
|
||||||
|
@ -32,7 +19,7 @@ impl IconButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon(mut self, icon: IconAsset) -> Self {
|
pub fn icon(mut self, icon: Icon) -> Self {
|
||||||
self.icon = icon;
|
self.icon = icon;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -75,6 +62,6 @@ impl IconButton {
|
||||||
.fill(theme.highest.base.hovered.background)
|
.fill(theme.highest.base.hovered.background)
|
||||||
.active()
|
.active()
|
||||||
.fill(theme.highest.base.pressed.background)
|
.fill(theme.highest.base.pressed.background)
|
||||||
.child(icon(self.icon).color(icon_color))
|
.child(IconElement::new(self.icon).color(icon_color))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
crates/ui/src/components/keybinding.rs
Normal file
158
crates/ui/src/components/keybinding.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use strum::{EnumIter, IntoEnumIterator};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::theme;
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct Keybinding {
|
||||||
|
/// A keybinding consists of a key and a set of modifier keys.
|
||||||
|
/// More then one keybinding produces a chord.
|
||||||
|
///
|
||||||
|
/// This should always contain at least one element.
|
||||||
|
keybinding: Vec<(String, ModifierKeys)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keybinding {
|
||||||
|
pub fn new(key: String, modifiers: ModifierKeys) -> Self {
|
||||||
|
Self {
|
||||||
|
keybinding: vec![(key, modifiers)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_chord(
|
||||||
|
first_note: (String, ModifierKeys),
|
||||||
|
second_note: (String, ModifierKeys),
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
keybinding: vec![first_note, second_note],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
.children(self.keybinding.iter().map(|(key, modifiers)| {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_1()
|
||||||
|
.children(ModifierKey::iter().filter_map(|modifier| {
|
||||||
|
if modifiers.0.contains(&modifier) {
|
||||||
|
Some(Key::new(modifier.glyph()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.child(Key::new(key.clone()))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Key {
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
pub fn new<K>(key: K) -> Self
|
||||||
|
where
|
||||||
|
K: Into<String>,
|
||||||
|
{
|
||||||
|
Self { key: key.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.py_0()
|
||||||
|
.rounded_md()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(theme.lowest.on.default.foreground)
|
||||||
|
.fill(theme.lowest.on.default.background)
|
||||||
|
.child(self.key.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: The order the modifier keys appear in this enum impacts the order in
|
||||||
|
// which they are rendered in the UI.
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
|
pub enum ModifierKey {
|
||||||
|
Control,
|
||||||
|
Alt,
|
||||||
|
Command,
|
||||||
|
Shift,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModifierKey {
|
||||||
|
/// Returns the glyph for the [`ModifierKey`].
|
||||||
|
pub fn glyph(&self) -> char {
|
||||||
|
match self {
|
||||||
|
Self::Control => '^',
|
||||||
|
Self::Alt => '⎇',
|
||||||
|
Self::Command => '⌘',
|
||||||
|
Self::Shift => '⇧',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ModifierKeys(HashSet<ModifierKey>);
|
||||||
|
|
||||||
|
impl ModifierKeys {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(HashSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all() -> Self {
|
||||||
|
Self(HashSet::from_iter(ModifierKey::iter()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(mut self, modifier: ModifierKey) -> Self {
|
||||||
|
self.0.insert(modifier);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn control(mut self, control: bool) -> Self {
|
||||||
|
if control {
|
||||||
|
self.0.insert(ModifierKey::Control);
|
||||||
|
} else {
|
||||||
|
self.0.remove(&ModifierKey::Control);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alt(mut self, alt: bool) -> Self {
|
||||||
|
if alt {
|
||||||
|
self.0.insert(ModifierKey::Alt);
|
||||||
|
} else {
|
||||||
|
self.0.remove(&ModifierKey::Alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(mut self, command: bool) -> Self {
|
||||||
|
if command {
|
||||||
|
self.0.insert(ModifierKey::Command);
|
||||||
|
} else {
|
||||||
|
self.0.remove(&ModifierKey::Command);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shift(mut self, shift: bool) -> Self {
|
||||||
|
if shift {
|
||||||
|
self.0.insert(ModifierKey::Shift);
|
||||||
|
} else {
|
||||||
|
self.0.remove(&ModifierKey::Shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +1,294 @@
|
||||||
use crate::theme::theme;
|
use gpui2::elements::div::Div;
|
||||||
use crate::tokens::token;
|
use gpui2::{Hsla, WindowContext};
|
||||||
use crate::{icon, label, prelude::*, IconAsset, LabelColor, ListItem, ListSectionHeader};
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
use crate::prelude::*;
|
||||||
pub struct List {
|
use crate::{
|
||||||
header: Option<ListSectionHeader>,
|
h_stack, theme, token, v_stack, Avatar, DisclosureControlVisibility, Icon, IconColor,
|
||||||
items: Vec<ListItem>,
|
IconElement, IconSize, InteractionState, Label, LabelColor, LabelSize, SystemColor,
|
||||||
empty_message: &'static str,
|
ToggleState,
|
||||||
toggle: Option<ToggleState>,
|
};
|
||||||
// footer: Option<ListSectionFooter>,
|
|
||||||
|
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
||||||
|
pub enum ListItemVariant {
|
||||||
|
/// The list item extends to the far left and right of the list.
|
||||||
|
#[default]
|
||||||
|
FullWidth,
|
||||||
|
Inset,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(items: Vec<ListItem>) -> List {
|
#[derive(Element, Clone, Copy)]
|
||||||
List {
|
pub struct ListHeader {
|
||||||
header: None,
|
label: &'static str,
|
||||||
items,
|
left_icon: Option<Icon>,
|
||||||
empty_message: "No items",
|
variant: ListItemVariant,
|
||||||
toggle: None,
|
state: InteractionState,
|
||||||
|
toggleable: Toggleable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListHeader {
|
||||||
|
pub fn new(label: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
left_icon: None,
|
||||||
|
variant: ListItemVariant::default(),
|
||||||
|
state: InteractionState::default(),
|
||||||
|
toggleable: Toggleable::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl List {
|
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
||||||
pub fn header(mut self, header: ListSectionHeader) -> Self {
|
self.toggleable = toggle.into();
|
||||||
self.header = Some(header);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_message(mut self, empty_message: &'static str) -> Self {
|
pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self {
|
||||||
self.empty_message = empty_message;
|
self.toggleable = toggleable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||||
|
self.left_icon = left_icon;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(mut self, state: InteractionState) -> Self {
|
||||||
|
self.state = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disclosure_control<V: 'static>(&self) -> Div<V> {
|
||||||
|
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||||
|
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||||
|
|
||||||
|
match (is_toggleable, is_toggled) {
|
||||||
|
(false, _) => div(),
|
||||||
|
(_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)),
|
||||||
|
(_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Hovered => theme.lowest.base.hovered.background,
|
||||||
|
InteractionState::Active => theme.lowest.base.pressed.background,
|
||||||
|
InteractionState::Enabled => theme.lowest.on.default.background,
|
||||||
|
_ => system_color.transparent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_color(&self) -> LabelColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => LabelColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_color(&self) -> IconColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => IconColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let token = token();
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
let background_color = self.background_color(cx);
|
||||||
|
|
||||||
|
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||||
|
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||||
|
|
||||||
|
let disclosure_control = self.disclosure_control();
|
||||||
|
|
||||||
|
h_stack()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
|
.fill(background_color)
|
||||||
|
.when(self.state == InteractionState::Focused, |this| {
|
||||||
|
this.border()
|
||||||
|
.border_color(theme.lowest.accent.default.border)
|
||||||
|
})
|
||||||
|
.relative()
|
||||||
|
.py_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h_6()
|
||||||
|
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||||
|
.flex()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.children(self.left_icon.map(|i| {
|
||||||
|
IconElement::new(i)
|
||||||
|
.color(IconColor::Muted)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
Label::new(self.label.clone())
|
||||||
|
.color(LabelColor::Muted)
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(disclosure_control),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element, Clone, Copy)]
|
||||||
|
pub struct ListSubHeader {
|
||||||
|
label: &'static str,
|
||||||
|
left_icon: Option<Icon>,
|
||||||
|
variant: ListItemVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListSubHeader {
|
||||||
|
pub fn new(label: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
left_icon: None,
|
||||||
|
variant: ListItemVariant::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||||
|
self.left_icon = left_icon;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let token = token();
|
||||||
|
|
||||||
|
h_stack().flex_1().w_full().relative().py_1().child(
|
||||||
|
div()
|
||||||
|
.h_6()
|
||||||
|
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||||
|
.flex()
|
||||||
|
.flex_1()
|
||||||
|
.w_full()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.children(self.left_icon.map(|i| {
|
||||||
|
IconElement::new(i)
|
||||||
|
.color(IconColor::Muted)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
Label::new(self.label.clone())
|
||||||
|
.color(LabelColor::Muted)
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum LeftContent {
|
||||||
|
Icon(Icon),
|
||||||
|
Avatar(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
|
pub enum ListEntrySize {
|
||||||
|
#[default]
|
||||||
|
Small,
|
||||||
|
Medium,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Element)]
|
||||||
|
pub enum ListItem {
|
||||||
|
Entry(ListEntry),
|
||||||
|
Separator(ListSeparator),
|
||||||
|
Header(ListSubHeader),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListEntry> for ListItem {
|
||||||
|
fn from(entry: ListEntry) -> Self {
|
||||||
|
Self::Entry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListSeparator> for ListItem {
|
||||||
|
fn from(entry: ListSeparator) -> Self {
|
||||||
|
Self::Separator(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListSubHeader> for ListItem {
|
||||||
|
fn from(entry: ListSubHeader) -> Self {
|
||||||
|
Self::Header(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListItem {
|
||||||
|
fn render<V: 'static>(&mut self, v: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
match self {
|
||||||
|
ListItem::Entry(entry) => div().child(entry.render(v, cx)),
|
||||||
|
ListItem::Separator(separator) => div().child(separator.render(v, cx)),
|
||||||
|
ListItem::Header(header) => div().child(header.render(v, cx)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new(label: Label) -> Self {
|
||||||
|
Self::Entry(ListEntry::new(label))
|
||||||
|
}
|
||||||
|
pub fn as_entry(&mut self) -> Option<&mut ListEntry> {
|
||||||
|
if let Self::Entry(entry) = self {
|
||||||
|
Some(entry)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct ListEntry {
|
||||||
|
disclosure_control_style: DisclosureControlVisibility,
|
||||||
|
indent_level: u32,
|
||||||
|
label: Label,
|
||||||
|
left_content: Option<LeftContent>,
|
||||||
|
variant: ListItemVariant,
|
||||||
|
size: ListEntrySize,
|
||||||
|
state: InteractionState,
|
||||||
|
toggle: Option<ToggleState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListEntry {
|
||||||
|
pub fn new(label: Label) -> Self {
|
||||||
|
Self {
|
||||||
|
disclosure_control_style: DisclosureControlVisibility::default(),
|
||||||
|
indent_level: 0,
|
||||||
|
label,
|
||||||
|
variant: ListItemVariant::default(),
|
||||||
|
left_content: None,
|
||||||
|
size: ListEntrySize::default(),
|
||||||
|
state: InteractionState::default(),
|
||||||
|
toggle: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn variant(mut self, variant: ListItemVariant) -> Self {
|
||||||
|
self.variant = variant;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn indent_level(mut self, indent_level: u32) -> Self {
|
||||||
|
self.indent_level = indent_level;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,26 +297,216 @@ impl List {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
pub fn left_content(mut self, left_content: LeftContent) -> Self {
|
||||||
|
self.left_content = Some(left_content);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_icon(mut self, left_icon: Icon) -> Self {
|
||||||
|
self.left_content = Some(LeftContent::Icon(left_icon));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_avatar(mut self, left_avatar: &'static str) -> Self {
|
||||||
|
self.left_content = Some(LeftContent::Avatar(left_avatar));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(mut self, state: InteractionState) -> Self {
|
||||||
|
self.state = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(mut self, size: ListEntrySize) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disclosure_control_style(
|
||||||
|
mut self,
|
||||||
|
disclosure_control_style: DisclosureControlVisibility,
|
||||||
|
) -> Self {
|
||||||
|
self.disclosure_control_style = disclosure_control_style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Hovered => theme.lowest.base.hovered.background,
|
||||||
|
InteractionState::Active => theme.lowest.base.pressed.background,
|
||||||
|
InteractionState::Enabled => theme.lowest.on.default.background,
|
||||||
|
_ => system_color.transparent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_color(&self) -> LabelColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => LabelColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_color(&self) -> IconColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => IconColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disclosure_control<V: 'static>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut ViewContext<V>,
|
||||||
|
) -> Option<impl IntoElement<V>> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let token = token();
|
let token = token();
|
||||||
|
|
||||||
let disclosure_control = match self.toggle {
|
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
|
||||||
Some(ToggleState::NotToggled) => Some(icon(IconAsset::ChevronRight)),
|
IconElement::new(Icon::ChevronDown)
|
||||||
Some(ToggleState::Toggled) => Some(icon(IconAsset::ChevronDown)),
|
} else {
|
||||||
|
IconElement::new(Icon::ChevronRight)
|
||||||
|
}
|
||||||
|
.color(IconColor::Muted)
|
||||||
|
.size(IconSize::Small);
|
||||||
|
|
||||||
|
match (self.toggle, self.disclosure_control_style) {
|
||||||
|
(Some(_), DisclosureControlVisibility::OnHover) => {
|
||||||
|
Some(div().absolute().neg_left_5().child(disclosure_control_icon))
|
||||||
|
}
|
||||||
|
(Some(_), DisclosureControlVisibility::Always) => {
|
||||||
|
Some(div().child(disclosure_control_icon))
|
||||||
|
}
|
||||||
|
(None, _) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let token = token();
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
let background_color = self.background_color(cx);
|
||||||
|
|
||||||
|
let left_content = match self.left_content {
|
||||||
|
Some(LeftContent::Icon(i)) => {
|
||||||
|
Some(h_stack().child(IconElement::new(i).size(IconSize::Small)))
|
||||||
|
}
|
||||||
|
Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sized_item = match self.size {
|
||||||
|
ListEntrySize::Small => div().h_6(),
|
||||||
|
ListEntrySize::Medium => div().h_7(),
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.fill(background_color)
|
||||||
|
.when(self.state == InteractionState::Focused, |this| {
|
||||||
|
this.border()
|
||||||
|
.border_color(theme.lowest.accent.default.border)
|
||||||
|
})
|
||||||
|
.relative()
|
||||||
.py_1()
|
.py_1()
|
||||||
.flex()
|
.child(
|
||||||
.flex_col()
|
sized_item
|
||||||
.children(self.header.map(|h| h))
|
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||||
.children(
|
// .ml(rems(0.75 * self.indent_level as f32))
|
||||||
self.items
|
.children((0..self.indent_level).map(|_| {
|
||||||
.is_empty()
|
div()
|
||||||
.then(|| label(self.empty_message).color(LabelColor::Muted)),
|
.w(token.list_indent_depth)
|
||||||
|
.h_full()
|
||||||
|
.flex()
|
||||||
|
.justify_center()
|
||||||
|
.child(h_stack().child(div().w_px().h_full()).child(
|
||||||
|
div().w_px().h_full().fill(theme.middle.base.default.border),
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.flex()
|
||||||
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
|
.relative()
|
||||||
|
.children(self.disclosure_control(cx))
|
||||||
|
.children(left_content)
|
||||||
|
.child(self.label.clone()),
|
||||||
)
|
)
|
||||||
.children(self.items.iter().cloned())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Element)]
|
||||||
|
pub struct ListSeparator;
|
||||||
|
|
||||||
|
impl ListSeparator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div().h_px().w_full().fill(theme.lowest.base.default.border)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct List {
|
||||||
|
items: Vec<ListItem>,
|
||||||
|
empty_message: &'static str,
|
||||||
|
header: Option<ListHeader>,
|
||||||
|
toggleable: Toggleable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
pub fn new(items: Vec<ListItem>) -> Self {
|
||||||
|
Self {
|
||||||
|
items,
|
||||||
|
empty_message: "No items",
|
||||||
|
header: None,
|
||||||
|
toggleable: Toggleable::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_message(mut self, empty_message: &'static str) -> Self {
|
||||||
|
self.empty_message = empty_message;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn header(mut self, header: ListHeader) -> Self {
|
||||||
|
self.header = Some(header);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
||||||
|
self.toggleable = toggle.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let token = token();
|
||||||
|
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
|
||||||
|
let is_toggled = Toggleable::is_toggled(&self.toggleable);
|
||||||
|
|
||||||
|
let disclosure_control = if is_toggleable {
|
||||||
|
IconElement::new(Icon::ChevronRight)
|
||||||
|
} else {
|
||||||
|
IconElement::new(Icon::ChevronDown)
|
||||||
|
};
|
||||||
|
|
||||||
|
let list_content = match (self.items.is_empty(), is_toggled) {
|
||||||
|
(_, false) => div(),
|
||||||
|
(false, _) => div().children(self.items.iter().cloned()),
|
||||||
|
(true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)),
|
||||||
|
};
|
||||||
|
|
||||||
|
v_stack()
|
||||||
|
.py_1()
|
||||||
|
.children(
|
||||||
|
self.header
|
||||||
|
.clone()
|
||||||
|
.map(|header| header.set_toggleable(self.toggleable)),
|
||||||
|
)
|
||||||
|
.child(list_content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
use crate::prelude::{DisclosureControlVisibility, InteractionState, ToggleState};
|
|
||||||
use crate::theme::theme;
|
|
||||||
use crate::tokens::token;
|
|
||||||
use crate::{icon, IconAsset, Label};
|
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
#[derive(Element, Clone)]
|
|
||||||
pub struct ListItem {
|
|
||||||
label: Label,
|
|
||||||
left_icon: Option<IconAsset>,
|
|
||||||
indent_level: u32,
|
|
||||||
state: InteractionState,
|
|
||||||
disclosure_control_style: DisclosureControlVisibility,
|
|
||||||
toggle: Option<ToggleState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_item(label: Label) -> ListItem {
|
|
||||||
ListItem {
|
|
||||||
label,
|
|
||||||
indent_level: 0,
|
|
||||||
left_icon: None,
|
|
||||||
disclosure_control_style: DisclosureControlVisibility::default(),
|
|
||||||
state: InteractionState::default(),
|
|
||||||
toggle: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListItem {
|
|
||||||
pub fn indent_level(mut self, indent_level: u32) -> Self {
|
|
||||||
self.indent_level = indent_level;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
|
||||||
self.toggle = Some(toggle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_icon(mut self, left_icon: Option<IconAsset>) -> Self {
|
|
||||||
self.left_icon = left_icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(mut self, state: InteractionState) -> Self {
|
|
||||||
self.state = state;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disclosure_control_style(
|
|
||||||
mut self,
|
|
||||||
disclosure_control_style: DisclosureControlVisibility,
|
|
||||||
) -> Self {
|
|
||||||
self.disclosure_control_style = disclosure_control_style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
let token = token();
|
|
||||||
let mut disclosure_control = match self.toggle {
|
|
||||||
Some(ToggleState::NotToggled) => Some(div().child(icon(IconAsset::ChevronRight))),
|
|
||||||
Some(ToggleState::Toggled) => Some(div().child(icon(IconAsset::ChevronDown))),
|
|
||||||
None => Some(div()),
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.disclosure_control_style {
|
|
||||||
DisclosureControlVisibility::OnHover => {
|
|
||||||
disclosure_control =
|
|
||||||
disclosure_control.map(|c| div().absolute().neg_left_5().child(c));
|
|
||||||
}
|
|
||||||
DisclosureControlVisibility::Always => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
|
||||||
.fill(theme.middle.base.default.background)
|
|
||||||
.hover()
|
|
||||||
.fill(theme.middle.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.middle.base.pressed.background)
|
|
||||||
.relative()
|
|
||||||
.py_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_6()
|
|
||||||
.px_2()
|
|
||||||
// .ml(rems(0.75 * self.indent_level as f32))
|
|
||||||
.children((0..self.indent_level).map(|_| {
|
|
||||||
div()
|
|
||||||
.w(token.list_indent_depth)
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.ml_px()
|
|
||||||
.w_px()
|
|
||||||
.h_full()
|
|
||||||
.fill(theme.middle.base.default.border),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.flex()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.relative()
|
|
||||||
.children(disclosure_control)
|
|
||||||
.children(self.left_icon.map(|i| icon(i)))
|
|
||||||
.child(self.label.clone()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
use crate::prelude::{InteractionState, ToggleState};
|
|
||||||
use crate::theme::theme;
|
|
||||||
use crate::tokens::token;
|
|
||||||
use crate::{icon, label, IconAsset, LabelColor, LabelSize};
|
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
#[derive(Element, Clone, Copy)]
|
|
||||||
pub struct ListSectionHeader {
|
|
||||||
label: &'static str,
|
|
||||||
left_icon: Option<IconAsset>,
|
|
||||||
state: InteractionState,
|
|
||||||
toggle: Option<ToggleState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_section_header(label: &'static str) -> ListSectionHeader {
|
|
||||||
ListSectionHeader {
|
|
||||||
label,
|
|
||||||
left_icon: None,
|
|
||||||
state: InteractionState::default(),
|
|
||||||
toggle: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListSectionHeader {
|
|
||||||
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
|
|
||||||
self.toggle = Some(toggle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_icon(mut self, left_icon: Option<IconAsset>) -> Self {
|
|
||||||
self.left_icon = left_icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(mut self, state: InteractionState) -> Self {
|
|
||||||
self.state = state;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
let token = token();
|
|
||||||
|
|
||||||
let disclosure_control = match self.toggle {
|
|
||||||
Some(ToggleState::NotToggled) => Some(div().child(icon(IconAsset::ChevronRight))),
|
|
||||||
Some(ToggleState::Toggled) => Some(div().child(icon(IconAsset::ChevronDown))),
|
|
||||||
None => Some(div()),
|
|
||||||
};
|
|
||||||
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_1()
|
|
||||||
.w_full()
|
|
||||||
.fill(theme.middle.base.default.background)
|
|
||||||
.hover()
|
|
||||||
.fill(theme.middle.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.middle.base.pressed.background)
|
|
||||||
.relative()
|
|
||||||
.py_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_6()
|
|
||||||
.px_2()
|
|
||||||
.flex()
|
|
||||||
.flex_1()
|
|
||||||
.w_full()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.gap_1()
|
|
||||||
.items_center()
|
|
||||||
.children(self.left_icon.map(|i| icon(i)))
|
|
||||||
.child(
|
|
||||||
label(self.label.clone())
|
|
||||||
.color(LabelColor::Muted)
|
|
||||||
.size(LabelSize::Small),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.children(disclosure_control),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,8 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::prelude::OrderMethod;
|
use crate::prelude::*;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use crate::{label, palette_item, LabelColor, PaletteItem};
|
use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
|
||||||
use gpui2::elements::div::ScrollState;
|
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Palette<V: 'static> {
|
pub struct Palette<V: 'static> {
|
||||||
|
@ -18,20 +14,19 @@ pub struct Palette<V: 'static> {
|
||||||
default_order: OrderMethod,
|
default_order: OrderMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn palette<V: 'static>(scroll_state: ScrollState) -> Palette<V> {
|
|
||||||
Palette {
|
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state,
|
|
||||||
input_placeholder: "Find something...",
|
|
||||||
empty_string: "No items found.",
|
|
||||||
items: vec![],
|
|
||||||
default_order: OrderMethod::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> Palette<V> {
|
impl<V: 'static> Palette<V> {
|
||||||
pub fn items(mut self, mut items: Vec<PaletteItem>) -> Self {
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
items.sort_by_key(|item| item.label);
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
input_placeholder: "Find something...",
|
||||||
|
empty_string: "No items found.",
|
||||||
|
items: vec![],
|
||||||
|
default_order: OrderMethod::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
|
||||||
self.items = items;
|
self.items = items;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -55,49 +50,33 @@ impl<V: 'static> Palette<V> {
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
div()
|
v_stack()
|
||||||
.w_96()
|
.w_96()
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.fill(theme.lowest.base.default.background)
|
.fill(theme.lowest.base.default.background)
|
||||||
.border()
|
.border()
|
||||||
.border_color(theme.lowest.base.default.border)
|
.border_color(theme.lowest.base.default.border)
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_stack()
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(
|
.child(v_stack().py_0p5().px_1().child(
|
||||||
div().py_0p5().px_1().flex().flex_col().child(
|
div().px_2().py_0p5().child(
|
||||||
div().px_2().py_0p5().child(
|
Label::new(self.input_placeholder).color(LabelColor::Placeholder),
|
||||||
label(self.input_placeholder).color(LabelColor::Placeholder),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
.child(div().h_px().w_full().fill(theme.lowest.base.default.border))
|
.child(div().h_px().w_full().fill(theme.lowest.base.default.border))
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_stack()
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
.px_1()
|
.px_1()
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.grow()
|
.grow()
|
||||||
.max_h_96()
|
.max_h_96()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
.children(
|
.children(
|
||||||
vec![if self.items.is_empty() {
|
vec![if self.items.is_empty() {
|
||||||
Some(
|
Some(h_stack().justify_between().px_2().py_1().child(
|
||||||
div()
|
Label::new(self.empty_string).color(LabelColor::Muted),
|
||||||
.flex()
|
))
|
||||||
.flex_row()
|
|
||||||
.justify_between()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.child(
|
|
||||||
label(self.empty_string).color(LabelColor::Muted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}]
|
}]
|
||||||
|
@ -105,9 +84,7 @@ impl<V: 'static> Palette<V> {
|
||||||
.flatten(),
|
.flatten(),
|
||||||
)
|
)
|
||||||
.children(self.items.iter().map(|item| {
|
.children(self.items.iter().map(|item| {
|
||||||
div()
|
h_stack()
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
|
@ -116,9 +93,52 @@ impl<V: 'static> Palette<V> {
|
||||||
.fill(theme.lowest.base.hovered.background)
|
.fill(theme.lowest.base.hovered.background)
|
||||||
.active()
|
.active()
|
||||||
.fill(theme.lowest.base.pressed.background)
|
.fill(theme.lowest.base.pressed.background)
|
||||||
.child(palette_item(item.label, item.keybinding))
|
.child(
|
||||||
|
PaletteItem::new(item.label)
|
||||||
|
.keybinding(item.keybinding.clone()),
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct PaletteItem {
|
||||||
|
pub label: &'static str,
|
||||||
|
pub keybinding: Option<Keybinding>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaletteItem {
|
||||||
|
pub fn new(label: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
keybinding: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(mut self, label: &'static str) -> Self {
|
||||||
|
self.label = label;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keybinding<K>(mut self, keybinding: K) -> Self
|
||||||
|
where
|
||||||
|
K: Into<Option<Keybinding>>,
|
||||||
|
{
|
||||||
|
self.keybinding = keybinding.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.grow()
|
||||||
|
.justify_between()
|
||||||
|
.child(Label::new(self.label))
|
||||||
|
.children(self.keybinding.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
use crate::theme::theme;
|
|
||||||
use crate::{label, LabelColor, LabelSize};
|
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement};
|
|
||||||
use gpui2::{ParentElement, ViewContext};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
|
||||||
pub struct PaletteItem {
|
|
||||||
pub label: &'static str,
|
|
||||||
pub keybinding: Option<&'static str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn palette_item(label: &'static str, keybinding: Option<&'static str>) -> PaletteItem {
|
|
||||||
PaletteItem { label, keybinding }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PaletteItem {
|
|
||||||
pub fn label(mut self, label: &'static str) -> Self {
|
|
||||||
self.label = label;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keybinding(mut self, keybinding: Option<&'static str>) -> Self {
|
|
||||||
self.keybinding = keybinding;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
|
|
||||||
let keybinding_label = match self.keybinding {
|
|
||||||
Some(keybind) => label(keybind)
|
|
||||||
.color(LabelColor::Muted)
|
|
||||||
.size(LabelSize::Small),
|
|
||||||
None => label(""),
|
|
||||||
};
|
|
||||||
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.grow()
|
|
||||||
.justify_between()
|
|
||||||
.child(label(self.label))
|
|
||||||
.child(
|
|
||||||
self.keybinding
|
|
||||||
.map(|_| {
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.px_1()
|
|
||||||
.py_0()
|
|
||||||
.my_0p5()
|
|
||||||
.rounded_md()
|
|
||||||
.text_sm()
|
|
||||||
.fill(theme.lowest.on.default.background)
|
|
||||||
.child(keybinding_label)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| div()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
146
crates/ui/src/components/panel.rs
Normal file
146
crates/ui/src/components/panel.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use gpui2::geometry::AbsoluteLength;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{theme, token, v_stack};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub enum PanelAllowedSides {
|
||||||
|
LeftOnly,
|
||||||
|
RightOnly,
|
||||||
|
BottomOnly,
|
||||||
|
#[default]
|
||||||
|
LeftAndRight,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelAllowedSides {
|
||||||
|
/// Return a `HashSet` that contains the allowable `PanelSide`s.
|
||||||
|
pub fn allowed_sides(&self) -> HashSet<PanelSide> {
|
||||||
|
match self {
|
||||||
|
Self::LeftOnly => HashSet::from_iter([PanelSide::Left]),
|
||||||
|
Self::RightOnly => HashSet::from_iter([PanelSide::Right]),
|
||||||
|
Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]),
|
||||||
|
Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]),
|
||||||
|
Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub enum PanelSide {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Panel<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
current_side: PanelSide,
|
||||||
|
/// Defaults to PanelAllowedSides::LeftAndRight
|
||||||
|
allowed_sides: PanelAllowedSides,
|
||||||
|
initial_width: AbsoluteLength,
|
||||||
|
width: Option<AbsoluteLength>,
|
||||||
|
children: HackyChildren<V>,
|
||||||
|
payload: HackyChildrenPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> Panel<V> {
|
||||||
|
pub fn new(
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
children: HackyChildren<V>,
|
||||||
|
payload: HackyChildrenPayload,
|
||||||
|
) -> Self {
|
||||||
|
let token = token();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
current_side: PanelSide::default(),
|
||||||
|
allowed_sides: PanelAllowedSides::default(),
|
||||||
|
initial_width: token.default_panel_size,
|
||||||
|
width: None,
|
||||||
|
children,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self {
|
||||||
|
self.initial_width = initial_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(mut self, width: AbsoluteLength) -> Self {
|
||||||
|
self.width = Some(width);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self {
|
||||||
|
self.allowed_sides = allowed_sides;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn side(mut self, side: PanelSide) -> Self {
|
||||||
|
let allowed_sides = self.allowed_sides.allowed_sides();
|
||||||
|
|
||||||
|
if allowed_sides.contains(&side) {
|
||||||
|
self.current_side = side;
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"The panel side {:?} was not added as allowed before it was set.",
|
||||||
|
side
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let token = token();
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let panel_base;
|
||||||
|
let current_width = if let Some(width) = self.width {
|
||||||
|
width
|
||||||
|
} else {
|
||||||
|
self.initial_width
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.current_side {
|
||||||
|
PanelSide::Left => {
|
||||||
|
panel_base = v_stack()
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
|
.h_full()
|
||||||
|
.w(current_width)
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
|
.border_r()
|
||||||
|
.border_color(theme.middle.base.default.border);
|
||||||
|
}
|
||||||
|
PanelSide::Right => {
|
||||||
|
panel_base = v_stack()
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
|
.h_full()
|
||||||
|
.w(current_width)
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
|
.border_r()
|
||||||
|
.border_color(theme.middle.base.default.border);
|
||||||
|
}
|
||||||
|
PanelSide::Bottom => {
|
||||||
|
panel_base = v_stack()
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
|
.w_full()
|
||||||
|
.h(current_width)
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
|
.border_r()
|
||||||
|
.border_color(theme.middle.base.default.border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panel_base.children_any((self.children)(cx, self.payload.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
132
crates/ui/src/components/panes.rs
Normal file
132
crates/ui/src/components/panes.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use gpui2::geometry::{Length, Size};
|
||||||
|
use gpui2::{hsla, Hsla};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::theme;
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq)]
|
||||||
|
pub enum SplitDirection {
|
||||||
|
#[default]
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Pane<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
size: Size<Length>,
|
||||||
|
fill: Hsla,
|
||||||
|
children: HackyChildren<V>,
|
||||||
|
payload: HackyChildrenPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> Pane<V> {
|
||||||
|
pub fn new(
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
size: Size<Length>,
|
||||||
|
children: HackyChildren<V>,
|
||||||
|
payload: HackyChildrenPayload,
|
||||||
|
) -> Self {
|
||||||
|
// Fill is only here for debugging purposes, remove before release
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
size,
|
||||||
|
fill: hsla(0.3, 0.3, 0.3, 1.),
|
||||||
|
// fill: system_color.transparent,
|
||||||
|
children,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(mut self, fill: Hsla) -> Self {
|
||||||
|
self.fill = fill;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_initial()
|
||||||
|
.fill(self.fill)
|
||||||
|
.w(self.size.width)
|
||||||
|
.h(self.size.height)
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
|
.children_any((self.children)(cx, self.payload.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct PaneGroup<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
groups: Vec<PaneGroup<V>>,
|
||||||
|
panes: Vec<Pane<V>>,
|
||||||
|
split_direction: SplitDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> PaneGroup<V> {
|
||||||
|
pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
groups,
|
||||||
|
panes: Vec::new(),
|
||||||
|
split_direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
groups: Vec::new(),
|
||||||
|
panes,
|
||||||
|
split_direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
if !self.panes.is_empty() {
|
||||||
|
let el = div()
|
||||||
|
.flex()
|
||||||
|
.flex_1()
|
||||||
|
.gap_px()
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
|
.fill(theme.lowest.base.default.background)
|
||||||
|
.children(self.panes.iter_mut().map(|pane| pane.render(view, cx)));
|
||||||
|
|
||||||
|
if self.split_direction == SplitDirection::Horizontal {
|
||||||
|
return el;
|
||||||
|
} else {
|
||||||
|
return el.flex_col();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.groups.is_empty() {
|
||||||
|
let el = div()
|
||||||
|
.flex()
|
||||||
|
.flex_1()
|
||||||
|
.gap_px()
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
|
.fill(theme.lowest.base.default.background)
|
||||||
|
.children(self.groups.iter_mut().map(|group| group.render(view, cx)));
|
||||||
|
|
||||||
|
if self.split_direction == SplitDirection::Horizontal {
|
||||||
|
return el;
|
||||||
|
} else {
|
||||||
|
return el.flex_col();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
66
crates/ui/src/components/player_stack.rs
Normal file
66
crates/ui/src/components/player_stack.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{Avatar, Facepile, PlayerWithCallStatus};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct PlayerStack {
|
||||||
|
player_with_call_status: PlayerWithCallStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerStack {
|
||||||
|
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
|
||||||
|
Self {
|
||||||
|
player_with_call_status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
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_1()
|
||||||
|
.rounded_bl_sm()
|
||||||
|
.rounded_br_sm()
|
||||||
|
.fill(player.cursor_color(cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.h_6()
|
||||||
|
.px_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().neg_mr_1().child(Facepile::new(followers.into_iter()))
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +1,87 @@
|
||||||
use crate::{
|
|
||||||
input, list, list_section_header, prelude::*, static_project_panel_project_items,
|
|
||||||
static_project_panel_single_items, theme,
|
|
||||||
};
|
|
||||||
|
|
||||||
use gpui2::{
|
|
||||||
elements::{div, div::ScrollState},
|
|
||||||
style::StyleHelpers,
|
|
||||||
ParentElement, ViewContext,
|
|
||||||
};
|
|
||||||
use gpui2::{Element, IntoElement};
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{
|
||||||
|
static_project_panel_project_items, static_project_panel_single_items, theme, Input, List,
|
||||||
|
ListHeader, Panel, PanelSide, Theme,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct ProjectPanel<V: 'static> {
|
pub struct ProjectPanel<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
}
|
current_side: PanelSide,
|
||||||
|
|
||||||
pub fn project_panel<V: 'static>(scroll_state: ScrollState) -> ProjectPanel<V> {
|
|
||||||
ProjectPanel {
|
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> ProjectPanel<V> {
|
impl<V: 'static> ProjectPanel<V> {
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
let theme = theme(cx);
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
current_side: PanelSide::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div()
|
pub fn side(mut self, side: PanelSide) -> Self {
|
||||||
.w_56()
|
self.current_side = side;
|
||||||
.h_full()
|
self
|
||||||
.flex()
|
}
|
||||||
.flex_col()
|
|
||||||
.fill(theme.middle.base.default.background)
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
.child(
|
struct PanelPayload {
|
||||||
div()
|
pub theme: Arc<Theme>,
|
||||||
.w_56()
|
pub scroll_state: ScrollState,
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::new(
|
||||||
|
self.scroll_state.clone(),
|
||||||
|
|_, payload| {
|
||||||
|
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
|
||||||
|
|
||||||
|
let theme = payload.theme.clone();
|
||||||
|
|
||||||
|
vec![div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.w_56()
|
||||||
|
.h_full()
|
||||||
|
.px_2()
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
.child(
|
.child(
|
||||||
list(static_project_panel_single_items())
|
div()
|
||||||
.header(list_section_header("FILES").set_toggle(ToggleState::Toggled))
|
.w_56()
|
||||||
.empty_message("No files in directory")
|
.flex()
|
||||||
.set_toggle(ToggleState::Toggled),
|
.flex_col()
|
||||||
|
.overflow_y_scroll(payload.scroll_state.clone())
|
||||||
|
.child(
|
||||||
|
List::new(static_project_panel_single_items())
|
||||||
|
.header(
|
||||||
|
ListHeader::new("FILES").set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
.empty_message("No files in directory")
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
List::new(static_project_panel_project_items())
|
||||||
|
.header(
|
||||||
|
ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled),
|
||||||
|
)
|
||||||
|
.empty_message("No folders in directory")
|
||||||
|
.set_toggle(ToggleState::Toggled),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
list(static_project_panel_project_items())
|
Input::new("Find something...")
|
||||||
.header(list_section_header("PROJECT").set_toggle(ToggleState::Toggled))
|
.value("buffe".to_string())
|
||||||
.empty_message("No folders in directory")
|
.state(InteractionState::Focused),
|
||||||
.set_toggle(ToggleState::Toggled),
|
)
|
||||||
),
|
.into_any()]
|
||||||
)
|
},
|
||||||
.child(
|
Box::new(PanelPayload {
|
||||||
input("Find something...")
|
theme: theme(cx),
|
||||||
.value("buffe".to_string())
|
scroll_state: self.scroll_state.clone(),
|
||||||
.state(InteractionState::Focused),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use gpui2::style::StyleHelpers;
|
use crate::prelude::*;
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme::{theme, Theme};
|
use crate::theme::{theme, Theme};
|
||||||
use crate::{icon_button, text_button, tool_divider, IconAsset};
|
use crate::{Button, Icon, IconButton, IconColor, ToolDivider};
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
pub enum Tool {
|
pub enum Tool {
|
||||||
|
@ -40,16 +37,16 @@ pub struct StatusBar<V: 'static> {
|
||||||
bottom_tools: Option<ToolGroup>,
|
bottom_tools: Option<ToolGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status_bar<V: 'static>() -> StatusBar<V> {
|
|
||||||
StatusBar {
|
|
||||||
view_type: PhantomData,
|
|
||||||
left_tools: None,
|
|
||||||
right_tools: None,
|
|
||||||
bottom_tools: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> StatusBar<V> {
|
impl<V: 'static> StatusBar<V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
left_tools: None,
|
||||||
|
right_tools: None,
|
||||||
|
bottom_tools: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn left_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
pub fn left_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||||
self.left_tools = {
|
self.left_tools = {
|
||||||
let mut tools = vec![tool];
|
let mut tools = vec![tool];
|
||||||
|
@ -106,10 +103,10 @@ impl<V: 'static> StatusBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(icon_button().icon(IconAsset::FileTree))
|
.child(IconButton::new(Icon::FileTree).color(IconColor::Accent))
|
||||||
.child(icon_button().icon(IconAsset::Hash))
|
.child(IconButton::new(Icon::Hash))
|
||||||
.child(tool_divider())
|
.child(ToolDivider::new())
|
||||||
.child(icon_button().icon(IconAsset::XCircle))
|
.child(IconButton::new(Icon::XCircle))
|
||||||
}
|
}
|
||||||
fn right_tools(&self, theme: &Theme) -> impl Element<V> {
|
fn right_tools(&self, theme: &Theme) -> impl Element<V> {
|
||||||
div()
|
div()
|
||||||
|
@ -121,27 +118,27 @@ impl<V: 'static> StatusBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(text_button("116:25"))
|
.child(Button::new("116:25"))
|
||||||
.child(text_button("Rust")),
|
.child(Button::new("Rust")),
|
||||||
)
|
)
|
||||||
.child(tool_divider())
|
.child(ToolDivider::new())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(icon_button().icon(IconAsset::Copilot))
|
.child(IconButton::new(Icon::Copilot))
|
||||||
.child(icon_button().icon(IconAsset::Envelope)),
|
.child(IconButton::new(Icon::Envelope)),
|
||||||
)
|
)
|
||||||
.child(tool_divider())
|
.child(ToolDivider::new())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(icon_button().icon(IconAsset::Terminal))
|
.child(IconButton::new(Icon::Terminal))
|
||||||
.child(icon_button().icon(IconAsset::MessageBubbles))
|
.child(IconButton::new(Icon::MessageBubbles))
|
||||||
.child(icon_button().icon(IconAsset::Ai)),
|
.child(IconButton::new(Icon::Ai)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,96 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor};
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
title: &'static str,
|
title: String,
|
||||||
enabled: bool,
|
icon: Option<Icon>,
|
||||||
}
|
current: bool,
|
||||||
|
dirty: bool,
|
||||||
pub fn tab<V: 'static>(title: &'static str, enabled: bool) -> impl Element<V> {
|
fs_status: FileSystemStatus,
|
||||||
Tab { title, enabled }
|
git_status: GitStatus,
|
||||||
|
diagnostic_status: DiagnosticStatus,
|
||||||
|
close_side: IconSide,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
title: "untitled".to_string(),
|
||||||
|
icon: None,
|
||||||
|
current: false,
|
||||||
|
dirty: false,
|
||||||
|
fs_status: FileSystemStatus::None,
|
||||||
|
git_status: GitStatus::None,
|
||||||
|
diagnostic_status: DiagnosticStatus::None,
|
||||||
|
close_side: IconSide::Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(mut self, current: bool) -> Self {
|
||||||
|
self.current = current;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(mut self, title: String) -> Self {
|
||||||
|
self.title = title;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon<I>(mut self, icon: I) -> Self
|
||||||
|
where
|
||||||
|
I: Into<Option<Icon>>,
|
||||||
|
{
|
||||||
|
self.icon = icon.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dirty(mut self, dirty: bool) -> Self {
|
||||||
|
self.dirty = dirty;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
|
||||||
|
self.fs_status = fs_status;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn git_status(mut self, git_status: GitStatus) -> Self {
|
||||||
|
self.git_status = git_status;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
|
||||||
|
self.diagnostic_status = diagnostic_status;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_side(mut self, close_side: IconSide) -> Self {
|
||||||
|
self.close_side = close_side;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
|
||||||
|
let is_deleted = self.fs_status == FileSystemStatus::Deleted;
|
||||||
|
|
||||||
|
let label = match (self.git_status, is_deleted) {
|
||||||
|
(_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone())
|
||||||
|
.color(LabelColor::Hidden)
|
||||||
|
.set_strikethrough(true),
|
||||||
|
(GitStatus::None, false) => Label::new(self.title.clone()),
|
||||||
|
(GitStatus::Created, false) => {
|
||||||
|
Label::new(self.title.clone()).color(LabelColor::Created)
|
||||||
|
}
|
||||||
|
(GitStatus::Modified, false) => {
|
||||||
|
Label::new(self.title.clone()).color(LabelColor::Modified)
|
||||||
|
}
|
||||||
|
(GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent),
|
||||||
|
(GitStatus::Conflict, false) => Label::new(self.title.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let close_icon = IconElement::new(Icon::Close).color(IconColor::Muted);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
@ -24,33 +98,34 @@ impl Tab {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.rounded_lg()
|
.fill(if self.current {
|
||||||
.fill(if self.enabled {
|
|
||||||
theme.highest.on.default.background
|
|
||||||
} else {
|
|
||||||
theme.highest.base.default.background
|
theme.highest.base.default.background
|
||||||
})
|
|
||||||
.hover()
|
|
||||||
.fill(if self.enabled {
|
|
||||||
theme.highest.on.hovered.background
|
|
||||||
} else {
|
} else {
|
||||||
theme.highest.base.hovered.background
|
theme.middle.base.default.background
|
||||||
})
|
|
||||||
.active()
|
|
||||||
.fill(if self.enabled {
|
|
||||||
theme.highest.on.pressed.background
|
|
||||||
} else {
|
|
||||||
theme.highest.base.pressed.background
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.px_1()
|
||||||
.text_color(if self.enabled {
|
.flex()
|
||||||
theme.highest.base.default.foreground
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.children(has_fs_conflict.then(|| {
|
||||||
|
IconElement::new(Icon::ExclamationTriangle)
|
||||||
|
.size(crate::IconSize::Small)
|
||||||
|
.color(IconColor::Warning)
|
||||||
|
}))
|
||||||
|
.children(self.icon.map(IconElement::new))
|
||||||
|
.children(if self.close_side == IconSide::Left {
|
||||||
|
Some(close_icon.clone())
|
||||||
} else {
|
} else {
|
||||||
theme.highest.variant.default.foreground
|
None
|
||||||
})
|
})
|
||||||
.child(self.title),
|
.child(label)
|
||||||
|
.children(if self.close_side == IconSide::Right {
|
||||||
|
Some(close_icon)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use gpui2::elements::div::ScrollState;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
use crate::{theme, Icon, IconButton, Tab};
|
||||||
use gpui2::{elements::div, IntoElement};
|
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::prelude::InteractionState;
|
|
||||||
use crate::theme::theme;
|
|
||||||
use crate::{icon_button, tab, IconAsset};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct TabBar<V: 'static> {
|
pub struct TabBar<V: 'static> {
|
||||||
|
@ -15,14 +9,14 @@ pub struct TabBar<V: 'static> {
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_bar<V: 'static>(scroll_state: ScrollState) -> TabBar<V> {
|
|
||||||
TabBar {
|
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> TabBar<V> {
|
impl<V: 'static> TabBar<V> {
|
||||||
|
pub fn new(scroll_state: ScrollState) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let can_navigate_back = true;
|
let can_navigate_back = true;
|
||||||
|
@ -30,6 +24,7 @@ impl<V: 'static> TabBar<V> {
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
// Left Side
|
// Left Side
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
@ -44,12 +39,11 @@ impl<V: 'static> TabBar<V> {
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(
|
.child(
|
||||||
icon_button()
|
IconButton::new(Icon::ArrowLeft)
|
||||||
.icon(IconAsset::ArrowLeft)
|
|
||||||
.state(InteractionState::Enabled.if_enabled(can_navigate_back)),
|
.state(InteractionState::Enabled.if_enabled(can_navigate_back)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
icon_button().icon(IconAsset::ArrowRight).state(
|
IconButton::new(Icon::ArrowRight).state(
|
||||||
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -59,17 +53,52 @@ impl<V: 'static> TabBar<V> {
|
||||||
div().w_0().flex_1().h_full().child(
|
div().w_0().flex_1().h_full().child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_1()
|
|
||||||
.overflow_x_scroll(self.scroll_state.clone())
|
.overflow_x_scroll(self.scroll_state.clone())
|
||||||
.child(tab("Cargo.toml", false))
|
.child(
|
||||||
.child(tab("Channels Panel", true))
|
Tab::new()
|
||||||
.child(tab("channels_panel.rs", false))
|
.title("Cargo.toml".to_string())
|
||||||
.child(tab("workspace.rs", false))
|
.current(false)
|
||||||
.child(tab("icon_button.rs", false))
|
.git_status(GitStatus::Modified),
|
||||||
.child(tab("storybook.rs", false))
|
)
|
||||||
.child(tab("theme.rs", false))
|
.child(
|
||||||
.child(tab("theme_registry.rs", false))
|
Tab::new()
|
||||||
.child(tab("styleable_helpers.rs", false)),
|
.title("Channels Panel".to_string())
|
||||||
|
.current(false),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("channels_panel.rs".to_string())
|
||||||
|
.current(true)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("workspace.rs".to_string())
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("icon_button.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("storybook.rs".to_string())
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Created),
|
||||||
|
)
|
||||||
|
.child(Tab::new().title("theme.rs".to_string()).current(false))
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("theme_registry.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("styleable_helpers.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// Right Side
|
// Right Side
|
||||||
|
@ -85,8 +114,8 @@ impl<V: 'static> TabBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(icon_button().icon(IconAsset::Plus))
|
.child(IconButton::new(Icon::Plus))
|
||||||
.child(icon_button().icon(IconAsset::Split)),
|
.child(IconButton::new(Icon::Split)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
77
crates/ui/src/components/terminal.rs
Normal file
77
crates/ui/src/components/terminal.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
use gpui2::geometry::{relative, rems, Size};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{theme, Icon, IconButton, Pane, Tab};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Terminal {}
|
||||||
|
|
||||||
|
impl Terminal {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let can_navigate_back = true;
|
||||||
|
let can_navigate_forward = false;
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.child(
|
||||||
|
// Terminal Tabs.
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
|
.child(
|
||||||
|
div().px_1().flex().flex_none().gap_2().child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_px()
|
||||||
|
.child(
|
||||||
|
IconButton::new(Icon::ArrowLeft).state(
|
||||||
|
InteractionState::Enabled.if_enabled(can_navigate_back),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(IconButton::new(Icon::ArrowRight).state(
|
||||||
|
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().w_0().flex_1().h_full().child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("zed — fish".to_string())
|
||||||
|
.icon(Icon::Terminal)
|
||||||
|
.close_side(IconSide::Right)
|
||||||
|
.current(true),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Tab::new()
|
||||||
|
.title("zed — fish".to_string())
|
||||||
|
.icon(Icon::Terminal)
|
||||||
|
.close_side(IconSide::Right)
|
||||||
|
.current(false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Terminal Pane.
|
||||||
|
.child(Pane::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: rems(36.).into(),
|
||||||
|
},
|
||||||
|
|_, _| vec![],
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +1,41 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::prelude::Shape;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
avatar, follow_group, icon_button, text_button, theme, tool_divider, traffic_lights, IconAsset,
|
static_players_with_call_status, theme, Avatar, Button, Icon, IconButton, IconColor,
|
||||||
IconColor,
|
PlayerStack, ToolDivider, TrafficLights,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct TitleBar<V: 'static> {
|
pub struct TitleBar<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
}
|
is_active: Arc<AtomicBool>,
|
||||||
|
|
||||||
pub fn title_bar<V: 'static>() -> TitleBar<V> {
|
|
||||||
TitleBar {
|
|
||||||
view_type: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> TitleBar<V> {
|
impl<V: 'static> TitleBar<V> {
|
||||||
|
pub fn new(cx: &mut ViewContext<V>) -> 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 {
|
||||||
|
view_type: PhantomData,
|
||||||
|
is_active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let player_list = vec![
|
let has_focus = cx.window_is_active();
|
||||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
|
||||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
let player_list = static_players_with_call_status().into_iter();
|
||||||
];
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -43,20 +51,17 @@ impl<V: 'static> TitleBar<V> {
|
||||||
.h_full()
|
.h_full()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.px_2()
|
.px_2()
|
||||||
.child(traffic_lights())
|
.child(TrafficLights::new().window_has_focus(has_focus))
|
||||||
// === Project Info === //
|
// === Project Info === //
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(text_button("maxbrunsfeld"))
|
.child(Button::new("zed"))
|
||||||
.child(text_button("zed"))
|
.child(Button::new("nate/gpui2-ui-components")),
|
||||||
.child(text_button("nate/gpui2-ui-components")),
|
|
||||||
)
|
)
|
||||||
.child(follow_group(player_list.clone()).player(0))
|
.children(player_list.map(|p| PlayerStack::new(p))),
|
||||||
.child(follow_group(player_list.clone()).player(1))
|
|
||||||
.child(follow_group(player_list.clone()).player(2)),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
@ -68,27 +73,23 @@ impl<V: 'static> TitleBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(icon_button().icon(IconAsset::FolderX))
|
.child(IconButton::new(Icon::FolderX))
|
||||||
.child(icon_button().icon(IconAsset::Close)),
|
.child(IconButton::new(Icon::Close)),
|
||||||
)
|
)
|
||||||
.child(tool_divider())
|
.child(ToolDivider::new())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.px_2()
|
.px_2()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(icon_button().icon(IconAsset::Mic))
|
.child(IconButton::new(Icon::Mic))
|
||||||
.child(icon_button().icon(IconAsset::AudioOn))
|
.child(IconButton::new(Icon::AudioOn))
|
||||||
.child(
|
.child(IconButton::new(Icon::Screen).color(IconColor::Accent)),
|
||||||
icon_button()
|
|
||||||
.icon(IconAsset::Screen)
|
|
||||||
.color(IconColor::Accent),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().px_2().flex().items_center().child(
|
div().px_2().flex().items_center().child(
|
||||||
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
|
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||||
.shape(Shape::RoundedRectangle),
|
.shape(Shape::RoundedRectangle),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
use crate::{theme, Breadcrumb, Icon, IconButton};
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::{breadcrumb, theme, IconAsset, IconButton};
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ToolbarItem {}
|
pub struct ToolbarItem {}
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element, Clone)]
|
||||||
pub struct Toolbar {
|
pub struct Toolbar {
|
||||||
items: Vec<ToolbarItem>,
|
items: Vec<ToolbarItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toolbar() -> Toolbar {
|
|
||||||
Toolbar { items: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Toolbar {
|
impl Toolbar {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { items: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
@ -23,13 +21,13 @@ impl Toolbar {
|
||||||
.p_2()
|
.p_2()
|
||||||
.flex()
|
.flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(breadcrumb())
|
.child(Breadcrumb::new())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.child(IconButton::new(IconAsset::InlayHint))
|
.child(IconButton::new(Icon::InlayHint))
|
||||||
.child(IconButton::new(IconAsset::MagnifyingGlass))
|
.child(IconButton::new(Icon::MagnifyingGlass))
|
||||||
.child(IconButton::new(IconAsset::MagicWand)),
|
.child(IconButton::new(Icon::MagicWand)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,78 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
use crate::{theme, token, SystemColor};
|
||||||
use gpui2::{Element, Hsla, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
#[derive(Clone, Copy)]
|
||||||
|
enum TrafficLightColor {
|
||||||
|
Red,
|
||||||
|
Yellow,
|
||||||
|
Green,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct TrafficLights {}
|
struct TrafficLight {
|
||||||
|
color: TrafficLightColor,
|
||||||
|
window_has_focus: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn traffic_lights() -> TrafficLights {
|
impl TrafficLight {
|
||||||
TrafficLights {}
|
fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
color,
|
||||||
|
window_has_focus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
let fill = match (self.window_has_focus, self.color) {
|
||||||
|
(true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red,
|
||||||
|
(true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow,
|
||||||
|
(true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green,
|
||||||
|
(false, _) => theme.lowest.base.active.background,
|
||||||
|
};
|
||||||
|
|
||||||
|
div().w_3().h_3().rounded_full().fill(fill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct TrafficLights {
|
||||||
|
window_has_focus: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrafficLights {
|
impl TrafficLights {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
window_has_focus: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
|
||||||
|
self.window_has_focus = window_has_focus;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
let token = token();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(traffic_light(theme.lowest.negative.default.foreground))
|
.child(TrafficLight::new(
|
||||||
.child(traffic_light(theme.lowest.warning.default.foreground))
|
TrafficLightColor::Red,
|
||||||
.child(traffic_light(theme.lowest.positive.default.foreground))
|
self.window_has_focus,
|
||||||
|
))
|
||||||
|
.child(TrafficLight::new(
|
||||||
|
TrafficLightColor::Yellow,
|
||||||
|
self.window_has_focus,
|
||||||
|
))
|
||||||
|
.child(TrafficLight::new(
|
||||||
|
TrafficLightColor::Green,
|
||||||
|
self.window_has_focus,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn traffic_light<V: 'static, C: Into<Hsla>>(fill: C) -> div::Div<V> {
|
|
||||||
div().w_3().h_3().rounded_full().fill(fill.into())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,30 +1,68 @@
|
||||||
use crate::{chat_panel, collab_panel, project_panel, status_bar, tab_bar, theme, title_bar};
|
use chrono::DateTime;
|
||||||
|
use gpui2::geometry::{relative, rems, Size};
|
||||||
|
|
||||||
use gpui2::{
|
use crate::prelude::*;
|
||||||
elements::{div, div::ScrollState},
|
use crate::{
|
||||||
style::StyleHelpers,
|
theme, v_stack, ChatMessage, ChatPanel, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide,
|
||||||
Element, IntoElement, ParentElement, ViewContext,
|
ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Element, Default)]
|
#[derive(Element, Default)]
|
||||||
struct WorkspaceElement {
|
pub struct WorkspaceElement {
|
||||||
project_panel_scroll_state: ScrollState,
|
left_panel_scroll_state: ScrollState,
|
||||||
collab_panel_scroll_state: ScrollState,
|
right_panel_scroll_state: ScrollState,
|
||||||
right_scroll_state: ScrollState,
|
|
||||||
tab_bar_scroll_state: ScrollState,
|
tab_bar_scroll_state: ScrollState,
|
||||||
palette_scroll_state: ScrollState,
|
bottom_panel_scroll_state: ScrollState,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn workspace<V: 'static>() -> impl Element<V> {
|
|
||||||
WorkspaceElement::default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceElement {
|
impl WorkspaceElement {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let temp_size = rems(36.).into();
|
||||||
|
|
||||||
|
let root_group = PaneGroup::new_groups(
|
||||||
|
vec![
|
||||||
|
PaneGroup::new_panes(
|
||||||
|
vec![
|
||||||
|
Pane::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: temp_size,
|
||||||
|
},
|
||||||
|
|_, _| vec![Terminal::new().into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
),
|
||||||
|
Pane::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: temp_size,
|
||||||
|
},
|
||||||
|
|_, _| vec![Terminal::new().into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SplitDirection::Vertical,
|
||||||
|
),
|
||||||
|
PaneGroup::new_panes(
|
||||||
|
vec![Pane::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: relative(1.).into(),
|
||||||
|
},
|
||||||
|
|_, _| vec![Terminal::new().into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
)],
|
||||||
|
SplitDirection::Vertical,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SplitDirection::Horizontal,
|
||||||
|
);
|
||||||
|
|
||||||
|
let theme = theme(cx).clone();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
// Elevation Level 0
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
@ -34,9 +72,7 @@ impl WorkspaceElement {
|
||||||
.items_start()
|
.items_start()
|
||||||
.text_color(theme.lowest.base.default.foreground)
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
.fill(theme.lowest.base.default.background)
|
.fill(theme.lowest.base.default.background)
|
||||||
.relative()
|
.child(TitleBar::new(cx))
|
||||||
// Elevation Level 1
|
|
||||||
.child(title_bar())
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
@ -44,37 +80,57 @@ impl WorkspaceElement {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(project_panel(self.project_panel_scroll_state.clone()))
|
.border_t()
|
||||||
.child(collab_panel(self.collab_panel_scroll_state.clone()))
|
.border_b()
|
||||||
|
.border_color(theme.lowest.base.default.border)
|
||||||
.child(
|
.child(
|
||||||
div()
|
ProjectPanel::new(self.left_panel_scroll_state.clone())
|
||||||
.h_full()
|
.side(PanelSide::Left),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_stack()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.fill(theme.highest.base.default.background)
|
.h_full()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(tab_bar(self.tab_bar_scroll_state.clone())),
|
// CSS Hack: Flex 1 has to have a set height to properly fill the space
|
||||||
|
// Or it will give you a height of 0
|
||||||
|
.h_px()
|
||||||
|
.child(root_group),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Panel::new(
|
||||||
|
self.bottom_panel_scroll_state.clone(),
|
||||||
|
|_, _| vec![Terminal::new().into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
)
|
||||||
|
.allowed_sides(PanelAllowedSides::BottomOnly)
|
||||||
|
.side(PanelSide::Bottom),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(chat_panel(self.right_scroll_state.clone())),
|
.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(),
|
||||||
|
),
|
||||||
|
])),
|
||||||
)
|
)
|
||||||
.child(status_bar())
|
.child(StatusBar::new())
|
||||||
// Elevation Level 3
|
|
||||||
// .child(
|
|
||||||
// div()
|
|
||||||
// .absolute()
|
|
||||||
// .top_0()
|
|
||||||
// .left_0()
|
|
||||||
// .size_full()
|
|
||||||
// .flex()
|
|
||||||
// .justify_center()
|
|
||||||
// .items_center()
|
|
||||||
// // .fill(theme.lowest.base.default.background)
|
|
||||||
// // Elevation Level 4
|
|
||||||
// .child(command_palette(self.palette_scroll_state.clone())),
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
mod avatar;
|
mod avatar;
|
||||||
|
mod button;
|
||||||
mod details;
|
mod details;
|
||||||
mod icon;
|
mod icon;
|
||||||
mod indicator;
|
|
||||||
mod input;
|
mod input;
|
||||||
mod label;
|
mod label;
|
||||||
mod text_button;
|
mod player;
|
||||||
|
mod stack;
|
||||||
mod tool_divider;
|
mod tool_divider;
|
||||||
|
|
||||||
pub use avatar::*;
|
pub use avatar::*;
|
||||||
|
pub use button::*;
|
||||||
pub use details::*;
|
pub use details::*;
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
pub use indicator::*;
|
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
pub use text_button::*;
|
pub use player::*;
|
||||||
|
pub use stack::*;
|
||||||
pub use tool_divider::*;
|
pub use tool_divider::*;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use gpui2::elements::img;
|
use gpui2::elements::img;
|
||||||
use gpui2::style::StyleHelpers;
|
use gpui2::ArcCow;
|
||||||
use gpui2::{ArcCow, Element, IntoElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
@ -11,14 +10,14 @@ pub struct Avatar {
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn avatar(src: impl Into<ArcCow<'static, str>>) -> Avatar {
|
|
||||||
Avatar {
|
|
||||||
src: src.into(),
|
|
||||||
shape: Shape::Circle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Avatar {
|
impl Avatar {
|
||||||
|
pub fn new(src: impl Into<ArcCow<'static, str>>) -> Self {
|
||||||
|
Self {
|
||||||
|
src: src.into(),
|
||||||
|
shape: Shape::Circle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn shape(mut self, shape: Shape) -> Self {
|
pub fn shape(mut self, shape: Shape) -> Self {
|
||||||
self.shape = shape;
|
self.shape = shape;
|
||||||
self
|
self
|
||||||
|
|
203
crates/ui/src/elements/button.rs
Normal file
203
crates/ui/src/elements/button.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui2::geometry::DefiniteLength;
|
||||||
|
use gpui2::platform::MouseButton;
|
||||||
|
use gpui2::{EventContext, Hsla, Interactive, WindowContext};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize};
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone, Copy)]
|
||||||
|
pub enum IconPosition {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, PartialEq)]
|
||||||
|
pub enum ButtonVariant {
|
||||||
|
#[default]
|
||||||
|
Ghost,
|
||||||
|
Filled,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ButtonHandlers<V> {
|
||||||
|
click: Option<Rc<dyn Fn(&mut V, &mut EventContext<V>)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> Default for ButtonHandlers<V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { click: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Button<V: 'static> {
|
||||||
|
label: String,
|
||||||
|
variant: ButtonVariant,
|
||||||
|
state: InteractionState,
|
||||||
|
icon: Option<Icon>,
|
||||||
|
icon_position: Option<IconPosition>,
|
||||||
|
width: Option<DefiniteLength>,
|
||||||
|
handlers: ButtonHandlers<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> Button<V> {
|
||||||
|
pub fn new<L>(label: L) -> Self
|
||||||
|
where
|
||||||
|
L: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
variant: Default::default(),
|
||||||
|
state: Default::default(),
|
||||||
|
icon: None,
|
||||||
|
icon_position: None,
|
||||||
|
width: Default::default(),
|
||||||
|
handlers: ButtonHandlers::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ghost<L>(label: L) -> Self
|
||||||
|
where
|
||||||
|
L: Into<String>,
|
||||||
|
{
|
||||||
|
Self::new(label).variant(ButtonVariant::Ghost)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
||||||
|
self.variant = variant;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(mut self, state: InteractionState) -> Self {
|
||||||
|
self.state = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: Icon) -> Self {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
|
||||||
|
if self.icon.is_none() {
|
||||||
|
panic!("An icon must be present if an icon_position is provided.");
|
||||||
|
}
|
||||||
|
self.icon_position = Some(icon_position);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
|
||||||
|
self.width = width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext<V>) + 'static) -> Self {
|
||||||
|
self.handlers.click = Some(Rc::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self, cx: &mut ViewContext<V>) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match (self.variant, self.state) {
|
||||||
|
(ButtonVariant::Ghost, InteractionState::Hovered) => {
|
||||||
|
theme.lowest.base.hovered.background
|
||||||
|
}
|
||||||
|
(ButtonVariant::Ghost, InteractionState::Active) => {
|
||||||
|
theme.lowest.base.pressed.background
|
||||||
|
}
|
||||||
|
(ButtonVariant::Filled, InteractionState::Enabled) => {
|
||||||
|
theme.lowest.on.default.background
|
||||||
|
}
|
||||||
|
(ButtonVariant::Filled, InteractionState::Hovered) => {
|
||||||
|
theme.lowest.on.hovered.background
|
||||||
|
}
|
||||||
|
(ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background,
|
||||||
|
(ButtonVariant::Filled, InteractionState::Disabled) => {
|
||||||
|
theme.lowest.on.disabled.background
|
||||||
|
}
|
||||||
|
_ => system_color.transparent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_color(&self) -> LabelColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => LabelColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_color(&self) -> IconColor {
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Disabled => IconColor::Disabled,
|
||||||
|
_ => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn border_color(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
InteractionState::Focused => theme.lowest.accent.default.border,
|
||||||
|
_ => system_color.transparent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_label(&self) -> Label {
|
||||||
|
Label::new(self.label.clone())
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(self.label_color())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_icon(&self, icon_color: IconColor) -> Option<IconElement> {
|
||||||
|
self.icon.map(|i| IconElement::new(i).color(icon_color))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let icon_color = self.icon_color();
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
let border_color = self.border_color(cx);
|
||||||
|
|
||||||
|
let mut el = h_stack()
|
||||||
|
.h_6()
|
||||||
|
.px_1()
|
||||||
|
.items_center()
|
||||||
|
.rounded_md()
|
||||||
|
.border()
|
||||||
|
.border_color(border_color)
|
||||||
|
.fill(self.background_color(cx));
|
||||||
|
|
||||||
|
match (self.icon, self.icon_position) {
|
||||||
|
(Some(_), Some(IconPosition::Left)) => {
|
||||||
|
el = el
|
||||||
|
.gap_1()
|
||||||
|
.child(self.render_label())
|
||||||
|
.children(self.render_icon(icon_color))
|
||||||
|
}
|
||||||
|
(Some(_), Some(IconPosition::Right)) => {
|
||||||
|
el = el
|
||||||
|
.gap_1()
|
||||||
|
.children(self.render_icon(icon_color))
|
||||||
|
.child(self.render_label())
|
||||||
|
}
|
||||||
|
(_, _) => el = el.child(self.render_label()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(width) = self.width {
|
||||||
|
el = el.w(width).justify_center();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(click_handler) = self.handlers.click.clone() {
|
||||||
|
el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| {
|
||||||
|
click_handler(view, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
el
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,4 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
|
||||||
#[derive(Element, Clone)]
|
#[derive(Element, Clone)]
|
||||||
|
@ -10,11 +7,11 @@ pub struct Details {
|
||||||
meta: Option<&'static str>,
|
meta: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn details(text: &'static str) -> Details {
|
|
||||||
Details { text, meta: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Details {
|
impl Details {
|
||||||
|
pub fn new(text: &'static str) -> Self {
|
||||||
|
Self { text, meta: None }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn meta_text(mut self, meta: &'static str) -> Self {
|
pub fn meta_text(mut self, meta: &'static str) -> Self {
|
||||||
self.meta = Some(meta);
|
self.meta = Some(meta);
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gpui2::elements::svg;
|
||||||
|
use gpui2::Hsla;
|
||||||
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use gpui2::elements::svg;
|
|
||||||
use gpui2::style::StyleHelpers;
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
use gpui2::{Element, ViewContext};
|
pub enum IconSize {
|
||||||
use gpui2::{Hsla, IntoElement};
|
Small,
|
||||||
|
#[default]
|
||||||
|
Large,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
pub enum IconColor {
|
pub enum IconColor {
|
||||||
|
@ -37,8 +45,8 @@ impl IconColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone, EnumIter)]
|
||||||
pub enum IconAsset {
|
pub enum Icon {
|
||||||
Ai,
|
Ai,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
@ -53,6 +61,7 @@ pub enum IconAsset {
|
||||||
Close,
|
Close,
|
||||||
ExclamationTriangle,
|
ExclamationTriangle,
|
||||||
File,
|
File,
|
||||||
|
FileGeneric,
|
||||||
FileDoc,
|
FileDoc,
|
||||||
FileGit,
|
FileGit,
|
||||||
FileLock,
|
FileLock,
|
||||||
|
@ -67,89 +76,106 @@ pub enum IconAsset {
|
||||||
InlayHint,
|
InlayHint,
|
||||||
MagicWand,
|
MagicWand,
|
||||||
MagnifyingGlass,
|
MagnifyingGlass,
|
||||||
|
Maximize,
|
||||||
|
Menu,
|
||||||
MessageBubbles,
|
MessageBubbles,
|
||||||
Mic,
|
Mic,
|
||||||
MicMute,
|
MicMute,
|
||||||
Plus,
|
Plus,
|
||||||
|
Quote,
|
||||||
Screen,
|
Screen,
|
||||||
Split,
|
Split,
|
||||||
|
SplitMessage,
|
||||||
Terminal,
|
Terminal,
|
||||||
XCircle,
|
XCircle,
|
||||||
Copilot,
|
Copilot,
|
||||||
Envelope,
|
Envelope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconAsset {
|
impl Icon {
|
||||||
pub fn path(self) -> &'static str {
|
pub fn path(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
IconAsset::Ai => "icons/ai.svg",
|
Icon::Ai => "icons/ai.svg",
|
||||||
IconAsset::ArrowLeft => "icons/arrow_left.svg",
|
Icon::ArrowLeft => "icons/arrow_left.svg",
|
||||||
IconAsset::ArrowRight => "icons/arrow_right.svg",
|
Icon::ArrowRight => "icons/arrow_right.svg",
|
||||||
IconAsset::ArrowUpRight => "icons/arrow_up_right.svg",
|
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
||||||
IconAsset::AudioOff => "icons/speaker-off.svg",
|
Icon::AudioOff => "icons/speaker-off.svg",
|
||||||
IconAsset::AudioOn => "icons/speaker-loud.svg",
|
Icon::AudioOn => "icons/speaker-loud.svg",
|
||||||
IconAsset::Bolt => "icons/bolt.svg",
|
Icon::Bolt => "icons/bolt.svg",
|
||||||
IconAsset::ChevronDown => "icons/chevron_down.svg",
|
Icon::ChevronDown => "icons/chevron_down.svg",
|
||||||
IconAsset::ChevronLeft => "icons/chevron_left.svg",
|
Icon::ChevronLeft => "icons/chevron_left.svg",
|
||||||
IconAsset::ChevronRight => "icons/chevron_right.svg",
|
Icon::ChevronRight => "icons/chevron_right.svg",
|
||||||
IconAsset::ChevronUp => "icons/chevron_up.svg",
|
Icon::ChevronUp => "icons/chevron_up.svg",
|
||||||
IconAsset::Close => "icons/x.svg",
|
Icon::Close => "icons/x.svg",
|
||||||
IconAsset::ExclamationTriangle => "icons/warning.svg",
|
Icon::ExclamationTriangle => "icons/warning.svg",
|
||||||
IconAsset::File => "icons/file_icons/file.svg",
|
Icon::File => "icons/file.svg",
|
||||||
IconAsset::FileDoc => "icons/file_icons/book.svg",
|
Icon::FileGeneric => "icons/file_icons/file.svg",
|
||||||
IconAsset::FileGit => "icons/file_icons/git.svg",
|
Icon::FileDoc => "icons/file_icons/book.svg",
|
||||||
IconAsset::FileLock => "icons/file_icons/lock.svg",
|
Icon::FileGit => "icons/file_icons/git.svg",
|
||||||
IconAsset::FileRust => "icons/file_icons/rust.svg",
|
Icon::FileLock => "icons/file_icons/lock.svg",
|
||||||
IconAsset::FileToml => "icons/file_icons/toml.svg",
|
Icon::FileRust => "icons/file_icons/rust.svg",
|
||||||
IconAsset::FileTree => "icons/project.svg",
|
Icon::FileToml => "icons/file_icons/toml.svg",
|
||||||
IconAsset::Folder => "icons/file_icons/folder.svg",
|
Icon::FileTree => "icons/project.svg",
|
||||||
IconAsset::FolderOpen => "icons/file_icons/folder_open.svg",
|
Icon::Folder => "icons/file_icons/folder.svg",
|
||||||
IconAsset::FolderX => "icons/stop_sharing.svg",
|
Icon::FolderOpen => "icons/file_icons/folder_open.svg",
|
||||||
IconAsset::Hash => "icons/hash.svg",
|
Icon::FolderX => "icons/stop_sharing.svg",
|
||||||
IconAsset::InlayHint => "icons/inlay_hint.svg",
|
Icon::Hash => "icons/hash.svg",
|
||||||
IconAsset::MagicWand => "icons/magic-wand.svg",
|
Icon::InlayHint => "icons/inlay_hint.svg",
|
||||||
IconAsset::MagnifyingGlass => "icons/magnifying_glass.svg",
|
Icon::MagicWand => "icons/magic-wand.svg",
|
||||||
IconAsset::MessageBubbles => "icons/conversations.svg",
|
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
|
||||||
IconAsset::Mic => "icons/mic.svg",
|
Icon::Maximize => "icons/maximize.svg",
|
||||||
IconAsset::MicMute => "icons/mic-mute.svg",
|
Icon::Menu => "icons/menu.svg",
|
||||||
IconAsset::Plus => "icons/plus.svg",
|
Icon::MessageBubbles => "icons/conversations.svg",
|
||||||
IconAsset::Screen => "icons/desktop.svg",
|
Icon::Mic => "icons/mic.svg",
|
||||||
IconAsset::Split => "icons/split.svg",
|
Icon::MicMute => "icons/mic-mute.svg",
|
||||||
IconAsset::Terminal => "icons/terminal.svg",
|
Icon::Plus => "icons/plus.svg",
|
||||||
IconAsset::XCircle => "icons/error.svg",
|
Icon::Quote => "icons/quote.svg",
|
||||||
IconAsset::Copilot => "icons/copilot.svg",
|
Icon::Screen => "icons/desktop.svg",
|
||||||
IconAsset::Envelope => "icons/feedback.svg",
|
Icon::Split => "icons/split.svg",
|
||||||
|
Icon::SplitMessage => "icons/split_message.svg",
|
||||||
|
Icon::Terminal => "icons/terminal.svg",
|
||||||
|
Icon::XCircle => "icons/error.svg",
|
||||||
|
Icon::Copilot => "icons/copilot.svg",
|
||||||
|
Icon::Envelope => "icons/feedback.svg",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Element, Clone)]
|
#[derive(Element, Clone)]
|
||||||
pub struct Icon {
|
pub struct IconElement {
|
||||||
asset: IconAsset,
|
icon: Icon,
|
||||||
color: IconColor,
|
color: IconColor,
|
||||||
|
size: IconSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon(asset: IconAsset) -> Icon {
|
impl IconElement {
|
||||||
Icon {
|
pub fn new(icon: Icon) -> Self {
|
||||||
asset,
|
Self {
|
||||||
color: IconColor::default(),
|
icon,
|
||||||
|
color: IconColor::default(),
|
||||||
|
size: IconSize::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Icon {
|
|
||||||
pub fn color(mut self, color: IconColor) -> Self {
|
pub fn color(mut self, color: IconColor) -> Self {
|
||||||
self.color = color;
|
self.color = color;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size(mut self, size: IconSize) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let fill = self.color.color(theme);
|
let fill = self.color.color(theme);
|
||||||
|
|
||||||
svg()
|
let sized_svg = match self.size {
|
||||||
.flex_none()
|
IconSize::Small => svg().size_3p5(),
|
||||||
.path(self.asset.path())
|
IconSize::Large => svg().size_4(),
|
||||||
.size_4()
|
};
|
||||||
.fill(fill)
|
|
||||||
|
sized_svg.flex_none().path(self.icon.path()).fill(fill)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
|
||||||
|
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,13 @@
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq)]
|
||||||
|
pub enum InputVariant {
|
||||||
|
#[default]
|
||||||
|
Ghost,
|
||||||
|
Filled,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
placeholder: &'static str,
|
placeholder: &'static str,
|
||||||
|
@ -13,24 +16,26 @@ pub struct Input {
|
||||||
variant: InputVariant,
|
variant: InputVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input(placeholder: &'static str) -> Input {
|
|
||||||
Input {
|
|
||||||
placeholder,
|
|
||||||
value: "".to_string(),
|
|
||||||
state: InteractionState::default(),
|
|
||||||
variant: InputVariant::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
|
pub fn new(placeholder: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
placeholder,
|
||||||
|
value: "".to_string(),
|
||||||
|
state: InteractionState::default(),
|
||||||
|
variant: InputVariant::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn value(mut self, value: String) -> Self {
|
pub fn value(mut self, value: String) -> Self {
|
||||||
self.value = value;
|
self.value = value;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(mut self, state: InteractionState) -> Self {
|
pub fn state(mut self, state: InteractionState) -> Self {
|
||||||
self.state = state;
|
self.state = state;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variant(mut self, variant: InputVariant) -> Self {
|
pub fn variant(mut self, variant: InputVariant) -> Self {
|
||||||
self.variant = variant;
|
self.variant = variant;
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
use gpui2::{Hsla, WindowContext};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, ViewContext};
|
|
||||||
use gpui2::{IntoElement, ParentElement};
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
pub enum LabelColor {
|
pub enum LabelColor {
|
||||||
|
@ -12,8 +12,28 @@ pub enum LabelColor {
|
||||||
Created,
|
Created,
|
||||||
Modified,
|
Modified,
|
||||||
Deleted,
|
Deleted,
|
||||||
|
Disabled,
|
||||||
Hidden,
|
Hidden,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
|
Accent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelColor {
|
||||||
|
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Default => theme.middle.base.default.foreground,
|
||||||
|
Self::Muted => theme.middle.variant.default.foreground,
|
||||||
|
Self::Created => theme.middle.positive.default.foreground,
|
||||||
|
Self::Modified => theme.middle.warning.default.foreground,
|
||||||
|
Self::Deleted => theme.middle.negative.default.foreground,
|
||||||
|
Self::Disabled => theme.middle.base.disabled.foreground,
|
||||||
|
Self::Hidden => theme.middle.variant.default.foreground,
|
||||||
|
Self::Placeholder => theme.middle.base.disabled.foreground,
|
||||||
|
Self::Accent => theme.middle.accent.default.foreground,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
|
@ -25,20 +45,27 @@ pub enum LabelSize {
|
||||||
|
|
||||||
#[derive(Element, Clone)]
|
#[derive(Element, Clone)]
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
label: &'static str,
|
label: String,
|
||||||
color: LabelColor,
|
color: LabelColor,
|
||||||
size: LabelSize,
|
size: LabelSize,
|
||||||
}
|
highlight_indices: Vec<usize>,
|
||||||
|
strikethrough: bool,
|
||||||
pub fn label(label: &'static str) -> Label {
|
|
||||||
Label {
|
|
||||||
label,
|
|
||||||
color: LabelColor::Default,
|
|
||||||
size: LabelSize::Default,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl Label {
|
||||||
|
pub fn new<L>(label: L) -> Self
|
||||||
|
where
|
||||||
|
L: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
color: LabelColor::Default,
|
||||||
|
size: LabelSize::Default,
|
||||||
|
highlight_indices: Vec::new(),
|
||||||
|
strikethrough: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn color(mut self, color: LabelColor) -> Self {
|
pub fn color(mut self, color: LabelColor) -> Self {
|
||||||
self.color = color;
|
self.color = color;
|
||||||
self
|
self
|
||||||
|
@ -49,27 +76,86 @@ impl Label {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
|
||||||
|
self.highlight_indices = indices;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
|
||||||
|
self.strikethrough = strikethrough;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
let color = match self.color {
|
let highlight_color = theme.lowest.accent.default.foreground;
|
||||||
LabelColor::Default => theme.lowest.base.default.foreground,
|
|
||||||
LabelColor::Muted => theme.lowest.variant.default.foreground,
|
|
||||||
LabelColor::Created => theme.lowest.positive.default.foreground,
|
|
||||||
LabelColor::Modified => theme.lowest.warning.default.foreground,
|
|
||||||
LabelColor::Deleted => theme.lowest.negative.default.foreground,
|
|
||||||
LabelColor::Hidden => theme.lowest.variant.default.foreground,
|
|
||||||
LabelColor::Placeholder => theme.lowest.base.disabled.foreground,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut div = div();
|
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
|
||||||
|
|
||||||
if self.size == LabelSize::Small {
|
let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
|
||||||
div = div.text_xs();
|
|
||||||
} else {
|
for (char_ix, char) in self.label.char_indices() {
|
||||||
div = div.text_sm();
|
let mut color = self.color.hsla(cx);
|
||||||
|
|
||||||
|
if let Some(highlight_ix) = highlight_indices.peek() {
|
||||||
|
if char_ix == *highlight_ix {
|
||||||
|
color = highlight_color;
|
||||||
|
|
||||||
|
highlight_indices.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_run = runs.last_mut();
|
||||||
|
|
||||||
|
let start_new_run = if let Some(last_run) = last_run {
|
||||||
|
if color == last_run.color {
|
||||||
|
last_run.text.push(char);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if start_new_run {
|
||||||
|
runs.push(Run {
|
||||||
|
text: char.to_string(),
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.text_color(color).child(self.label.clone())
|
div()
|
||||||
|
.flex()
|
||||||
|
.when(self.strikethrough, |this| {
|
||||||
|
this.relative().child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.top_px()
|
||||||
|
.my_auto()
|
||||||
|
.w_full()
|
||||||
|
.h_px()
|
||||||
|
.fill(LabelColor::Hidden.hsla(cx)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.children(runs.into_iter().map(|run| {
|
||||||
|
let mut div = div();
|
||||||
|
|
||||||
|
if self.size == LabelSize::Small {
|
||||||
|
div = div.text_xs();
|
||||||
|
} else {
|
||||||
|
div = div.text_sm();
|
||||||
|
}
|
||||||
|
|
||||||
|
div.text_color(run.color).child(run.text)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A run of text that receives the same style.
|
||||||
|
struct Run {
|
||||||
|
pub text: String,
|
||||||
|
pub color: Hsla,
|
||||||
|
}
|
||||||
|
|
132
crates/ui/src/elements/player.rs
Normal file
132
crates/ui/src/elements/player.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
use gpui2::{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(Clone)]
|
||||||
|
pub struct Player {
|
||||||
|
index: usize,
|
||||||
|
avatar_src: String,
|
||||||
|
username: String,
|
||||||
|
status: PlayerStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<V>(&self, cx: &mut ViewContext<V>) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let index = self.index % 8;
|
||||||
|
theme.players[self.index].cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection_color<V>(&self, cx: &mut ViewContext<V>) -> 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
|
||||||
|
}
|
||||||
|
}
|
31
crates/ui/src/elements/stack.rs
Normal file
31
crates/ui/src/elements/stack.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use gpui2::elements::div::Div;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub trait Stack: StyleHelpers {
|
||||||
|
/// Horizontally stacks elements.
|
||||||
|
fn h_stack(self) -> Self {
|
||||||
|
self.flex().flex_row().items_center()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vertically stacks elements.
|
||||||
|
fn v_stack(self) -> Self {
|
||||||
|
self.flex().flex_col()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> Stack for Div<V> {}
|
||||||
|
|
||||||
|
/// Horizontally stacks elements.
|
||||||
|
///
|
||||||
|
/// Sets `flex()`, `flex_row()`, `items_center()`
|
||||||
|
pub fn h_stack<V: 'static>() -> Div<V> {
|
||||||
|
div().h_stack()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vertically stacks elements.
|
||||||
|
///
|
||||||
|
/// Sets `flex()`, `flex_col()`
|
||||||
|
pub fn v_stack<V: 'static>() -> Div<V> {
|
||||||
|
div().v_stack()
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
use gpui2::elements::div;
|
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
|
||||||
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::theme;
|
|
||||||
|
|
||||||
#[derive(Element)]
|
|
||||||
pub struct TextButton {
|
|
||||||
label: &'static str,
|
|
||||||
variant: ButtonVariant,
|
|
||||||
state: InteractionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_button(label: &'static str) -> TextButton {
|
|
||||||
TextButton {
|
|
||||||
label,
|
|
||||||
variant: ButtonVariant::default(),
|
|
||||||
state: InteractionState::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextButton {
|
|
||||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
|
||||||
self.variant = variant;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(mut self, state: InteractionState) -> Self {
|
|
||||||
self.state = state;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
|
|
||||||
let text_color_default;
|
|
||||||
let text_color_hover;
|
|
||||||
let text_color_active;
|
|
||||||
|
|
||||||
let background_color_default;
|
|
||||||
let background_color_hover;
|
|
||||||
let background_color_active;
|
|
||||||
|
|
||||||
let div = div();
|
|
||||||
|
|
||||||
match self.variant {
|
|
||||||
ButtonVariant::Ghost => {
|
|
||||||
text_color_default = theme.lowest.base.default.foreground;
|
|
||||||
text_color_hover = theme.lowest.base.hovered.foreground;
|
|
||||||
text_color_active = theme.lowest.base.pressed.foreground;
|
|
||||||
background_color_default = theme.lowest.base.default.background;
|
|
||||||
background_color_hover = theme.lowest.base.hovered.background;
|
|
||||||
background_color_active = theme.lowest.base.pressed.background;
|
|
||||||
}
|
|
||||||
ButtonVariant::Filled => {
|
|
||||||
text_color_default = theme.lowest.base.default.foreground;
|
|
||||||
text_color_hover = theme.lowest.base.hovered.foreground;
|
|
||||||
text_color_active = theme.lowest.base.pressed.foreground;
|
|
||||||
background_color_default = theme.lowest.on.default.background;
|
|
||||||
background_color_hover = theme.lowest.on.hovered.background;
|
|
||||||
background_color_active = theme.lowest.on.pressed.background;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
div.h_6()
|
|
||||||
.px_1()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.rounded_md()
|
|
||||||
.text_xs()
|
|
||||||
.text_color(text_color_default)
|
|
||||||
.fill(background_color_default)
|
|
||||||
.hover()
|
|
||||||
.text_color(text_color_hover)
|
|
||||||
.fill(background_color_hover)
|
|
||||||
.active()
|
|
||||||
.text_color(text_color_active)
|
|
||||||
.fill(background_color_active)
|
|
||||||
.child(self.label.clone())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,14 @@
|
||||||
use gpui2::elements::div;
|
use crate::prelude::*;
|
||||||
use gpui2::style::StyleHelpers;
|
|
||||||
use gpui2::{Element, IntoElement, ViewContext};
|
|
||||||
|
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct ToolDivider {}
|
pub struct ToolDivider {}
|
||||||
|
|
||||||
pub fn tool_divider<V: 'static>() -> impl Element<V> {
|
|
||||||
ToolDivider {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolDivider {
|
impl ToolDivider {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
|
mod children;
|
||||||
mod components;
|
mod components;
|
||||||
mod element_ext;
|
mod element_ext;
|
||||||
mod elements;
|
mod elements;
|
||||||
|
@ -8,10 +9,12 @@ mod static_data;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod tokens;
|
mod tokens;
|
||||||
|
|
||||||
pub use crate::theme::*;
|
pub use children::*;
|
||||||
pub use components::*;
|
pub use components::*;
|
||||||
pub use element_ext::*;
|
pub use element_ext::*;
|
||||||
pub use elements::*;
|
pub use elements::*;
|
||||||
pub use prelude::*;
|
pub use prelude::*;
|
||||||
pub use static_data::*;
|
pub use static_data::*;
|
||||||
pub use tokens::*;
|
pub use tokens::*;
|
||||||
|
|
||||||
|
pub use crate::theme::*;
|
||||||
|
|
|
@ -1,3 +1,151 @@
|
||||||
|
pub use gpui2::elements::div::{div, ScrollState};
|
||||||
|
pub use gpui2::style::{StyleHelpers, Styleable};
|
||||||
|
pub use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant};
|
||||||
|
|
||||||
|
use gpui2::{hsla, rgb, Hsla, WindowContext};
|
||||||
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SystemColor {
|
||||||
|
pub transparent: Hsla,
|
||||||
|
pub mac_os_traffic_light_red: Hsla,
|
||||||
|
pub mac_os_traffic_light_yellow: Hsla,
|
||||||
|
pub mac_os_traffic_light_green: Hsla,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemColor {
|
||||||
|
pub fn new() -> SystemColor {
|
||||||
|
SystemColor {
|
||||||
|
transparent: hsla(0.0, 0.0, 0.0, 0.0),
|
||||||
|
mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
|
||||||
|
mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
|
||||||
|
mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn color(&self) -> Hsla {
|
||||||
|
self.transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
|
||||||
|
pub enum HighlightColor {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Comment,
|
||||||
|
String,
|
||||||
|
Function,
|
||||||
|
Keyword,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HighlightColor {
|
||||||
|
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Default => theme
|
||||||
|
.syntax
|
||||||
|
.get("primary")
|
||||||
|
.expect("no theme.syntax.primary")
|
||||||
|
.clone(),
|
||||||
|
Self::Comment => theme
|
||||||
|
.syntax
|
||||||
|
.get("comment")
|
||||||
|
.expect("no theme.syntax.comment")
|
||||||
|
.clone(),
|
||||||
|
Self::String => theme
|
||||||
|
.syntax
|
||||||
|
.get("string")
|
||||||
|
.expect("no theme.syntax.string")
|
||||||
|
.clone(),
|
||||||
|
Self::Function => theme
|
||||||
|
.syntax
|
||||||
|
.get("function")
|
||||||
|
.expect("no theme.syntax.function")
|
||||||
|
.clone(),
|
||||||
|
Self::Keyword => theme
|
||||||
|
.syntax
|
||||||
|
.get("keyword")
|
||||||
|
.expect("no theme.syntax.keyword")
|
||||||
|
.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, EnumIter)]
|
||||||
|
pub enum FileSystemStatus {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Conflict,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystemStatus {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::None => "None".to_string(),
|
||||||
|
Self::Conflict => "Conflict".to_string(),
|
||||||
|
Self::Deleted => "Deleted".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
|
||||||
|
pub enum GitStatus {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Created,
|
||||||
|
Modified,
|
||||||
|
Deleted,
|
||||||
|
Conflict,
|
||||||
|
Renamed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitStatus {
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::None => "None".to_string(),
|
||||||
|
Self::Created => "Created".to_string(),
|
||||||
|
Self::Modified => "Modified".to_string(),
|
||||||
|
Self::Deleted => "Deleted".to_string(),
|
||||||
|
Self::Conflict => "Conflict".to_string(),
|
||||||
|
Self::Renamed => "Renamed".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::None => system_color.transparent,
|
||||||
|
Self::Created => theme.lowest.positive.default.foreground,
|
||||||
|
Self::Modified => theme.lowest.warning.default.foreground,
|
||||||
|
Self::Deleted => theme.lowest.negative.default.foreground,
|
||||||
|
Self::Conflict => theme.lowest.warning.default.foreground,
|
||||||
|
Self::Renamed => theme.lowest.accent.default.foreground,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq)]
|
||||||
|
pub enum DiagnosticStatus {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq)]
|
||||||
|
pub enum IconSide {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
pub enum OrderMethod {
|
pub enum OrderMethod {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -6,20 +154,6 @@ pub enum OrderMethod {
|
||||||
MostRecent,
|
MostRecent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
|
||||||
pub enum ButtonVariant {
|
|
||||||
#[default]
|
|
||||||
Ghost,
|
|
||||||
Filled,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
|
||||||
pub enum InputVariant {
|
|
||||||
#[default]
|
|
||||||
Ghost,
|
|
||||||
Filled,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
#[derive(Default, PartialEq, Clone, Copy)]
|
||||||
pub enum Shape {
|
pub enum Shape {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -34,14 +168,13 @@ pub enum DisclosureControlVisibility {
|
||||||
Always,
|
Always,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
|
||||||
pub enum InteractionState {
|
pub enum InteractionState {
|
||||||
#[default]
|
#[default]
|
||||||
Enabled,
|
Enabled,
|
||||||
Hovered,
|
Hovered,
|
||||||
Active,
|
Active,
|
||||||
Focused,
|
Focused,
|
||||||
Dragged,
|
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +196,60 @@ pub enum SelectedState {
|
||||||
Selected,
|
Selected,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Toggleable {
|
||||||
|
Toggleable(ToggleState),
|
||||||
|
#[default]
|
||||||
|
NotToggleable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Toggleable {
|
||||||
|
pub fn is_toggled(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Toggleable(ToggleState::Toggled) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ToggleState> for Toggleable {
|
||||||
|
fn from(state: ToggleState) -> Self {
|
||||||
|
Self::Toggleable(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum ToggleState {
|
pub enum ToggleState {
|
||||||
|
/// The "on" state of a toggleable element.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// - A collasable list that is currently expanded
|
||||||
|
/// - A toggle button that is currently on.
|
||||||
Toggled,
|
Toggled,
|
||||||
|
/// The "off" state of a toggleable element.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// - A collasable list that is currently collapsed
|
||||||
|
/// - A toggle button that is currently off.
|
||||||
|
#[default]
|
||||||
NotToggled,
|
NotToggled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Toggleable> for ToggleState {
|
||||||
|
fn from(toggleable: Toggleable) -> Self {
|
||||||
|
match toggleable {
|
||||||
|
Toggleable::Toggleable(state) => state,
|
||||||
|
Toggleable::NotToggleable => ToggleState::NotToggled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for ToggleState {
|
||||||
|
fn from(toggled: bool) -> Self {
|
||||||
|
if toggled {
|
||||||
|
ToggleState::Toggled
|
||||||
|
} else {
|
||||||
|
ToggleState::NotToggled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,166 +1,558 @@
|
||||||
|
use gpui2::WindowContext;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
label, list_item, palette_item, IconAsset, LabelColor, ListItem, PaletteItem, ToggleState,
|
Buffer, BufferRow, BufferRows, GitStatus, HighlightColor, HighlightedLine, HighlightedText,
|
||||||
|
Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, MicStatus,
|
||||||
|
ModifierKeys, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus,
|
||||||
|
ToggleState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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_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() -> Vec<ListItem> {
|
pub fn static_project_panel_project_items() -> Vec<ListItem> {
|
||||||
vec![
|
vec![
|
||||||
list_item(label("zed"))
|
ListEntry::new(Label::new("zed"))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(0)
|
.indent_level(0)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label(".cargo"))
|
ListEntry::new(Label::new(".cargo"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".config"))
|
ListEntry::new(Label::new(".config"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".git").color(LabelColor::Hidden))
|
ListEntry::new(Label::new(".git").color(LabelColor::Hidden))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".cargo"))
|
ListEntry::new(Label::new(".cargo"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".idea").color(LabelColor::Hidden))
|
ListEntry::new(Label::new(".idea").color(LabelColor::Hidden))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("assets"))
|
ListEntry::new(Label::new("assets"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1)
|
.indent_level(1)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("cargo-target").color(LabelColor::Hidden))
|
ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("crates"))
|
ListEntry::new(Label::new("crates"))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(1)
|
.indent_level(1)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("activity_indicator"))
|
ListEntry::new(Label::new("activity_indicator"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("ai"))
|
ListEntry::new(Label::new("ai"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("audio"))
|
ListEntry::new(Label::new("audio"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("auto_update"))
|
ListEntry::new(Label::new("auto_update"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("breadcrumbs"))
|
ListEntry::new(Label::new("breadcrumbs"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("call"))
|
ListEntry::new(Label::new("call"))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2),
|
.indent_level(2),
|
||||||
list_item(label("sqlez").color(LabelColor::Modified))
|
ListEntry::new(Label::new("sqlez").color(LabelColor::Modified))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2)
|
.indent_level(2)
|
||||||
.set_toggle(ToggleState::NotToggled),
|
.set_toggle(ToggleState::NotToggled),
|
||||||
list_item(label("gpui2"))
|
ListEntry::new(Label::new("gpui2"))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(2)
|
.indent_level(2)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("src"))
|
ListEntry::new(Label::new("src"))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(3)
|
.indent_level(3)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("derrive_element.rs"))
|
ListEntry::new(Label::new("derrive_element.rs"))
|
||||||
.left_icon(IconAsset::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(4),
|
.indent_level(4),
|
||||||
list_item(label("storybook").color(LabelColor::Modified))
|
ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(1)
|
.indent_level(1)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("docs").color(LabelColor::Default))
|
ListEntry::new(Label::new("docs").color(LabelColor::Default))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(2)
|
.indent_level(2)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("src").color(LabelColor::Modified))
|
ListEntry::new(Label::new("src").color(LabelColor::Modified))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(3)
|
.indent_level(3)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("ui").color(LabelColor::Modified))
|
ListEntry::new(Label::new("ui").color(LabelColor::Modified))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(4)
|
.indent_level(4)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("component").color(LabelColor::Created))
|
ListEntry::new(Label::new("component").color(LabelColor::Created))
|
||||||
.left_icon(IconAsset::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(5)
|
.indent_level(5)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
list_item(label("facepile.rs").color(LabelColor::Default))
|
ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default))
|
||||||
.left_icon(IconAsset::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(6),
|
.indent_level(6),
|
||||||
list_item(label("follow_group.rs").color(LabelColor::Default))
|
ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default))
|
||||||
.left_icon(IconAsset::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(6),
|
.indent_level(6),
|
||||||
list_item(label("list_item.rs").color(LabelColor::Created))
|
ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created))
|
||||||
.left_icon(IconAsset::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(6),
|
.indent_level(6),
|
||||||
list_item(label("tab.rs").color(LabelColor::Default))
|
ListEntry::new(Label::new("tab.rs").color(LabelColor::Default))
|
||||||
.left_icon(IconAsset::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(6),
|
.indent_level(6),
|
||||||
list_item(label("target").color(LabelColor::Hidden))
|
ListEntry::new(Label::new("target").color(LabelColor::Hidden))
|
||||||
.left_icon(IconAsset::Folder.into())
|
.left_icon(Icon::Folder.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".dockerignore"))
|
ListEntry::new(Label::new(".dockerignore"))
|
||||||
.left_icon(IconAsset::File.into())
|
.left_icon(Icon::FileGeneric.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label(".DS_Store").color(LabelColor::Hidden))
|
ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden))
|
||||||
.left_icon(IconAsset::File.into())
|
.left_icon(Icon::FileGeneric.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("Cargo.lock"))
|
ListEntry::new(Label::new("Cargo.lock"))
|
||||||
.left_icon(IconAsset::FileLock.into())
|
.left_icon(Icon::FileLock.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("Cargo.toml"))
|
ListEntry::new(Label::new("Cargo.toml"))
|
||||||
.left_icon(IconAsset::FileToml.into())
|
.left_icon(Icon::FileToml.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("Dockerfile"))
|
ListEntry::new(Label::new("Dockerfile"))
|
||||||
.left_icon(IconAsset::File.into())
|
.left_icon(Icon::FileGeneric.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("Procfile"))
|
ListEntry::new(Label::new("Procfile"))
|
||||||
.left_icon(IconAsset::File.into())
|
.left_icon(Icon::FileGeneric.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
list_item(label("README.md"))
|
ListEntry::new(Label::new("README.md"))
|
||||||
.left_icon(IconAsset::FileDoc.into())
|
.left_icon(Icon::FileDoc.into())
|
||||||
.indent_level(1),
|
.indent_level(1),
|
||||||
]
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(From::from)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn static_project_panel_single_items() -> Vec<ListItem> {
|
pub fn static_project_panel_single_items() -> Vec<ListItem> {
|
||||||
vec![
|
vec![
|
||||||
list_item(label("todo.md"))
|
ListEntry::new(Label::new("todo.md"))
|
||||||
.left_icon(IconAsset::FileDoc.into())
|
.left_icon(Icon::FileDoc.into())
|
||||||
.indent_level(0),
|
.indent_level(0),
|
||||||
list_item(label("README.md"))
|
ListEntry::new(Label::new("README.md"))
|
||||||
.left_icon(IconAsset::FileDoc.into())
|
.left_icon(Icon::FileDoc.into())
|
||||||
.indent_level(0),
|
.indent_level(0),
|
||||||
list_item(label("config.json"))
|
ListEntry::new(Label::new("config.json"))
|
||||||
.left_icon(IconAsset::File.into())
|
.left_icon(Icon::FileGeneric.into())
|
||||||
.indent_level(0),
|
.indent_level(0),
|
||||||
]
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(From::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_collab_panel_current_call() -> Vec<ListItem> {
|
||||||
|
vec![
|
||||||
|
ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
|
||||||
|
ListEntry::new(Label::new("nathansobo"))
|
||||||
|
.left_avatar("http://github.com/nathansobo.png?s=50"),
|
||||||
|
ListEntry::new(Label::new("maxbrunsfeld"))
|
||||||
|
.left_avatar("http://github.com/maxbrunsfeld.png?s=50"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(From::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_collab_panel_channels() -> Vec<ListItem> {
|
||||||
|
vec![
|
||||||
|
ListEntry::new(Label::new("zed"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(0),
|
||||||
|
ListEntry::new(Label::new("community"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(1),
|
||||||
|
ListEntry::new(Label::new("dashboards"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("feedback"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("teams-in-channels-alpha"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("current-projects"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(1),
|
||||||
|
ListEntry::new(Label::new("codegen"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("gpui2"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("livestreaming"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("open-source"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("replace"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("semantic-index"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("vim"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
ListEntry::new(Label::new("web-tech"))
|
||||||
|
.left_icon(Icon::Hash.into())
|
||||||
|
.size(ListEntrySize::Medium)
|
||||||
|
.indent_level(2),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(From::from)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn example_editor_actions() -> Vec<PaletteItem> {
|
pub fn example_editor_actions() -> Vec<PaletteItem> {
|
||||||
vec![
|
vec![
|
||||||
palette_item("New File", Some("Ctrl+N")),
|
PaletteItem::new("New File").keybinding(Keybinding::new(
|
||||||
palette_item("Open File", Some("Ctrl+O")),
|
"N".to_string(),
|
||||||
palette_item("Save File", Some("Ctrl+S")),
|
ModifierKeys::new().control(true),
|
||||||
palette_item("Cut", Some("Ctrl+X")),
|
)),
|
||||||
palette_item("Copy", Some("Ctrl+C")),
|
PaletteItem::new("Open File").keybinding(Keybinding::new(
|
||||||
palette_item("Paste", Some("Ctrl+V")),
|
"O".to_string(),
|
||||||
palette_item("Undo", Some("Ctrl+Z")),
|
ModifierKeys::new().control(true),
|
||||||
palette_item("Redo", Some("Ctrl+Shift+Z")),
|
)),
|
||||||
palette_item("Find", Some("Ctrl+F")),
|
PaletteItem::new("Save File").keybinding(Keybinding::new(
|
||||||
palette_item("Replace", Some("Ctrl+R")),
|
"S".to_string(),
|
||||||
palette_item("Jump to Line", None),
|
ModifierKeys::new().control(true),
|
||||||
palette_item("Select All", None),
|
)),
|
||||||
palette_item("Deselect All", None),
|
PaletteItem::new("Cut").keybinding(Keybinding::new(
|
||||||
palette_item("Switch Document", None),
|
"X".to_string(),
|
||||||
palette_item("Insert Line Below", None),
|
ModifierKeys::new().control(true),
|
||||||
palette_item("Insert Line Above", None),
|
)),
|
||||||
palette_item("Move Line Up", None),
|
PaletteItem::new("Copy").keybinding(Keybinding::new(
|
||||||
palette_item("Move Line Down", None),
|
"C".to_string(),
|
||||||
palette_item("Toggle Comment", None),
|
ModifierKeys::new().control(true),
|
||||||
palette_item("Delete Line", None),
|
)),
|
||||||
|
PaletteItem::new("Paste").keybinding(Keybinding::new(
|
||||||
|
"V".to_string(),
|
||||||
|
ModifierKeys::new().control(true),
|
||||||
|
)),
|
||||||
|
PaletteItem::new("Undo").keybinding(Keybinding::new(
|
||||||
|
"Z".to_string(),
|
||||||
|
ModifierKeys::new().control(true),
|
||||||
|
)),
|
||||||
|
PaletteItem::new("Redo").keybinding(Keybinding::new(
|
||||||
|
"Z".to_string(),
|
||||||
|
ModifierKeys::new().control(true).shift(true),
|
||||||
|
)),
|
||||||
|
PaletteItem::new("Find").keybinding(Keybinding::new(
|
||||||
|
"F".to_string(),
|
||||||
|
ModifierKeys::new().control(true),
|
||||||
|
)),
|
||||||
|
PaletteItem::new("Replace").keybinding(Keybinding::new(
|
||||||
|
"R".to_string(),
|
||||||
|
ModifierKeys::new().control(true),
|
||||||
|
)),
|
||||||
|
PaletteItem::new("Jump to Line"),
|
||||||
|
PaletteItem::new("Select All"),
|
||||||
|
PaletteItem::new("Deselect All"),
|
||||||
|
PaletteItem::new("Switch Document"),
|
||||||
|
PaletteItem::new("Insert Line Below"),
|
||||||
|
PaletteItem::new("Insert Line Above"),
|
||||||
|
PaletteItem::new("Move Line Up"),
|
||||||
|
PaletteItem::new("Move Line Down"),
|
||||||
|
PaletteItem::new("Toggle Comment"),
|
||||||
|
PaletteItem::new("Delete Line"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_buffer_example<V: 'static>() -> Buffer<V> {
|
||||||
|
Buffer::new().set_rows(Some(BufferRows::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_buffer_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
|
||||||
|
Buffer::new()
|
||||||
|
.set_title("hello_world.rs".to_string())
|
||||||
|
.set_path("src/hello_world.rs".to_string())
|
||||||
|
.set_language("rust".to_string())
|
||||||
|
.set_rows(Some(BufferRows {
|
||||||
|
show_line_numbers: true,
|
||||||
|
rows: hello_world_rust_buffer_rows(cx),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_buffer_with_status_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
|
||||||
|
Buffer::new()
|
||||||
|
.set_title("hello_world.rs".to_string())
|
||||||
|
.set_path("src/hello_world.rs".to_string())
|
||||||
|
.set_language("rust".to_string())
|
||||||
|
.set_rows(Some(BufferRows {
|
||||||
|
show_line_numbers: true,
|
||||||
|
rows: hello_world_rust_with_status_buffer_rows(cx),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
|
let show_line_number = true;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
BufferRow {
|
||||||
|
line_number: 1,
|
||||||
|
code_action: false,
|
||||||
|
current: true,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(cx),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "main".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(cx),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "() {".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 2,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: " // Statements here are executed when the compiled binary is called."
|
||||||
|
.to_string(),
|
||||||
|
color: HighlightColor::Comment.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 3,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: None,
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 4,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: " // Print text to the console.".to_string(),
|
||||||
|
color: HighlightColor::Comment.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 5,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "}".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
|
let show_line_number = true;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
BufferRow {
|
||||||
|
line_number: 1,
|
||||||
|
code_action: false,
|
||||||
|
current: true,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(cx),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "main".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(cx),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "() {".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 2,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "// Statements here are executed when the compiled binary is called."
|
||||||
|
.to_string(),
|
||||||
|
color: HighlightColor::Comment.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::Modified,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 3,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: None,
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 4,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: " // Print text to the console.".to_string(),
|
||||||
|
color: HighlightColor::Comment.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 5,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "}".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 6,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::Created,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 7,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "Marshall and Nate were here".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(cx),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::Created,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
use gpui2::geometry::AbsoluteLength;
|
use gpui2::geometry::AbsoluteLength;
|
||||||
|
use gpui2::{hsla, Hsla};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub list_indent_depth: AbsoluteLength,
|
pub list_indent_depth: AbsoluteLength,
|
||||||
|
pub default_panel_size: AbsoluteLength,
|
||||||
|
pub state_hover_background: Hsla,
|
||||||
|
pub state_active_background: Hsla,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Token {
|
impl Default for Token {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list_indent_depth: AbsoluteLength::Rems(0.5),
|
list_indent_depth: AbsoluteLength::Rems(0.5),
|
||||||
|
default_panel_size: AbsoluteLength::Rems(16.),
|
||||||
|
state_hover_background: hsla(0.0, 0.0, 0.0, 0.08),
|
||||||
|
state_active_background: hsla(0.0, 0.0, 0.0, 0.16),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue