Detailed changes
@@ -4,11 +4,15 @@ use gpui2::{
};
use std::{marker::PhantomData, rc::Rc};
+mod avatar;
mod icon_button;
mod tab;
+mod tool_divider;
-pub(crate) use icon_button::{icon_button, ButtonVariant};
+pub(crate) use avatar::avatar;
+pub(crate) use icon_button::icon_button;
pub(crate) use tab::tab;
+pub(crate) use tool_divider::tool_divider;
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
@@ -0,0 +1,39 @@
+use crate::prelude::Shape;
+use crate::theme::theme;
+use gpui2::elements::img;
+use gpui2::style::StyleHelpers;
+use gpui2::{ArcCow, IntoElement};
+use gpui2::{Element, ViewContext};
+
+pub type UnknownString = ArcCow<'static, str>;
+
+#[derive(Element)]
+pub(crate) struct Avatar {
+ src: ArcCow<'static, str>,
+ shape: Shape,
+}
+
+pub fn avatar<V: 'static>(src: impl Into<ArcCow<'static, str>>, shape: Shape) -> impl Element<V> {
+ Avatar {
+ src: src.into(),
+ shape,
+ }
+}
+
+impl Avatar {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ let mut img = img();
+
+ if self.shape == Shape::Circle {
+ img = img.rounded_full();
+ } else {
+ img = img.rounded_md();
+ }
+
+ img.uri(self.src.clone())
+ .size_4()
+ .fill(theme.middle.warning.default.foreground)
+ }
+}
@@ -1,3 +1,4 @@
+use crate::prelude::{ButtonVariant, UIState};
use crate::theme::theme;
use gpui2::elements::svg;
use gpui2::style::{StyleHelpers, Styleable};
@@ -8,22 +9,33 @@ use gpui2::{Element, ParentElement, ViewContext};
pub(crate) struct IconButton {
path: &'static str,
variant: ButtonVariant,
+ state: UIState,
}
-#[derive(PartialEq)]
-pub enum ButtonVariant {
- Ghost,
- Filled,
-}
-
-pub fn icon_button<V: 'static>(path: &'static str, variant: ButtonVariant) -> impl Element<V> {
- IconButton { path, variant }
+pub fn icon_button<V: 'static>(
+ path: &'static str,
+ variant: ButtonVariant,
+ state: UIState,
+) -> impl Element<V> {
+ IconButton {
+ path,
+ variant,
+ state,
+ }
}
impl IconButton {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
+ let icon_color;
+
+ if self.state == UIState::Disabled {
+ icon_color = theme.highest.base.disabled.foreground;
+ } else {
+ icon_color = theme.highest.base.default.foreground;
+ }
+
let mut div = div();
if self.variant == ButtonVariant::Filled {
div = div.fill(theme.highest.on.default.background);
@@ -39,12 +51,6 @@ impl IconButton {
.fill(theme.highest.base.hovered.background)
.active()
.fill(theme.highest.base.pressed.background)
- .child(
- svg()
- .path(self.path)
- .w_4()
- .h_4()
- .fill(theme.highest.variant.default.foreground),
- )
+ .child(svg().path(self.path).w_4().h_4().fill(icon_color))
}
}
@@ -0,0 +1,19 @@
+use crate::theme::theme;
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ViewContext};
+
+#[derive(Element)]
+pub(crate) struct ToolDivider {}
+
+pub fn tool_divider<V: 'static>() -> impl Element<V> {
+ ToolDivider {}
+}
+
+impl ToolDivider {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ div().w_px().h_3().fill(theme.lowest.base.default.border)
+ }
+}
@@ -1,3 +1,7 @@
+mod chat_panel;
mod tab_bar;
+mod title_bar;
+pub(crate) use chat_panel::chat_panel;
pub(crate) use tab_bar::tab_bar;
+pub(crate) use title_bar::title_bar;
@@ -0,0 +1,74 @@
+use std::marker::PhantomData;
+
+use crate::components::icon_button;
+use crate::prelude::{ButtonVariant, UIState};
+use crate::theme::theme;
+use gpui2::elements::div::ScrollState;
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ParentElement, ViewContext};
+
+#[derive(Element)]
+pub struct ChatPanel<V: 'static> {
+ view_type: PhantomData<V>,
+ scroll_state: ScrollState,
+}
+
+pub fn chat_panel<V: 'static>(scroll_state: ScrollState) -> ChatPanel<V> {
+ ChatPanel {
+ view_type: PhantomData,
+ scroll_state,
+ }
+}
+
+impl<V: 'static> ChatPanel<V> {
+ fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ div()
+ .h_full()
+ .flex()
+ // Header
+ .child(
+ div()
+ .px_2()
+ .flex()
+ .gap_2()
+ // Nav Buttons
+ .child("#gpui2"),
+ )
+ // Chat Body
+ .child(
+ div()
+ .w_full()
+ .flex()
+ .flex_col()
+ .overflow_y_scroll(self.scroll_state.clone())
+ .child("body"),
+ )
+ // Composer
+ .child(
+ div()
+ .px_2()
+ .flex()
+ .gap_2()
+ // Nav Buttons
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .gap_px()
+ .child(icon_button(
+ "icons/plus.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/split.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ )),
+ ),
+ )
+ }
+}
@@ -1,6 +1,7 @@
use std::marker::PhantomData;
-use crate::components::{icon_button, tab, ButtonVariant};
+use crate::components::{icon_button, tab};
+use crate::prelude::{ButtonVariant, UIState};
use crate::theme::theme;
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
@@ -40,15 +41,23 @@ impl<V: 'static> TabBar<V> {
.flex()
.items_center()
.gap_px()
- .child(icon_button("icons/arrow_left.svg", ButtonVariant::Filled))
- .child(icon_button("icons/arrow_right.svg", ButtonVariant::Ghost)),
+ .child(icon_button(
+ "icons/arrow_left.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/arrow_right.svg",
+ ButtonVariant::Ghost,
+ UIState::Disabled,
+ )),
),
)
.child(
div().w_0().flex_1().h_full().child(
div()
.flex()
- .gap_px()
+ .gap_8()
.overflow_x_scroll(self.scroll_state.clone())
.child(tab("Cargo.toml", false))
.child(tab("Channels Panel", true))
@@ -74,8 +83,16 @@ impl<V: 'static> TabBar<V> {
.flex()
.items_center()
.gap_px()
- .child(icon_button("icons/plus.svg", ButtonVariant::Ghost))
- .child(icon_button("icons/split.svg", ButtonVariant::Ghost)),
+ .child(icon_button(
+ "icons/plus.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/split.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ )),
),
)
}
@@ -0,0 +1,154 @@
+use std::marker::PhantomData;
+
+use crate::components::{avatar, icon_button, tool_divider};
+use crate::prelude::{ButtonVariant, Shape, UIState};
+use crate::theme::theme;
+use gpui2::style::{StyleHelpers, Styleable};
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ParentElement, ViewContext};
+
+#[derive(Element)]
+pub struct TitleBar<V: 'static> {
+ view_type: PhantomData<V>,
+}
+
+pub fn title_bar<V: 'static>() -> TitleBar<V> {
+ TitleBar {
+ view_type: PhantomData,
+ }
+}
+
+impl<V: 'static> TitleBar<V> {
+ fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ div()
+ .flex()
+ .items_center()
+ .justify_between()
+ .w_full()
+ .h_8()
+ .fill(theme.lowest.base.default.background)
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .h_full()
+ .gap_4()
+ .px_2()
+ // === Traffic Lights === //
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .gap_2()
+ .child(
+ div()
+ .w_3()
+ .h_3()
+ .rounded_full()
+ .fill(theme.lowest.positive.default.foreground),
+ )
+ .child(
+ div()
+ .w_3()
+ .h_3()
+ .rounded_full()
+ .fill(theme.lowest.warning.default.foreground),
+ )
+ .child(
+ div()
+ .w_3()
+ .h_3()
+ .rounded_full()
+ .fill(theme.lowest.negative.default.foreground),
+ ),
+ )
+ // === Project Info === //
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .gap_1()
+ .child(
+ div()
+ .h_full()
+ .flex()
+ .items_center()
+ .justify_center()
+ .px_2()
+ .rounded_md()
+ .hover()
+ .fill(theme.lowest.base.hovered.background)
+ .active()
+ .fill(theme.lowest.base.pressed.background)
+ .child(div().text_sm().child("project")),
+ )
+ .child(
+ div()
+ .h_full()
+ .flex()
+ .items_center()
+ .justify_center()
+ .px_2()
+ .rounded_md()
+ .text_color(theme.lowest.variant.default.foreground)
+ .hover()
+ .fill(theme.lowest.base.hovered.background)
+ .active()
+ .fill(theme.lowest.base.pressed.background)
+ .child(div().text_sm().child("branch")),
+ ),
+ ),
+ )
+ .child(
+ div()
+ .flex()
+ .items_center()
+ .child(
+ div()
+ .px_2()
+ .flex()
+ .items_center()
+ .gap_1()
+ .child(icon_button(
+ "icons/stop_sharing.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/exit.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ )),
+ )
+ .child(tool_divider())
+ .child(
+ div()
+ .px_2()
+ .flex()
+ .items_center()
+ .gap_1()
+ .child(icon_button(
+ "icons/radix/mic.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/radix/speaker-loud.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ ))
+ .child(icon_button(
+ "icons/radix/desktop.svg",
+ ButtonVariant::Ghost,
+ UIState::Default,
+ )),
+ )
+ .child(div().px_2().flex().items_center().child(avatar(
+ "https://avatars.githubusercontent.com/u/1714999?v=4",
+ Shape::Squircle,
+ ))),
+ )
+ }
+}
@@ -0,0 +1,26 @@
+#[derive(PartialEq)]
+pub enum ButtonVariant {
+ Ghost,
+ Filled,
+}
+
+#[derive(PartialEq)]
+pub enum Shape {
+ Circle,
+ Squircle,
+}
+
+#[derive(PartialEq)]
+pub enum UIState {
+ Default,
+ Hovered,
+ Active,
+ Focused,
+ Disabled,
+}
+
+#[derive(PartialEq)]
+pub enum UIToggleState {
+ Default,
+ Enabled,
+}
@@ -13,6 +13,7 @@ mod collab_panel;
mod components;
mod element_ext;
mod modules;
+mod prelude;
mod theme;
mod workspace;
@@ -1,4 +1,8 @@
-use crate::{collab_panel::collab_panel, modules::tab_bar, theme::theme};
+use crate::{
+ collab_panel::collab_panel,
+ modules::{chat_panel, tab_bar, title_bar},
+ theme::theme,
+};
use gpui2::{
elements::{div, div::ScrollState, img, svg},
style::{StyleHelpers, Styleable},
@@ -30,7 +34,7 @@ impl WorkspaceElement {
.items_start()
.text_color(theme.lowest.base.default.foreground)
.fill(theme.middle.base.default.background)
- .child(titlebar())
+ .child(title_bar())
.child(
div()
.flex_1()
@@ -52,7 +56,7 @@ impl WorkspaceElement {
.child(tab_bar(self.tab_bar_scroll_state.clone())),
),
)
- .child(collab_panel(self.right_scroll_state.clone())),
+ .child(chat_panel(self.right_scroll_state.clone())),
)
.child(statusbar())
}