Bring UI crate up to date

This commit is contained in:
Nate Butler 2023-09-21 23:46:06 -04:00
parent 66358f2900
commit f54634aeb2
24 changed files with 1090 additions and 167 deletions

View File

@ -0,0 +1,57 @@
# Elevation
Elevation in Zed applies to all surfaces and components. Elevation is categorized into levels.
Elevation accomplishes the following:
- Allows surfaces to move in front of or behind others, such as content scrolling beneath app top bars.
- Reflects spatial relationships, for instance, how a floating action buttons shadow intimates its disconnection from a collection of cards.
- Directs attention to structures at the highest elevation, like a temporary dialog arising in front of other surfaces.
Elevations are the initial elevation values assigned to components by default.
Components may transition to a higher elevation in some cases, like user interations.
On such occasions, components transition to predetermined dynamic elevation offsets. These are the typical elevations to which components move when they are not at rest.
## Understanding Elevation
Elevation can be thought of as the physical closeness of an element to the user. Elements with lower elevations are physically further away from the user on the z-axis and appear to be underneath elements with higher elevations.
Material Design 3 has a some great visualizations of elevation that may be helpful to understanding the mental modal of elevation. [Material Design Elevation](https://m3.material.io/styles/elevation/overview)
## Elevation Levels
Zed integrates six unique elevation levels in its design system. The elevation of a surface is expressed as a whole number ranging from 0 to 5, both numbers inclusive. A components elevation is ascertained by combining the components resting elevation with any dynamic elevation offsets.
The levels are detailed as follows:
0. App Background
1. UI Surface
2. Elevated Elements
3. Wash
4. Focused Element
5. Dragged Element
### 0. App Background
The app background constitutes the lowest elevation layer, appearing behind all other surfaces and components. It is predominantly used for the background color of the app.
### 1. UI Surface
The UI Surface is the standard elevation for components and is placed above the app background. It is generally used for the background color of the app bar, card, and sheet.
### 2. Elevated Elements
Elevated elements appear above the UI surface layer surfaces and components. Elevated elements are predominantly used for creating popovers, context menus, and tooltips.
### 3. Wash
Wash denotes a distinct elevation reserved to isolate app UI layers from high elevation components such as modals, notifications, and overlaid panels. The wash may not consistently be visible when these components are active. This layer is often referred to as a scrim or overlay and the background color of the wash is typically deployed in its design.
### 4. Focused Element
Focused elements obtain a higher elevation above surfaces and components at wash elevation. They are often used for modals, notifications, and overlaid panels and indicate that they are the sole element the user is interacting with at the moment.
### 5. Dragged Element
Dragged elements gain the highest elevation, thus appearing above surfaces and components at the elevation of focused elements. These are typically used for elements that are being dragged, following the cursor

View File

@ -1,11 +1,15 @@
mod facepile;
mod follow_group;
mod list_item;
mod list_section_header;
mod palette_item;
mod tab;
pub use facepile::*;
pub use follow_group::*;
pub use list_item::*;
pub use list_section_header::*;
pub use palette_item::*;
pub use tab::*;
use std::marker::PhantomData;

View File

@ -1,17 +1,18 @@
use gpui2::elements::div;
use gpui2::geometry::rems;
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::{Element, IntoElement, ParentElement, ViewContext};
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
use crate::prelude::*;
use crate::{icon, theme, IconAsset, Label};
#[derive(Element)]
#[derive(Element, Clone)]
pub struct ListItem {
label: Label,
left_icon: Option<IconAsset>,
indent_level: u32,
state: InteractionState,
disclosure_control_style: DisclosureControlVisibility,
toggle: Option<ToggleState>,
}
@ -20,6 +21,7 @@ pub fn list_item(label: Label) -> ListItem {
label,
indent_level: 0,
left_icon: None,
disclosure_control_style: DisclosureControlVisibility::default(),
state: InteractionState::default(),
toggle: None,
}
@ -46,8 +48,30 @@ impl ListItem {
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)
@ -56,31 +80,31 @@ impl ListItem {
.active()
.fill(theme.middle.base.pressed.background)
.relative()
.py_1()
.child(
div()
.h_7()
.h_6()
.px_2()
// .ml(rems(0.75 * self.indent_level as f32))
.children((0..self.indent_level).map(|_| {
div().w(rems(0.75)).h_full().flex().justify_center().child(
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)
.hover()
.fill(theme.middle.warning.default.border)
.active()
.fill(theme.middle.negative.default.border),
.fill(theme.middle.base.default.border),
)
}))
.flex()
.gap_2()
.gap_1()
.items_center()
.children(match self.toggle {
Some(ToggleState::NotToggled) => Some(icon(IconAsset::ChevronRight)),
Some(ToggleState::Toggled) => Some(icon(IconAsset::ChevronDown)),
None => None,
})
.relative()
.children(disclosure_control)
.children(self.left_icon.map(|i| icon(i)))
.child(self.label.clone()),
)

View File

@ -0,0 +1,88 @@
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

@ -0,0 +1,63 @@
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

@ -1,29 +1,30 @@
use crate::theme::theme;
use gpui2::elements::svg;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ViewContext};
use crate::theme;
// Icon::Hash
// icon(IconAsset::Hash).color(IconColor::Warning)
// Icon::new(IconAsset::Hash).color(IconColor::Warning)
use gpui2::IntoElement;
use gpui2::{Element, ViewContext};
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconAsset {
Ai,
ArrowLeft,
ArrowRight,
#[default]
ArrowUpRight,
Bolt,
Hash,
File,
Folder,
FolderOpen,
ChevronDown,
ChevronUp,
ChevronLeft,
ChevronRight,
ChevronUp,
#[default]
File,
FileDoc,
FileGit,
FileLock,
FileRust,
FileToml,
Folder,
FolderOpen,
Hash,
}
impl IconAsset {
@ -34,14 +35,19 @@ impl IconAsset {
IconAsset::ArrowRight => "icons/arrow_right.svg",
IconAsset::ArrowUpRight => "icons/arrow_up_right.svg",
IconAsset::Bolt => "icons/bolt.svg",
IconAsset::Hash => "icons/hash.svg",
IconAsset::ChevronDown => "icons/chevron_down.svg",
IconAsset::ChevronUp => "icons/chevron_up.svg",
IconAsset::ChevronLeft => "icons/chevron_left.svg",
IconAsset::ChevronRight => "icons/chevron_right.svg",
IconAsset::ChevronUp => "icons/chevron_up.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::Folder => "icons/file_icons/folder.svg",
IconAsset::FolderOpen => "icons/file_icons/folder_open.svg",
IconAsset::Hash => "icons/hash.svg",
}
}
}
@ -55,19 +61,14 @@ pub fn icon(asset: IconAsset) -> Icon {
Icon { asset }
}
// impl Icon {
// pub fn new(asset: IconAsset) -> Icon {
// Icon { asset }
// }
// }
impl Icon {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
svg()
.flex_none()
.path(self.asset.path())
.size_4()
.fill(theme.lowest.base.default.foreground)
.fill(theme.lowest.variant.default.foreground)
}
}

View File

@ -1,29 +1,40 @@
use crate::theme::theme;
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::theme;
use gpui2::{Element, ViewContext};
use gpui2::{IntoElement, ParentElement};
#[derive(Default, PartialEq, Copy, Clone)]
pub enum LabelColor {
#[default]
Default,
Muted,
Created,
Modified,
Deleted,
Hidden,
Placeholder,
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum LabelSize {
#[default]
Default,
Small,
}
#[derive(Element, Clone)]
pub struct Label {
label: &'static str,
color: LabelColor,
size: LabelSize,
}
pub fn label(label: &'static str) -> Label {
Label {
label,
color: LabelColor::Default,
size: LabelSize::Default,
}
}
@ -33,17 +44,32 @@ impl Label {
self
}
pub fn size(mut self, size: LabelSize) -> 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 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,
};
div().text_sm().text_color(color).child(self.label.clone())
let mut div = div();
if self.size == LabelSize::Small {
div = div.text_xs();
} else {
div = div.text_sm();
}
div.text_color(color).child(self.label.clone())
}
}

View File

@ -5,10 +5,17 @@ mod element_ext;
mod elements;
mod modules;
pub mod prelude;
mod static_data;
mod templates;
mod theme;
mod tokens;
pub use crate::theme::*;
pub use components::*;
pub use element_ext::*;
pub use elements::*;
pub use modules::*;
pub use prelude::*;
pub use static_data::*;
pub use templates::*;
pub use tokens::*;

View File

@ -1,11 +1,5 @@
mod chat_panel;
mod project_panel;
mod status_bar;
mod tab_bar;
mod title_bar;
mod list;
mod palette;
pub use chat_panel::*;
pub use project_panel::*;
pub use status_bar::*;
pub use tab_bar::*;
pub use title_bar::*;
pub use list::*;
pub use palette::*;

View File

@ -0,0 +1,64 @@
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};
#[derive(Element)]
pub struct List {
header: Option<ListSectionHeader>,
items: Vec<ListItem>,
empty_message: &'static str,
toggle: Option<ToggleState>,
// footer: Option<ListSectionFooter>,
}
pub fn list(items: Vec<ListItem>) -> List {
List {
header: None,
items,
empty_message: "No items",
toggle: None,
}
}
impl List {
pub fn header(mut self, header: ListSectionHeader) -> Self {
self.header = Some(header);
self
}
pub fn empty_message(mut self, empty_message: &'static str) -> Self {
self.empty_message = empty_message;
self
}
pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
self.toggle = Some(toggle);
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(icon(IconAsset::ChevronRight)),
Some(ToggleState::Toggled) => Some(icon(IconAsset::ChevronDown)),
None => None,
};
div()
.py_1()
.flex()
.flex_col()
.children(self.header.map(|h| h))
.children(
self.items
.is_empty()
.then(|| label(self.empty_message).color(LabelColor::Muted)),
)
.children(self.items.iter().cloned())
}
}

View File

@ -0,0 +1,124 @@
use std::marker::PhantomData;
use crate::prelude::OrderMethod;
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};
#[derive(Element)]
pub struct Palette<V: 'static> {
view_type: PhantomData<V>,
scroll_state: ScrollState,
input_placeholder: &'static str,
empty_string: &'static str,
items: Vec<PaletteItem>,
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);
self.items = items;
self
}
pub fn placeholder(mut self, input_placeholder: &'static str) -> Self {
self.input_placeholder = input_placeholder;
self
}
pub fn empty_string(mut self, empty_string: &'static str) -> Self {
self.empty_string = empty_string;
self
}
// TODO: Hook up sort order
pub fn default_order(mut self, default_order: OrderMethod) -> Self {
self.default_order = default_order;
self
}
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.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()
.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(div().h_px().w_full().fill(theme.lowest.base.default.border))
.child(
div()
.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),
),
)
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.iter().map(|item| {
div()
.flex()
.flex_row()
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover()
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(palette_item(item.label, item.keybinding))
})),
),
)
}
}

View File

@ -1,94 +0,0 @@
use std::marker::PhantomData;
use gpui2::elements::div;
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::prelude::*;
use crate::{details, input, label, list_item, theme, IconAsset, LabelColor};
#[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,
}
}
impl<V: 'static> ProjectPanel<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.w_56()
.h_full()
.flex()
.flex_col()
.fill(theme.middle.base.default.background)
.child(
div()
.w_56()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
.child(details("This is a long string that should wrap when it keeps going for a long time.").meta_text("6 h ago)"))
.child(
div().flex().flex_col().children(
std::iter::repeat_with(|| {
vec![
list_item(label("sqlez").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(0)
.set_toggle(ToggleState::NotToggled),
list_item(label("storybook").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(0)
.set_toggle(ToggleState::Toggled),
list_item(label("docs").color(LabelColor::Default))
.left_icon(IconAsset::Folder.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("src").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(2)
.set_toggle(ToggleState::Toggled),
list_item(label("ui").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(3)
.set_toggle(ToggleState::Toggled),
list_item(label("component").color(LabelColor::Created))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(4)
.set_toggle(ToggleState::Toggled),
list_item(label("facepile.rs").color(LabelColor::Default))
.left_icon(IconAsset::File.into())
.indent_level(5),
list_item(label("follow_group.rs").color(LabelColor::Default))
.left_icon(IconAsset::File.into())
.indent_level(5),
list_item(label("list_item.rs").color(LabelColor::Created))
.left_icon(IconAsset::File.into())
.indent_level(5),
list_item(label("tab.rs").color(LabelColor::Default))
.left_icon(IconAsset::File.into())
.indent_level(5),
]
})
.take(10)
.flatten(),
),
),
)
.child(
input("Find something...")
.value("buffe".to_string())
.state(InteractionState::Focused),
)
}
}

View File

@ -1,3 +1,11 @@
#[derive(Default, PartialEq)]
pub enum OrderMethod {
#[default]
Ascending,
Descending,
MostRecent,
}
#[derive(Default, PartialEq)]
pub enum ButtonVariant {
#[default]
@ -19,6 +27,13 @@ pub enum Shape {
RoundedRectangle,
}
#[derive(Default, PartialEq, Clone, Copy)]
pub enum DisclosureControlVisibility {
#[default]
OnHover,
Always,
}
#[derive(Default, PartialEq, Clone, Copy)]
pub enum InteractionState {
#[default]

View File

@ -0,0 +1,166 @@
use crate::{
label, list_item, palette_item, IconAsset, LabelColor, ListItem, PaletteItem, ToggleState,
};
pub fn static_project_panel_project_items() -> Vec<ListItem> {
vec![
list_item(label("zed"))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(0)
.set_toggle(ToggleState::Toggled),
list_item(label(".cargo"))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label(".config"))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label(".git").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label(".cargo"))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label(".idea").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label("assets"))
.left_icon(IconAsset::Folder.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("cargo-target").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label("crates"))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("activity_indicator"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("ai"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("audio"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("auto_update"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("breadcrumbs"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("call"))
.left_icon(IconAsset::Folder.into())
.indent_level(2),
list_item(label("sqlez").color(LabelColor::Modified))
.left_icon(IconAsset::Folder.into())
.indent_level(2)
.set_toggle(ToggleState::NotToggled),
list_item(label("gpui2"))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(2)
.set_toggle(ToggleState::Toggled),
list_item(label("src"))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(3)
.set_toggle(ToggleState::Toggled),
list_item(label("derrive_element.rs"))
.left_icon(IconAsset::FileRust.into())
.indent_level(4),
list_item(label("storybook").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(1)
.set_toggle(ToggleState::Toggled),
list_item(label("docs").color(LabelColor::Default))
.left_icon(IconAsset::Folder.into())
.indent_level(2)
.set_toggle(ToggleState::Toggled),
list_item(label("src").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(3)
.set_toggle(ToggleState::Toggled),
list_item(label("ui").color(LabelColor::Modified))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(4)
.set_toggle(ToggleState::Toggled),
list_item(label("component").color(LabelColor::Created))
.left_icon(IconAsset::FolderOpen.into())
.indent_level(5)
.set_toggle(ToggleState::Toggled),
list_item(label("facepile.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
.indent_level(6),
list_item(label("follow_group.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
.indent_level(6),
list_item(label("list_item.rs").color(LabelColor::Created))
.left_icon(IconAsset::FileRust.into())
.indent_level(6),
list_item(label("tab.rs").color(LabelColor::Default))
.left_icon(IconAsset::FileRust.into())
.indent_level(6),
list_item(label("target").color(LabelColor::Hidden))
.left_icon(IconAsset::Folder.into())
.indent_level(1),
list_item(label(".dockerignore"))
.left_icon(IconAsset::File.into())
.indent_level(1),
list_item(label(".DS_Store").color(LabelColor::Hidden))
.left_icon(IconAsset::File.into())
.indent_level(1),
list_item(label("Cargo.lock"))
.left_icon(IconAsset::FileLock.into())
.indent_level(1),
list_item(label("Cargo.toml"))
.left_icon(IconAsset::FileToml.into())
.indent_level(1),
list_item(label("Dockerfile"))
.left_icon(IconAsset::File.into())
.indent_level(1),
list_item(label("Procfile"))
.left_icon(IconAsset::File.into())
.indent_level(1),
list_item(label("README.md"))
.left_icon(IconAsset::FileDoc.into())
.indent_level(1),
]
}
pub fn static_project_panel_single_items() -> Vec<ListItem> {
vec![
list_item(label("todo.md"))
.left_icon(IconAsset::FileDoc.into())
.indent_level(0),
list_item(label("README.md"))
.left_icon(IconAsset::FileDoc.into())
.indent_level(0),
list_item(label("config.json"))
.left_icon(IconAsset::File.into())
.indent_level(0),
]
}
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),
]
}

View File

@ -0,0 +1,17 @@
mod chat_panel;
mod collab_panel;
mod command_palette;
mod project_panel;
mod status_bar;
mod tab_bar;
mod title_bar;
mod workspace;
pub use chat_panel::*;
pub use collab_panel::*;
pub use command_palette::*;
pub use project_panel::*;
pub use status_bar::*;
pub use tab_bar::*;
pub use title_bar::*;
pub use workspace::*;

View File

@ -1,11 +1,11 @@
use std::marker::PhantomData;
use gpui2::elements::div;
use crate::icon_button;
use crate::theme::theme;
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::{icon_button, theme};
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
#[derive(Element)]
pub struct ChatPanel<V: 'static> {

View File

@ -0,0 +1,177 @@
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;
#[derive(Element)]
pub struct CollabPanelElement<V: 'static> {
view_type: PhantomData<V>,
scroll_state: ScrollState,
}
// When I improve child view rendering, I'd like to have V implement a trait that
// provides the scroll state, among other things.
pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
CollabPanelElement {
view_type: PhantomData,
scroll_state,
}
}
impl<V: 'static> CollabPanelElement<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
// Panel
div()
.w_64()
.h_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
.text_color(theme.middle.base.default.foreground)
.border_color(theme.middle.base.default.border)
.border()
.fill(theme.middle.base.default.background)
.child(
div()
.w_full()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
// List Container
.child(
div()
.fill(theme.lowest.base.default.background)
.pb_1()
.border_color(theme.lowest.base.default.border)
.border_b()
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
// .group()
// List Section Header
.child(self.list_section_header("#CRDB", true, theme))
// List Item Large
.child(self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CHANNELS", true, theme)),
)
.child(
div()
.py_2()
.flex()
.flex_col()
.child(self.list_section_header("CONTACTS", true, theme))
.children(
std::iter::repeat_with(|| {
vec![
self.list_item(
"http://github.com/as-cii.png?s=50",
"as-cii",
theme,
),
self.list_item(
"http://github.com/nathansobo.png?s=50",
"nathansobo",
theme,
),
self.list_item(
"http://github.com/maxbrunsfeld.png?s=50",
"maxbrunsfeld",
theme,
),
]
})
.take(3)
.flatten(),
),
),
)
.child(
div()
.h_7()
.px_2()
.border_t()
.border_color(theme.middle.variant.default.border)
.flex()
.items_center()
.child(
div()
.text_sm()
.text_color(theme.middle.variant.default.foreground)
.child("Find..."),
),
)
}
fn list_section_header(
&self,
label: impl Into<ArcCow<'static, str>>,
expanded: bool,
theme: &Theme,
) -> impl Element<V> {
div()
.h_7()
.px_2()
.flex()
.justify_between()
.items_center()
.child(div().flex().gap_1().text_sm().child(label))
.child(
div().flex().h_full().gap_1().items_center().child(
svg()
.path(if expanded {
"icons/caret_down.svg"
} else {
"icons/caret_up.svg"
})
.w_3p5()
.h_3p5()
.fill(theme.middle.variant.default.foreground),
),
)
}
fn list_item(
&self,
avatar_uri: impl Into<ArcCow<'static, str>>,
label: impl Into<ArcCow<'static, str>>,
theme: &Theme,
) -> impl Element<V> {
div()
.h_7()
.px_2()
.flex()
.items_center()
.hover()
.fill(theme.lowest.variant.hovered.background)
.active()
.fill(theme.lowest.variant.pressed.background)
.child(
div()
.flex()
.items_center()
.gap_1()
.text_sm()
.child(
img()
.uri(avatar_uri)
.size_3p5()
.rounded_full()
.fill(theme.middle.positive.default.foreground),
)
.child(label),
)
}
}

View File

@ -0,0 +1,31 @@
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};
#[derive(Element)]
pub struct CommandPalette<V: 'static> {
view_type: PhantomData<V>,
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> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
div().child(
palette(self.scroll_state.clone())
.items(example_editor_actions())
.placeholder("Execute a command...")
.empty_string("No items found.")
.default_order(OrderMethod::Ascending),
)
}
}

View File

@ -0,0 +1,62 @@
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;
#[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,
}
}
impl<V: 'static> ProjectPanel<V> {
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.w_56()
.h_full()
.flex()
.flex_col()
.fill(theme.middle.base.default.background)
.child(
div()
.w_56()
.flex()
.flex_col()
.overflow_y_scroll(self.scroll_state.clone())
.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),
)
.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),
)
}
}

View File

@ -1,11 +1,10 @@
use std::marker::PhantomData;
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::theme::{theme, Theme};
use crate::{icon_button, text_button, tool_divider};
use gpui2::style::StyleHelpers;
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
#[derive(Default, PartialEq)]
pub enum Tool {

View File

@ -1,12 +1,12 @@
use std::marker::PhantomData;
use gpui2::elements::div;
use crate::prelude::InteractionState;
use crate::theme::theme;
use crate::{icon_button, tab};
use gpui2::elements::div::ScrollState;
use gpui2::style::StyleHelpers;
use gpui2::{Element, IntoElement, ParentElement, ViewContext};
use crate::prelude::InteractionState;
use crate::{icon_button, tab, theme};
use gpui2::{elements::div, IntoElement};
use gpui2::{Element, ParentElement, ViewContext};
#[derive(Element)]
pub struct TabBar<V: 'static> {

View File

@ -0,0 +1,80 @@
use crate::{chat_panel, collab_panel, project_panel, status_bar, tab_bar, theme, title_bar};
use gpui2::{
elements::{div, div::ScrollState},
style::StyleHelpers,
Element, IntoElement, ParentElement, ViewContext,
};
#[derive(Element, Default)]
struct WorkspaceElement {
project_panel_scroll_state: ScrollState,
collab_panel_scroll_state: ScrollState,
right_scroll_state: ScrollState,
tab_bar_scroll_state: ScrollState,
palette_scroll_state: ScrollState,
}
pub fn workspace<V: 'static>() -> impl Element<V> {
WorkspaceElement::default()
}
impl WorkspaceElement {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
// Elevation Level 0
.size_full()
.flex()
.flex_col()
.font("Zed Sans Extended")
.gap_0()
.justify_start()
.items_start()
.text_color(theme.lowest.base.default.foreground)
.fill(theme.lowest.base.default.background)
.relative()
// Elevation Level 1
.child(title_bar())
.child(
div()
.flex_1()
.w_full()
.flex()
.flex_row()
.overflow_hidden()
.child(project_panel(self.project_panel_scroll_state.clone()))
.child(collab_panel(self.collab_panel_scroll_state.clone()))
.child(
div()
.h_full()
.flex_1()
.fill(theme.highest.base.default.background)
.child(
div()
.flex()
.flex_col()
.flex_1()
.child(tab_bar(self.tab_bar_scroll_state.clone())),
),
)
.child(chat_panel(self.right_scroll_state.clone())),
)
.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())),
// )
}
}

18
crates/ui/src/tokens.rs Normal file
View File

@ -0,0 +1,18 @@
use gpui2::geometry::AbsoluteLength;
#[derive(Clone, Copy)]
pub struct Token {
pub list_indent_depth: AbsoluteLength,
}
impl Default for Token {
fn default() -> Self {
Self {
list_indent_depth: AbsoluteLength::Rems(0.5),
}
}
}
pub fn token() -> Token {
Token::default()
}