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:
Marshall Bowers 2023-09-28 19:36:21 -04:00 committed by GitHub
parent e7ee8a95f6
commit f26ca0866c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 4658 additions and 1623 deletions

View file

@ -6,7 +6,10 @@ publish = false
[dependencies]
anyhow.workspace = true
chrono = "0.4"
gpui2 = { path = "../gpui2" }
serde.workspace = true
settings = { path = "../settings" }
smallvec.workspace = true
strum = { version = "0.25.0", features = ["derive"] }
theme = { path = "../theme" }

View 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.

View 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>;

View file

@ -1,142 +1,153 @@
mod assistant_panel;
mod breadcrumb;
mod buffer;
mod chat_panel;
mod collab_panel;
mod command_palette;
mod context_menu;
mod editor;
mod facepile;
mod follow_group;
mod icon_button;
mod keybinding;
mod list;
mod list_item;
mod list_section_header;
mod palette;
mod palette_item;
mod panel;
mod panes;
mod player_stack;
mod project_panel;
mod status_bar;
mod tab;
mod tab_bar;
mod terminal;
mod title_bar;
mod toolbar;
mod traffic_lights;
mod workspace;
pub use assistant_panel::*;
pub use breadcrumb::*;
pub use buffer::*;
pub use chat_panel::*;
pub use collab_panel::*;
pub use command_palette::*;
pub use context_menu::*;
pub use editor::*;
pub use facepile::*;
pub use follow_group::*;
pub use icon_button::*;
pub use keybinding::*;
pub use list::*;
pub use list_item::*;
pub use list_section_header::*;
pub use palette::*;
pub use palette_item::*;
pub use panel::*;
pub use panes::*;
pub use player_stack::*;
pub use project_panel::*;
pub use status_bar::*;
pub use tab::*;
pub use tab_bar::*;
pub use terminal::*;
pub use title_bar::*;
pub use toolbar::*;
pub use traffic_lights::*;
pub use workspace::*;
use std::marker::PhantomData;
use std::rc::Rc;
// Nate: Commenting this out for now, unsure if we need it.
use gpui2::elements::div;
use gpui2::interactive::Interactive;
use gpui2::platform::MouseButton;
use gpui2::style::StyleHelpers;
use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext};
// use std::marker::PhantomData;
// use std::rc::Rc;
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
}
// use gpui2::elements::div;
// use gpui2::interactive::Interactive;
// use gpui2::platform::MouseButton;
// use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext};
impl<V, D> Default for ButtonHandlers<V, D> {
fn default() -> Self {
Self { click: None }
}
}
// struct ButtonHandlers<V, D> {
// click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
// }
#[derive(Element)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
label: Option<ArcCow<'static, str>>,
icon: Option<ArcCow<'static, str>>,
data: Rc<D>,
view_type: PhantomData<V>,
}
// impl<V, D> Default for ButtonHandlers<V, D> {
// fn default() -> Self {
// Self { click: None }
// }
// }
// Impl block for buttons without data.
// See below for an impl block for any button.
impl<V: 'static> Button<V, ()> {
fn new() -> Self {
Self {
handlers: ButtonHandlers::default(),
label: None,
icon: None,
data: Rc::new(()),
view_type: PhantomData,
}
}
// #[derive(Element)]
// pub struct Button<V: 'static, D: 'static> {
// handlers: ButtonHandlers<V, D>,
// label: Option<ArcCow<'static, str>>,
// icon: Option<ArcCow<'static, str>>,
// data: Rc<D>,
// view_type: PhantomData<V>,
// }
pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
Button {
handlers: ButtonHandlers::default(),
label: self.label,
icon: self.icon,
data: Rc::new(data),
view_type: PhantomData,
}
}
}
// // Impl block for buttons without data.
// // See below for an impl block for any button.
// impl<V: 'static> Button<V, ()> {
// fn new() -> Self {
// Self {
// handlers: ButtonHandlers::default(),
// label: None,
// icon: None,
// data: Rc::new(()),
// view_type: PhantomData,
// }
// }
// Impl block for button regardless of its data type.
impl<V: 'static, D: 'static> Button<V, D> {
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
self.label = Some(label.into());
self
}
// pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
// Button {
// handlers: ButtonHandlers::default(),
// label: self.label,
// icon: self.icon,
// data: Rc::new(data),
// view_type: PhantomData,
// }
// }
// }
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
self.icon = Some(icon.into());
self
}
// // Impl block for button regardless of its data type.
// impl<V: 'static, D: 'static> Button<V, D> {
// pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
// self.label = Some(label.into());
// self
// }
pub fn on_click(
mut self,
handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
) -> Self {
self.handlers.click = Some(Rc::new(handler));
self
}
}
// pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
// self.icon = Some(icon.into());
// self
// }
pub fn button<V>() -> Button<V, ()> {
Button::new()
}
// pub fn on_click(
// 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> {
fn render(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> impl IntoElement<V> + Interactive<V> {
// let colors = &cx.theme::<Theme>().colors;
// pub fn button<V>() -> Button<V, ()> {
// Button::new()
// }
let button = div()
// .fill(colors.error(0.5))
.h_4()
.children(self.label.clone());
// impl<V: 'static, D: 'static> Button<V, D> {
// fn render(
// &mut self,
// 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 data = self.data.clone();
button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
handler(view, data.as_ref(), cx)
})
} else {
button
}
}
}
// let button = div()
// // .fill(colors.error(0.5))
// .h_4()
// .children(self.label.clone());
// if let Some(handler) = self.handlers.click.clone() {
// let data = self.data.clone();
// button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
// handler(view, data.as_ref(), cx)
// })
// } else {
// button
// }
// }
// }

View 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.))
}
}

View file

@ -1,24 +1,19 @@
use gpui2::elements::div;
use gpui2::style::{StyleHelpers, Styleable};
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::theme;
use crate::prelude::*;
use crate::{h_stack, theme};
#[derive(Element)]
pub struct Breadcrumb {}
pub fn breadcrumb() -> Breadcrumb {
Breadcrumb {}
}
impl Breadcrumb {
pub fn new() -> Self {
Self {}
}
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
h_stack()
.px_1()
.flex()
.flex_row()
// TODO: Read font from theme (or settings?).
.font("Zed Mono Extended")
.text_sm()

View 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)
}
}

View file

@ -1,66 +1,127 @@
use std::marker::PhantomData;
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use chrono::NaiveDateTime;
use crate::prelude::*;
use crate::theme::theme;
use crate::{icon_button, IconAsset};
use crate::{Icon, IconButton, Input, Label, LabelColor, Panel, PanelSide};
#[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,
}
current_side: PanelSide,
messages: Vec<ChatMessage>,
}
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> {
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()
struct PanelPayload {
pub scroll_state: ScrollState,
pub messages: Vec<ChatMessage>,
}
Panel::new(
self.scroll_state.clone(),
|_, payload| {
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
vec![div()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
.child("body"),
)
// Composer
.child(
div()
.h_full()
.px_2()
.flex()
.gap_2()
// Nav Buttons
// Header
.child(
div()
.flex()
.items_center()
.gap_px()
.child(icon_button().icon(IconAsset::Plus))
.child(icon_button().icon(IconAsset::Split)),
),
)
.justify_between()
.gap_2()
.child(div().flex().child(Label::new("#design")))
.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())))
}
}

View file

@ -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 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)]
pub struct CollabPanelElement<V: 'static> {
pub struct CollabPanel<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> CollabPanel<V> {
pub fn new(scroll_state: ScrollState) -> Self {
Self {
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()
v_stack()
.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()
v_stack()
.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(3)
.flatten(),
.child(
List::new(static_collab_panel_current_call())
.header(
ListHeader::new("CRDB")
.left_icon(Icon::Hash.into())
.set_toggle(ToggleState::Toggled),
)
.set_toggle(ToggleState::Toggled),
),
)
.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(

View file

@ -1,9 +1,7 @@
use gpui2::elements::div;
use gpui2::{elements::div::ScrollState, ViewContext};
use gpui2::{Element, IntoElement, ParentElement};
use std::marker::PhantomData;
use crate::{example_editor_actions, palette, OrderMethod};
use crate::prelude::*;
use crate::{example_editor_actions, OrderMethod, Palette};
#[derive(Element)]
pub struct CommandPalette<V: 'static> {
@ -11,17 +9,17 @@ pub struct CommandPalette<V: 'static> {
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> {
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> {
div().child(
palette(self.scroll_state.clone())
Palette::new(self.scroll_state.clone())
.items(example_editor_actions())
.placeholder("Execute a command...")
.empty_string("No items found.")

View 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())
}
}

View 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())
}
}

View file

@ -1,29 +1,27 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::{theme, Avatar};
use crate::prelude::*;
use crate::{theme, Avatar, Player};
#[derive(Element)]
pub struct Facepile {
players: Vec<Avatar>,
}
pub fn facepile<P: Iterator<Item = Avatar>>(players: P) -> Facepile {
Facepile {
players: players.collect(),
}
players: Vec<Player>,
}
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> {
let theme = theme(cx);
let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
.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)
}

View file

@ -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())),
)
}
}

View file

@ -1,29 +1,16 @@
use gpui2::elements::div;
use gpui2::style::{StyleHelpers, Styleable};
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::{icon, theme, IconColor};
use crate::{prelude::*, IconAsset};
use crate::prelude::*;
use crate::{theme, Icon, IconColor, IconElement};
#[derive(Element)]
pub struct IconButton {
icon: IconAsset,
icon: Icon,
color: IconColor,
variant: ButtonVariant,
state: InteractionState,
}
pub fn icon_button() -> IconButton {
IconButton {
icon: IconAsset::default(),
color: IconColor::default(),
variant: ButtonVariant::default(),
state: InteractionState::default(),
}
}
impl IconButton {
pub fn new(icon: IconAsset) -> Self {
pub fn new(icon: Icon) -> Self {
Self {
icon,
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
}
@ -75,6 +62,6 @@ impl IconButton {
.fill(theme.highest.base.hovered.background)
.active()
.fill(theme.highest.base.pressed.background)
.child(icon(self.icon).color(icon_color))
.child(IconElement::new(self.icon).color(icon_color))
}
}

View 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
}
}

View file

@ -1,36 +1,294 @@
use crate::theme::theme;
use crate::tokens::token;
use crate::{icon, label, prelude::*, IconAsset, LabelColor, ListItem, ListSectionHeader};
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use gpui2::elements::div::Div;
use gpui2::{Hsla, WindowContext};
#[derive(Element)]
pub struct List {
header: Option<ListSectionHeader>,
items: Vec<ListItem>,
empty_message: &'static str,
toggle: Option<ToggleState>,
// footer: Option<ListSectionFooter>,
use crate::prelude::*;
use crate::{
h_stack, theme, token, v_stack, Avatar, DisclosureControlVisibility, Icon, IconColor,
IconElement, IconSize, InteractionState, Label, LabelColor, LabelSize, SystemColor,
ToggleState,
};
#[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 {
List {
header: None,
items,
empty_message: "No items",
toggle: None,
#[derive(Element, Clone, Copy)]
pub struct ListHeader {
label: &'static str,
left_icon: Option<Icon>,
variant: ListItemVariant,
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 header(mut self, header: ListSectionHeader) -> Self {
self.header = Some(header);
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
self.toggleable = toggle.into();
self
}
pub fn empty_message(mut self, empty_message: &'static str) -> Self {
self.empty_message = empty_message;
pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self {
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
}
@ -39,26 +297,216 @@ impl List {
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 token = token();
let disclosure_control = match self.toggle {
Some(ToggleState::NotToggled) => Some(icon(IconAsset::ChevronRight)),
Some(ToggleState::Toggled) => Some(icon(IconAsset::ChevronDown)),
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
IconElement::new(Icon::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,
};
let sized_item = match self.size {
ListEntrySize::Small => div().h_6(),
ListEntrySize::Medium => div().h_7(),
};
div()
.fill(background_color)
.when(self.state == InteractionState::Focused, |this| {
this.border()
.border_color(theme.lowest.accent.default.border)
})
.relative()
.py_1()
.flex()
.flex_col()
.children(self.header.map(|h| h))
.children(
self.items
.is_empty()
.then(|| label(self.empty_message).color(LabelColor::Muted)),
.child(
sized_item
.when(self.variant == ListItemVariant::Inset, |this| this.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(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)
}
}

View file

@ -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()),
)
}
}

View file

@ -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),
)
}
}

View file

@ -1,12 +1,8 @@
use std::marker::PhantomData;
use crate::prelude::OrderMethod;
use crate::prelude::*;
use crate::theme::theme;
use crate::{label, palette_item, LabelColor, PaletteItem};
use gpui2::elements::div::ScrollState;
use gpui2::style::{StyleHelpers, Styleable};
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
#[derive(Element)]
pub struct Palette<V: 'static> {
@ -18,20 +14,19 @@ pub struct Palette<V: 'static> {
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> {
pub fn items(mut self, mut items: Vec<PaletteItem>) -> Self {
items.sort_by_key(|item| item.label);
pub fn new(scroll_state: ScrollState) -> Self {
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
}
@ -55,49 +50,33 @@ impl<V: 'static> Palette<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
v_stack()
.w_96()
.rounded_lg()
.fill(theme.lowest.base.default.background)
.border()
.border_color(theme.lowest.base.default.border)
.flex()
.flex_col()
.child(
div()
.flex()
.flex_col()
v_stack()
.gap_px()
.child(
div().py_0p5().px_1().flex().flex_col().child(
div().px_2().py_0p5().child(
label(self.input_placeholder).color(LabelColor::Placeholder),
),
.child(v_stack().py_0p5().px_1().child(
div().px_2().py_0p5().child(
Label::new(self.input_placeholder).color(LabelColor::Placeholder),
),
)
))
.child(div().h_px().w_full().fill(theme.lowest.base.default.border))
.child(
div()
v_stack()
.py_0p5()
.px_1()
.flex()
.flex_col()
.grow()
.max_h_96()
.overflow_y_scroll(self.scroll_state.clone())
.children(
vec![if self.items.is_empty() {
Some(
div()
.flex()
.flex_row()
.justify_between()
.px_2()
.py_1()
.child(
label(self.empty_string).color(LabelColor::Muted),
),
)
Some(h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string).color(LabelColor::Muted),
))
} else {
None
}]
@ -105,9 +84,7 @@ impl<V: 'static> Palette<V> {
.flatten(),
)
.children(self.items.iter().map(|item| {
div()
.flex()
.flex_row()
h_stack()
.justify_between()
.px_2()
.py_0p5()
@ -116,9 +93,52 @@ impl<V: 'static> Palette<V> {
.fill(theme.lowest.base.hovered.background)
.active()
.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())
}
}

View file

@ -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()),
)
}
}

View 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()))
}
}

View 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!()
}
}

View 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()))
})),
)
}
}

View file

@ -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::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)]
pub struct ProjectPanel<V: 'static> {
view_type: PhantomData<V>,
scroll_state: ScrollState,
}
pub fn project_panel<V: 'static>(scroll_state: ScrollState) -> ProjectPanel<V> {
ProjectPanel {
view_type: PhantomData,
scroll_state,
}
current_side: PanelSide,
}
impl<V: 'static> ProjectPanel<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
pub fn new(scroll_state: ScrollState) -> Self {
Self {
view_type: PhantomData,
scroll_state,
current_side: PanelSide::default(),
}
}
div()
.w_56()
.h_full()
.flex()
.flex_col()
.fill(theme.middle.base.default.background)
.child(
div()
.w_56()
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> {
struct PanelPayload {
pub theme: Arc<Theme>,
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_col()
.overflow_y_scroll(self.scroll_state.clone())
.w_56()
.h_full()
.px_2()
.fill(theme.middle.base.default.background)
.child(
list(static_project_panel_single_items())
.header(list_section_header("FILES").set_toggle(ToggleState::Toggled))
.empty_message("No files in directory")
.set_toggle(ToggleState::Toggled),
div()
.w_56()
.flex()
.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(
list(static_project_panel_project_items())
.header(list_section_header("PROJECT").set_toggle(ToggleState::Toggled))
.empty_message("No folders in directory")
.set_toggle(ToggleState::Toggled),
),
)
.child(
input("Find something...")
.value("buffe".to_string())
.state(InteractionState::Focused),
)
Input::new("Find something...")
.value("buffe".to_string())
.state(InteractionState::Focused),
)
.into_any()]
},
Box::new(PanelPayload {
theme: theme(cx),
scroll_state: self.scroll_state.clone(),
}),
)
}
}

View file

@ -1,11 +1,8 @@
use std::marker::PhantomData;
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use crate::prelude::*;
use crate::theme::{theme, Theme};
use crate::{icon_button, text_button, tool_divider, IconAsset};
use crate::{Button, Icon, IconButton, IconColor, ToolDivider};
#[derive(Default, PartialEq)]
pub enum Tool {
@ -40,16 +37,16 @@ pub struct StatusBar<V: 'static> {
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> {
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 {
self.left_tools = {
let mut tools = vec![tool];
@ -106,10 +103,10 @@ impl<V: 'static> StatusBar<V> {
.flex()
.items_center()
.gap_1()
.child(icon_button().icon(IconAsset::FileTree))
.child(icon_button().icon(IconAsset::Hash))
.child(tool_divider())
.child(icon_button().icon(IconAsset::XCircle))
.child(IconButton::new(Icon::FileTree).color(IconColor::Accent))
.child(IconButton::new(Icon::Hash))
.child(ToolDivider::new())
.child(IconButton::new(Icon::XCircle))
}
fn right_tools(&self, theme: &Theme) -> impl Element<V> {
div()
@ -121,27 +118,27 @@ impl<V: 'static> StatusBar<V> {
.flex()
.items_center()
.gap_1()
.child(text_button("116:25"))
.child(text_button("Rust")),
.child(Button::new("116:25"))
.child(Button::new("Rust")),
)
.child(tool_divider())
.child(ToolDivider::new())
.child(
div()
.flex()
.items_center()
.gap_1()
.child(icon_button().icon(IconAsset::Copilot))
.child(icon_button().icon(IconAsset::Envelope)),
.child(IconButton::new(Icon::Copilot))
.child(IconButton::new(Icon::Envelope)),
)
.child(tool_divider())
.child(ToolDivider::new())
.child(
div()
.flex()
.items_center()
.gap_1()
.child(icon_button().icon(IconAsset::Terminal))
.child(icon_button().icon(IconAsset::MessageBubbles))
.child(icon_button().icon(IconAsset::Ai)),
.child(IconButton::new(Icon::Terminal))
.child(IconButton::new(Icon::MessageBubbles))
.child(IconButton::new(Icon::Ai)),
)
}
}

View file

@ -1,22 +1,96 @@
use gpui2::elements::div;
use gpui2::style::{StyleHelpers, Styleable};
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::theme;
use crate::prelude::*;
use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor};
#[derive(Element)]
pub struct Tab {
title: &'static str,
enabled: bool,
}
pub fn tab<V: 'static>(title: &'static str, enabled: bool) -> impl Element<V> {
Tab { title, enabled }
title: String,
icon: Option<Icon>,
current: bool,
dirty: bool,
fs_status: FileSystemStatus,
git_status: GitStatus,
diagnostic_status: DiagnosticStatus,
close_side: IconSide,
}
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> {
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()
.px_2()
@ -24,33 +98,34 @@ impl Tab {
.flex()
.items_center()
.justify_center()
.rounded_lg()
.fill(if self.enabled {
theme.highest.on.default.background
} else {
.fill(if self.current {
theme.highest.base.default.background
})
.hover()
.fill(if self.enabled {
theme.highest.on.hovered.background
} else {
theme.highest.base.hovered.background
})
.active()
.fill(if self.enabled {
theme.highest.on.pressed.background
} else {
theme.highest.base.pressed.background
theme.middle.base.default.background
})
.child(
div()
.text_sm()
.text_color(if self.enabled {
theme.highest.base.default.foreground
.px_1()
.flex()
.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 {
theme.highest.variant.default.foreground
None
})
.child(self.title),
.child(label)
.children(if self.close_side == IconSide::Right {
Some(close_icon)
} else {
None
}),
)
}
}

View file

@ -1,13 +1,7 @@
use std::marker::PhantomData;
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use crate::prelude::InteractionState;
use crate::theme::theme;
use crate::{icon_button, tab, IconAsset};
use crate::prelude::*;
use crate::{theme, Icon, IconButton, Tab};
#[derive(Element)]
pub struct TabBar<V: 'static> {
@ -15,14 +9,14 @@ pub struct TabBar<V: 'static> {
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> {
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> {
let theme = theme(cx);
let can_navigate_back = true;
@ -30,6 +24,7 @@ impl<V: 'static> TabBar<V> {
div()
.w_full()
.flex()
.fill(theme.middle.base.default.background)
// Left Side
.child(
div()
@ -44,12 +39,11 @@ impl<V: 'static> TabBar<V> {
.items_center()
.gap_px()
.child(
icon_button()
.icon(IconAsset::ArrowLeft)
IconButton::new(Icon::ArrowLeft)
.state(InteractionState::Enabled.if_enabled(can_navigate_back)),
)
.child(
icon_button().icon(IconAsset::ArrowRight).state(
IconButton::new(Icon::ArrowRight).state(
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()
.flex()
.gap_1()
.overflow_x_scroll(self.scroll_state.clone())
.child(tab("Cargo.toml", false))
.child(tab("Channels Panel", true))
.child(tab("channels_panel.rs", false))
.child(tab("workspace.rs", false))
.child(tab("icon_button.rs", false))
.child(tab("storybook.rs", false))
.child(tab("theme.rs", false))
.child(tab("theme_registry.rs", false))
.child(tab("styleable_helpers.rs", false)),
.child(
Tab::new()
.title("Cargo.toml".to_string())
.current(false)
.git_status(GitStatus::Modified),
)
.child(
Tab::new()
.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
@ -85,8 +114,8 @@ impl<V: 'static> TabBar<V> {
.flex()
.items_center()
.gap_px()
.child(icon_button().icon(IconAsset::Plus))
.child(icon_button().icon(IconAsset::Split)),
.child(IconButton::new(Icon::Plus))
.child(IconButton::new(Icon::Split)),
),
)
}

View 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(()),
))
}
}

View file

@ -1,33 +1,41 @@
use std::marker::PhantomData;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::prelude::Shape;
use crate::prelude::*;
use crate::{
avatar, follow_group, icon_button, text_button, theme, tool_divider, traffic_lights, IconAsset,
IconColor,
static_players_with_call_status, theme, Avatar, Button, Icon, IconButton, IconColor,
PlayerStack, ToolDivider, TrafficLights,
};
#[derive(Element)]
pub struct TitleBar<V: 'static> {
view_type: PhantomData<V>,
}
pub fn title_bar<V: 'static>() -> TitleBar<V> {
TitleBar {
view_type: PhantomData,
}
is_active: Arc<AtomicBool>,
}
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> {
let theme = theme(cx);
let player_list = vec![
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
];
let has_focus = cx.window_is_active();
let player_list = static_players_with_call_status().into_iter();
div()
.flex()
@ -43,20 +51,17 @@ impl<V: 'static> TitleBar<V> {
.h_full()
.gap_4()
.px_2()
.child(traffic_lights())
.child(TrafficLights::new().window_has_focus(has_focus))
// === Project Info === //
.child(
div()
.flex()
.items_center()
.gap_1()
.child(text_button("maxbrunsfeld"))
.child(text_button("zed"))
.child(text_button("nate/gpui2-ui-components")),
.child(Button::new("zed"))
.child(Button::new("nate/gpui2-ui-components")),
)
.child(follow_group(player_list.clone()).player(0))
.child(follow_group(player_list.clone()).player(1))
.child(follow_group(player_list.clone()).player(2)),
.children(player_list.map(|p| PlayerStack::new(p))),
)
.child(
div()
@ -68,27 +73,23 @@ impl<V: 'static> TitleBar<V> {
.flex()
.items_center()
.gap_1()
.child(icon_button().icon(IconAsset::FolderX))
.child(icon_button().icon(IconAsset::Close)),
.child(IconButton::new(Icon::FolderX))
.child(IconButton::new(Icon::Close)),
)
.child(tool_divider())
.child(ToolDivider::new())
.child(
div()
.px_2()
.flex()
.items_center()
.gap_1()
.child(icon_button().icon(IconAsset::Mic))
.child(icon_button().icon(IconAsset::AudioOn))
.child(
icon_button()
.icon(IconAsset::Screen)
.color(IconColor::Accent),
),
.child(IconButton::new(Icon::Mic))
.child(IconButton::new(Icon::AudioOn))
.child(IconButton::new(Icon::Screen).color(IconColor::Accent)),
)
.child(
div().px_2().flex().items_center().child(
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
),
),

View file

@ -1,21 +1,19 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::{breadcrumb, theme, IconAsset, IconButton};
use crate::prelude::*;
use crate::{theme, Breadcrumb, Icon, IconButton};
#[derive(Clone)]
pub struct ToolbarItem {}
#[derive(Element)]
#[derive(Element, Clone)]
pub struct Toolbar {
items: Vec<ToolbarItem>,
}
pub fn toolbar() -> Toolbar {
Toolbar { items: Vec::new() }
}
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> {
let theme = theme(cx);
@ -23,13 +21,13 @@ impl Toolbar {
.p_2()
.flex()
.justify_between()
.child(breadcrumb())
.child(Breadcrumb::new())
.child(
div()
.flex()
.child(IconButton::new(IconAsset::InlayHint))
.child(IconButton::new(IconAsset::MagnifyingGlass))
.child(IconButton::new(IconAsset::MagicWand)),
.child(IconButton::new(Icon::InlayHint))
.child(IconButton::new(Icon::MagnifyingGlass))
.child(IconButton::new(Icon::MagicWand)),
)
}
}

View file

@ -1,30 +1,78 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, Hsla, IntoElement, ParentElement, ViewContext};
use crate::prelude::*;
use crate::{theme, token, SystemColor};
use crate::theme;
#[derive(Clone, Copy)]
enum TrafficLightColor {
Red,
Yellow,
Green,
}
#[derive(Element)]
pub struct TrafficLights {}
struct TrafficLight {
color: TrafficLightColor,
window_has_focus: bool,
}
pub fn traffic_lights() -> TrafficLights {
TrafficLights {}
impl TrafficLight {
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 {
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> {
let theme = theme(cx);
let token = token();
div()
.flex()
.items_center()
.gap_2()
.child(traffic_light(theme.lowest.negative.default.foreground))
.child(traffic_light(theme.lowest.warning.default.foreground))
.child(traffic_light(theme.lowest.positive.default.foreground))
.child(TrafficLight::new(
TrafficLightColor::Red,
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())
}

View file

@ -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::{
elements::{div, div::ScrollState},
style::StyleHelpers,
Element, IntoElement, ParentElement, ViewContext,
use crate::prelude::*;
use crate::{
theme, v_stack, ChatMessage, ChatPanel, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide,
ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
};
#[derive(Element, Default)]
struct WorkspaceElement {
project_panel_scroll_state: ScrollState,
collab_panel_scroll_state: ScrollState,
right_scroll_state: ScrollState,
pub struct WorkspaceElement {
left_panel_scroll_state: ScrollState,
right_panel_scroll_state: ScrollState,
tab_bar_scroll_state: ScrollState,
palette_scroll_state: ScrollState,
}
pub fn workspace<V: 'static>() -> impl Element<V> {
WorkspaceElement::default()
bottom_panel_scroll_state: ScrollState,
}
impl WorkspaceElement {
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()
// Elevation Level 0
.size_full()
.flex()
.flex_col()
@ -34,9 +72,7 @@ impl WorkspaceElement {
.items_start()
.text_color(theme.lowest.base.default.foreground)
.fill(theme.lowest.base.default.background)
.relative()
// Elevation Level 1
.child(title_bar())
.child(TitleBar::new(cx))
.child(
div()
.flex_1()
@ -44,37 +80,57 @@ impl WorkspaceElement {
.flex()
.flex_row()
.overflow_hidden()
.child(project_panel(self.project_panel_scroll_state.clone()))
.child(collab_panel(self.collab_panel_scroll_state.clone()))
.border_t()
.border_b()
.border_color(theme.lowest.base.default.border)
.child(
div()
.h_full()
ProjectPanel::new(self.left_panel_scroll_state.clone())
.side(PanelSide::Left),
)
.child(
v_stack()
.flex_1()
.fill(theme.highest.base.default.background)
.h_full()
.child(
div()
.flex()
.flex_col()
.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())
// 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())),
// )
.child(StatusBar::new())
}
}

View file

@ -1,17 +1,19 @@
mod avatar;
mod button;
mod details;
mod icon;
mod indicator;
mod input;
mod label;
mod text_button;
mod player;
mod stack;
mod tool_divider;
pub use avatar::*;
pub use button::*;
pub use details::*;
pub use icon::*;
pub use indicator::*;
pub use input::*;
pub use label::*;
pub use text_button::*;
pub use player::*;
pub use stack::*;
pub use tool_divider::*;

View file

@ -1,6 +1,5 @@
use gpui2::elements::img;
use gpui2::style::StyleHelpers;
use gpui2::{ArcCow, Element, IntoElement, ViewContext};
use gpui2::ArcCow;
use crate::prelude::*;
use crate::theme;
@ -11,14 +10,14 @@ pub struct Avatar {
shape: Shape,
}
pub fn avatar(src: impl Into<ArcCow<'static, str>>) -> Avatar {
Avatar {
src: src.into(),
shape: Shape::Circle,
}
}
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 {
self.shape = shape;
self

View 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
}
}

View file

@ -1,7 +1,4 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::prelude::*;
use crate::theme;
#[derive(Element, Clone)]
@ -10,11 +7,11 @@ pub struct Details {
meta: Option<&'static str>,
}
pub fn details(text: &'static str) -> Details {
Details { text, meta: None }
}
impl Details {
pub fn new(text: &'static str) -> Self {
Self { text, meta: None }
}
pub fn meta_text(mut self, meta: &'static str) -> Self {
self.meta = Some(meta);
self

View file

@ -1,11 +1,19 @@
use std::sync::Arc;
use gpui2::elements::svg;
use gpui2::Hsla;
use strum::EnumIter;
use crate::prelude::*;
use crate::theme::theme;
use crate::Theme;
use gpui2::elements::svg;
use gpui2::style::StyleHelpers;
use gpui2::{Element, ViewContext};
use gpui2::{Hsla, IntoElement};
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconSize {
Small,
#[default]
Large,
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconColor {
@ -37,8 +45,8 @@ impl IconColor {
}
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconAsset {
#[derive(Default, PartialEq, Copy, Clone, EnumIter)]
pub enum Icon {
Ai,
ArrowLeft,
ArrowRight,
@ -53,6 +61,7 @@ pub enum IconAsset {
Close,
ExclamationTriangle,
File,
FileGeneric,
FileDoc,
FileGit,
FileLock,
@ -67,89 +76,106 @@ pub enum IconAsset {
InlayHint,
MagicWand,
MagnifyingGlass,
Maximize,
Menu,
MessageBubbles,
Mic,
MicMute,
Plus,
Quote,
Screen,
Split,
SplitMessage,
Terminal,
XCircle,
Copilot,
Envelope,
}
impl IconAsset {
impl Icon {
pub fn path(self) -> &'static str {
match self {
IconAsset::Ai => "icons/ai.svg",
IconAsset::ArrowLeft => "icons/arrow_left.svg",
IconAsset::ArrowRight => "icons/arrow_right.svg",
IconAsset::ArrowUpRight => "icons/arrow_up_right.svg",
IconAsset::AudioOff => "icons/speaker-off.svg",
IconAsset::AudioOn => "icons/speaker-loud.svg",
IconAsset::Bolt => "icons/bolt.svg",
IconAsset::ChevronDown => "icons/chevron_down.svg",
IconAsset::ChevronLeft => "icons/chevron_left.svg",
IconAsset::ChevronRight => "icons/chevron_right.svg",
IconAsset::ChevronUp => "icons/chevron_up.svg",
IconAsset::Close => "icons/x.svg",
IconAsset::ExclamationTriangle => "icons/warning.svg",
IconAsset::File => "icons/file_icons/file.svg",
IconAsset::FileDoc => "icons/file_icons/book.svg",
IconAsset::FileGit => "icons/file_icons/git.svg",
IconAsset::FileLock => "icons/file_icons/lock.svg",
IconAsset::FileRust => "icons/file_icons/rust.svg",
IconAsset::FileToml => "icons/file_icons/toml.svg",
IconAsset::FileTree => "icons/project.svg",
IconAsset::Folder => "icons/file_icons/folder.svg",
IconAsset::FolderOpen => "icons/file_icons/folder_open.svg",
IconAsset::FolderX => "icons/stop_sharing.svg",
IconAsset::Hash => "icons/hash.svg",
IconAsset::InlayHint => "icons/inlay_hint.svg",
IconAsset::MagicWand => "icons/magic-wand.svg",
IconAsset::MagnifyingGlass => "icons/magnifying_glass.svg",
IconAsset::MessageBubbles => "icons/conversations.svg",
IconAsset::Mic => "icons/mic.svg",
IconAsset::MicMute => "icons/mic-mute.svg",
IconAsset::Plus => "icons/plus.svg",
IconAsset::Screen => "icons/desktop.svg",
IconAsset::Split => "icons/split.svg",
IconAsset::Terminal => "icons/terminal.svg",
IconAsset::XCircle => "icons/error.svg",
IconAsset::Copilot => "icons/copilot.svg",
IconAsset::Envelope => "icons/feedback.svg",
Icon::Ai => "icons/ai.svg",
Icon::ArrowLeft => "icons/arrow_left.svg",
Icon::ArrowRight => "icons/arrow_right.svg",
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
Icon::AudioOff => "icons/speaker-off.svg",
Icon::AudioOn => "icons/speaker-loud.svg",
Icon::Bolt => "icons/bolt.svg",
Icon::ChevronDown => "icons/chevron_down.svg",
Icon::ChevronLeft => "icons/chevron_left.svg",
Icon::ChevronRight => "icons/chevron_right.svg",
Icon::ChevronUp => "icons/chevron_up.svg",
Icon::Close => "icons/x.svg",
Icon::ExclamationTriangle => "icons/warning.svg",
Icon::File => "icons/file.svg",
Icon::FileGeneric => "icons/file_icons/file.svg",
Icon::FileDoc => "icons/file_icons/book.svg",
Icon::FileGit => "icons/file_icons/git.svg",
Icon::FileLock => "icons/file_icons/lock.svg",
Icon::FileRust => "icons/file_icons/rust.svg",
Icon::FileToml => "icons/file_icons/toml.svg",
Icon::FileTree => "icons/project.svg",
Icon::Folder => "icons/file_icons/folder.svg",
Icon::FolderOpen => "icons/file_icons/folder_open.svg",
Icon::FolderX => "icons/stop_sharing.svg",
Icon::Hash => "icons/hash.svg",
Icon::InlayHint => "icons/inlay_hint.svg",
Icon::MagicWand => "icons/magic-wand.svg",
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
Icon::Maximize => "icons/maximize.svg",
Icon::Menu => "icons/menu.svg",
Icon::MessageBubbles => "icons/conversations.svg",
Icon::Mic => "icons/mic.svg",
Icon::MicMute => "icons/mic-mute.svg",
Icon::Plus => "icons/plus.svg",
Icon::Quote => "icons/quote.svg",
Icon::Screen => "icons/desktop.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)]
pub struct Icon {
asset: IconAsset,
pub struct IconElement {
icon: Icon,
color: IconColor,
size: IconSize,
}
pub fn icon(asset: IconAsset) -> Icon {
Icon {
asset,
color: IconColor::default(),
impl IconElement {
pub fn new(icon: Icon) -> Self {
Self {
icon,
color: IconColor::default(),
size: IconSize::default(),
}
}
}
impl Icon {
pub fn color(mut self, color: IconColor) -> Self {
self.color = color;
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> {
let theme = theme(cx);
let fill = self.color.color(theme);
svg()
.flex_none()
.path(self.asset.path())
.size_4()
.fill(fill)
let sized_svg = match self.size {
IconSize::Small => svg().size_3p5(),
IconSize::Large => svg().size_4(),
};
sized_svg.flex_none().path(self.icon.path()).fill(fill)
}
}

View file

@ -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)
}
}

View file

@ -1,10 +1,13 @@
use gpui2::elements::div;
use gpui2::style::{StyleHelpers, Styleable};
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::prelude::*;
use crate::theme;
#[derive(Default, PartialEq)]
pub enum InputVariant {
#[default]
Ghost,
Filled,
}
#[derive(Element)]
pub struct Input {
placeholder: &'static str,
@ -13,24 +16,26 @@ pub struct Input {
variant: InputVariant,
}
pub fn input(placeholder: &'static str) -> Input {
Input {
placeholder,
value: "".to_string(),
state: InteractionState::default(),
variant: InputVariant::default(),
}
}
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 {
self.value = value;
self
}
pub fn state(mut self, state: InteractionState) -> Self {
self.state = state;
self
}
pub fn variant(mut self, variant: InputVariant) -> Self {
self.variant = variant;
self

View file

@ -1,8 +1,8 @@
use gpui2::{Hsla, WindowContext};
use smallvec::SmallVec;
use crate::prelude::*;
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)]
pub enum LabelColor {
@ -12,8 +12,28 @@ pub enum LabelColor {
Created,
Modified,
Deleted,
Disabled,
Hidden,
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)]
@ -25,20 +45,27 @@ pub enum LabelSize {
#[derive(Element, Clone)]
pub struct Label {
label: &'static str,
label: String,
color: LabelColor,
size: LabelSize,
}
pub fn label(label: &'static str) -> Label {
Label {
label,
color: LabelColor::Default,
size: LabelSize::Default,
}
highlight_indices: Vec<usize>,
strikethrough: bool,
}
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 {
self.color = color;
self
@ -49,27 +76,86 @@ impl Label {
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> {
let theme = theme(cx);
let color = match self.color {
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 highlight_color = theme.lowest.accent.default.foreground;
let mut div = div();
let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
if self.size == LabelSize::Small {
div = div.text_xs();
} else {
div = div.text_sm();
let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
for (char_ix, char) in self.label.char_indices() {
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,
}

View 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
}
}

View 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()
}

View file

@ -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())
}
}

View file

@ -1,17 +1,14 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ViewContext};
use crate::prelude::*;
use crate::theme;
#[derive(Element)]
pub struct ToolDivider {}
pub fn tool_divider<V: 'static>() -> impl Element<V> {
ToolDivider {}
}
impl ToolDivider {
pub fn new() -> Self {
Self {}
}
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);

View file

@ -1,5 +1,6 @@
#![allow(dead_code, unused_variables)]
mod children;
mod components;
mod element_ext;
mod elements;
@ -8,10 +9,12 @@ mod static_data;
mod theme;
mod tokens;
pub use crate::theme::*;
pub use children::*;
pub use components::*;
pub use element_ext::*;
pub use elements::*;
pub use prelude::*;
pub use static_data::*;
pub use tokens::*;
pub use crate::theme::*;

View file

@ -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)]
pub enum OrderMethod {
#[default]
@ -6,20 +154,6 @@ pub enum OrderMethod {
MostRecent,
}
#[derive(Default, PartialEq)]
pub enum ButtonVariant {
#[default]
Ghost,
Filled,
}
#[derive(Default, PartialEq)]
pub enum InputVariant {
#[default]
Ghost,
Filled,
}
#[derive(Default, PartialEq, Clone, Copy)]
pub enum Shape {
#[default]
@ -34,14 +168,13 @@ pub enum DisclosureControlVisibility {
Always,
}
#[derive(Default, PartialEq, Clone, Copy)]
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
pub enum InteractionState {
#[default]
Enabled,
Hovered,
Active,
Focused,
Dragged,
Disabled,
}
@ -63,8 +196,60 @@ pub enum SelectedState {
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 {
/// The "on" state of a toggleable element.
///
/// Example:
/// - A collasable list that is currently expanded
/// - A toggle button that is currently on.
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,
}
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
}
}
}

View file

@ -1,166 +1,558 @@
use gpui2::WindowContext;
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> {
vec![
list_item(label("zed"))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("zed"))
.left_icon(Icon::FolderOpen.into())
.indent_level(0)
.set_toggle(ToggleState::Toggled),
list_item(label(".cargo"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new(".cargo"))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label(".config"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new(".config"))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label(".git").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new(".git").color(LabelColor::Hidden))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label(".cargo"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new(".cargo"))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label(".idea").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new(".idea").color(LabelColor::Hidden))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label("assets"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("assets"))
.left_icon(Icon::Folder.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("cargo-target").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label("crates"))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("crates"))
.left_icon(Icon::FolderOpen.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("activity_indicator"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("activity_indicator"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("ai"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("ai"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("audio"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("audio"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("auto_update"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("auto_update"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("breadcrumbs"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("breadcrumbs"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("call"))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("call"))
.left_icon(Icon::Folder.into())
.indent_level(2),
list_item(label("sqlez").color(LabelColor::Modified))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("sqlez").color(LabelColor::Modified))
.left_icon(Icon::Folder.into())
.indent_level(2)
.set_toggle(ToggleState::NotToggled),
list_item(label("gpui2"))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("gpui2"))
.left_icon(Icon::FolderOpen.into())
.indent_level(2)
.set_toggle(ToggleState::Toggled),
list_item(label("src"))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("src"))
.left_icon(Icon::FolderOpen.into())
.indent_level(3)
.set_toggle(ToggleState::Toggled),
list_item(label("derrive_element.rs"))
.left_icon(IconAsset::FileRust.into())
ListEntry::new(Label::new("derrive_element.rs"))
.left_icon(Icon::FileRust.into())
.indent_level(4),
list_item(label("storybook").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("docs").color(LabelColor::Default))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("docs").color(LabelColor::Default))
.left_icon(Icon::Folder.into())
.indent_level(2)
.set_toggle(ToggleState::Toggled),
list_item(label("src").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("src").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into())
.indent_level(3)
.set_toggle(ToggleState::Toggled),
list_item(label("ui").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("ui").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into())
.indent_level(4)
.set_toggle(ToggleState::Toggled),
list_item(label("component").color(LabelColor::Created))
.left_icon(IconAsset::FolderOpen.into())
ListEntry::new(Label::new("component").color(LabelColor::Created))
.left_icon(Icon::FolderOpen.into())
.indent_level(5)
.set_toggle(ToggleState::Toggled),
list_item(label("facepile.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default))
.left_icon(Icon::FileRust.into())
.indent_level(6),
list_item(label("follow_group.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default))
.left_icon(Icon::FileRust.into())
.indent_level(6),
list_item(label("list_item.rs").color(LabelColor::Created))
.left_icon(IconAsset::FileRust.into())
ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created))
.left_icon(Icon::FileRust.into())
.indent_level(6),
list_item(label("tab.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
ListEntry::new(Label::new("tab.rs").color(LabelColor::Default))
.left_icon(Icon::FileRust.into())
.indent_level(6),
list_item(label("target").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
ListEntry::new(Label::new("target").color(LabelColor::Hidden))
.left_icon(Icon::Folder.into())
.indent_level(1),
list_item(label(".dockerignore"))
.left_icon(IconAsset::File.into())
ListEntry::new(Label::new(".dockerignore"))
.left_icon(Icon::FileGeneric.into())
.indent_level(1),
list_item(label(".DS_Store").color(LabelColor::Hidden))
.left_icon(IconAsset::File.into())
ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden))
.left_icon(Icon::FileGeneric.into())
.indent_level(1),
list_item(label("Cargo.lock"))
.left_icon(IconAsset::FileLock.into())
ListEntry::new(Label::new("Cargo.lock"))
.left_icon(Icon::FileLock.into())
.indent_level(1),
list_item(label("Cargo.toml"))
.left_icon(IconAsset::FileToml.into())
ListEntry::new(Label::new("Cargo.toml"))
.left_icon(Icon::FileToml.into())
.indent_level(1),
list_item(label("Dockerfile"))
.left_icon(IconAsset::File.into())
ListEntry::new(Label::new("Dockerfile"))
.left_icon(Icon::FileGeneric.into())
.indent_level(1),
list_item(label("Procfile"))
.left_icon(IconAsset::File.into())
ListEntry::new(Label::new("Procfile"))
.left_icon(Icon::FileGeneric.into())
.indent_level(1),
list_item(label("README.md"))
.left_icon(IconAsset::FileDoc.into())
ListEntry::new(Label::new("README.md"))
.left_icon(Icon::FileDoc.into())
.indent_level(1),
]
.into_iter()
.map(From::from)
.collect()
}
pub fn static_project_panel_single_items() -> Vec<ListItem> {
vec![
list_item(label("todo.md"))
.left_icon(IconAsset::FileDoc.into())
ListEntry::new(Label::new("todo.md"))
.left_icon(Icon::FileDoc.into())
.indent_level(0),
list_item(label("README.md"))
.left_icon(IconAsset::FileDoc.into())
ListEntry::new(Label::new("README.md"))
.left_icon(Icon::FileDoc.into())
.indent_level(0),
list_item(label("config.json"))
.left_icon(IconAsset::File.into())
ListEntry::new(Label::new("config.json"))
.left_icon(Icon::FileGeneric.into())
.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> {
vec![
palette_item("New File", Some("Ctrl+N")),
palette_item("Open File", Some("Ctrl+O")),
palette_item("Save File", Some("Ctrl+S")),
palette_item("Cut", Some("Ctrl+X")),
palette_item("Copy", Some("Ctrl+C")),
palette_item("Paste", Some("Ctrl+V")),
palette_item("Undo", Some("Ctrl+Z")),
palette_item("Redo", Some("Ctrl+Shift+Z")),
palette_item("Find", Some("Ctrl+F")),
palette_item("Replace", Some("Ctrl+R")),
palette_item("Jump to Line", None),
palette_item("Select All", None),
palette_item("Deselect All", None),
palette_item("Switch Document", None),
palette_item("Insert Line Below", None),
palette_item("Insert Line Above", None),
palette_item("Move Line Up", None),
palette_item("Move Line Down", None),
palette_item("Toggle Comment", None),
palette_item("Delete Line", None),
PaletteItem::new("New File").keybinding(Keybinding::new(
"N".to_string(),
ModifierKeys::new().control(true),
)),
PaletteItem::new("Open File").keybinding(Keybinding::new(
"O".to_string(),
ModifierKeys::new().control(true),
)),
PaletteItem::new("Save File").keybinding(Keybinding::new(
"S".to_string(),
ModifierKeys::new().control(true),
)),
PaletteItem::new("Cut").keybinding(Keybinding::new(
"X".to_string(),
ModifierKeys::new().control(true),
)),
PaletteItem::new("Copy").keybinding(Keybinding::new(
"C".to_string(),
ModifierKeys::new().control(true),
)),
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,
},
]
}

View file

@ -1,14 +1,21 @@
use gpui2::geometry::AbsoluteLength;
use gpui2::{hsla, Hsla};
#[derive(Clone, Copy)]
pub struct Token {
pub list_indent_depth: AbsoluteLength,
pub default_panel_size: AbsoluteLength,
pub state_hover_background: Hsla,
pub state_active_background: Hsla,
}
impl Default for Token {
fn default() -> Self {
Self {
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),
}
}
}