Clean out UI

This commit is contained in:
Nate Butler 2023-11-21 00:44:51 -05:00
parent b4275008f9
commit 205607a9cd
37 changed files with 530 additions and 1876 deletions

View file

@ -4,6 +4,10 @@ version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "ui2"
path = "src/ui2.rs"
[dependencies]
anyhow.workspace = true
chrono = "0.4"

View file

@ -2,56 +2,32 @@ mod avatar;
mod button;
mod checkbox;
mod context_menu;
mod details;
mod divider;
mod elevated_surface;
mod facepile;
mod icon;
mod icon_button;
mod indicator;
mod input;
mod keybinding;
mod label;
mod list;
mod modal;
mod notification_toast;
mod palette;
mod panel;
mod player;
mod player_stack;
mod slot;
mod stack;
mod tab;
mod toast;
mod stories;
mod toggle;
mod tool_divider;
mod tooltip;
pub use avatar::*;
pub use button::*;
pub use checkbox::*;
pub use context_menu::*;
pub use details::*;
pub use divider::*;
pub use elevated_surface::*;
pub use facepile::*;
pub use icon::*;
pub use icon_button::*;
pub use indicator::*;
pub use input::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
pub use modal::*;
pub use notification_toast::*;
pub use palette::*;
pub use panel::*;
pub use player::*;
pub use player_stack::*;
pub use slot::*;
pub use stack::*;
pub use tab::*;
pub use toast::*;
pub use stories::*;
pub use toggle::*;
pub use tool_divider::*;
pub use tooltip::*;

View file

@ -39,31 +39,3 @@ impl Avatar {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct AvatarStory;
impl Render for AvatarStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Avatar>(cx))
.child(Story::label(cx, "Default"))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/326587?v=4",
))
}
}
}

View file

@ -231,171 +231,3 @@ impl ButtonGroup {
Self { buttons }
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, v_stack, Story, TextColor};
use gpui::{rems, Div, Render};
use strum::IntoEnumIterator;
pub struct ButtonStory;
impl Render for ButtonStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let states = InteractionState::iter();
Story::container(cx)
.child(Story::title_for::<Button>(cx))
.child(
div()
.flex()
.gap_8()
.child(
div()
.child(Story::label(cx, "Ghost (Default)"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Filled"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
)
})))
.child(Story::label(cx, "Filled Left Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Filled Right Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Fixed With"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Left)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Right)
.width(Some(rems(6.).into())),
)
}))),
),
)
.child(Story::label(cx, "Button with `on_click`"))
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.on_click(|_, cx| println!("Button clicked.")),
)
}
}
}

View file

@ -283,63 +283,3 @@ impl Checkbox {
)
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, Story};
use gpui::{Div, Render, ViewContext};
pub struct CheckboxStory;
impl Render for CheckboxStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Checkbox>(cx))
.child(Story::label(cx, "Default"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(Checkbox::new("checkbox-enabled", Selection::Unselected))
.child(Checkbox::new(
"checkbox-intermediate",
Selection::Indeterminate,
))
.child(Checkbox::new("checkbox-selected", Selection::Selected)),
)
.child(Story::label(cx, "Disabled"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(
Checkbox::new("checkbox-disabled", Selection::Unselected)
.disabled(true),
)
.child(
Checkbox::new(
"checkbox-disabled-intermediate",
Selection::Indeterminate,
)
.disabled(true),
)
.child(
Checkbox::new("checkbox-disabled-selected", Selection::Selected)
.disabled(true),
),
)
}
}
}

View file

@ -297,116 +297,3 @@ impl<M: ManagedView> RenderOnce for MenuHandle<M> {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{story::Story, Label};
use gpui::{actions, Div, Render};
actions!(PrintCurrentDate, PrintBestFood);
fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<ContextMenu> {
ContextMenu::build(cx, |menu, _| {
menu.header(header)
.separator()
.entry(
ListItem::new("Print current time", Label::new("Print current time")),
|v, cx| {
println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone())
},
)
.entry(
ListItem::new("Print best food", Label::new("Print best food")),
|v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
)
})
}
pub struct ContextMenuStory;
impl Render for ContextMenuStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.on_action(|_: &PrintCurrentDate, _| {
println!("printing unix time!");
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
println!("Current Unix time is {:?}", unix_time.as_secs());
}
})
.on_action(|_: &PrintBestFood, _| {
println!("burrito");
})
.flex()
.flex_row()
.justify_between()
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test2")
.child(|is_open| {
Label::new(if is_open {
"TOP LEFT"
} else {
"RIGHT CLICK ME"
})
})
.menu(move |cx| build_menu(cx, "top left")),
)
.child(
menu_handle("test1")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM LEFT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft)
.menu(move |cx| build_menu(cx, "bottom left")),
),
)
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test3")
.child(|is_open| {
Label::new(if is_open {
"TOP RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "top right")),
)
.child(
menu_handle("test4")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "bottom right")),
),
)
}
}
}

View file

@ -1,83 +0,0 @@
use crate::prelude::*;
use crate::{v_stack, ButtonGroup};
#[derive(RenderOnce)]
pub struct Details {
text: &'static str,
meta: Option<&'static str>,
actions: Option<ButtonGroup>,
}
impl Component for Details {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
v_stack()
.p_1()
.gap_0p5()
.text_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
}
impl Details {
pub fn new(text: &'static str) -> Self {
Self {
text,
meta: None,
actions: None,
}
}
pub fn meta_text(mut self, meta: &'static str) -> Self {
self.meta = Some(meta);
self
}
pub fn actions(mut self, actions: ButtonGroup) -> Self {
self.actions = Some(actions);
self
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{Button, Story};
use gpui::{Div, Render};
pub struct DetailsStory;
impl Render for DetailsStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Details>(cx))
.child(Story::label(cx, "Default"))
.child(Details::new("The quick brown fox jumps over the lazy dog"))
.child(Story::label(cx, "With meta"))
.child(
Details::new("The quick brown fox jumps over the lazy dog")
.meta_text("Sphinx of black quartz, judge my vow."),
)
.child(Story::label(cx, "With meta and actions"))
.child(
Details::new("The quick brown fox jumps over the lazy dog")
.meta_text("Sphinx of black quartz, judge my vow.")
.actions(ButtonGroup::new(vec![
Button::new("Decline"),
Button::new("Accept").variant(crate::ButtonVariant::Filled),
])),
)
}
}
}

View file

@ -1,28 +0,0 @@
use gpui::Div;
use crate::{prelude::*, v_stack};
/// Create an elevated surface.
///
/// Must be used inside of a relative parent element
pub fn elevated_surface(level: ElevationIndex, cx: &mut WindowContext) -> Div {
let colors = cx.theme().colors();
// let shadow = BoxShadow {
// color: hsla(0., 0., 0., 0.1),
// offset: point(px(0.), px(1.)),
// blur_radius: px(3.),
// spread_radius: px(0.),
// };
v_stack()
.rounded_lg()
.bg(colors.elevated_surface_background)
.border()
.border_color(colors.border)
.shadow(level.shadow())
}
pub fn modal(cx: &mut WindowContext) -> Div {
elevated_surface(ElevationIndex::ModalSurface, cx)
}

View file

@ -1,33 +0,0 @@
use crate::prelude::*;
use crate::{Avatar, Player};
#[derive(RenderOnce)]
pub struct Facepile {
players: Vec<Player>,
}
impl Component for Facepile {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
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(Avatar::new(player.avatar_src().to_string()))
});
div().p_1().flex().items_center().children(player_list)
}
}
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
players: players.collect(),
}
}
}
use gpui::{Div, RenderOnce};

View file

@ -197,31 +197,3 @@ impl IconElement {
.text_color(self.color.color(cx))
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use strum::IntoEnumIterator;
use crate::Story;
use super::*;
pub struct IconStory;
impl Render for IconStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let icons = Icon::iter();
Story::container(cx)
.child(Story::title_for::<IconElement>(cx))
.child(Story::label(cx, "All Icons"))
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
}
}
}

View file

@ -1,37 +0,0 @@
use crate::prelude::*;
use gpui::{px, Div, RenderOnce};
#[derive(RenderOnce)]
pub struct UnreadIndicator;
impl Component for UnreadIndicator {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface_background)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info)
}
}
impl UnreadIndicator {
pub fn new() -> Self {
Self
}
fn render(self, cx: &mut WindowContext) -> impl Element {
div()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface_background)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info)
}
}

View file

@ -106,26 +106,3 @@ impl Input {
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct InputStory;
impl Render for InputStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Input>(cx))
.child(Story::label(cx, "Default"))
.child(div().flex().child(Input::new("Search")))
}
}
}

View file

@ -69,70 +69,3 @@ impl Key {
Self { key: key.into() }
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
pub use crate::KeyBinding;
use crate::Story;
use gpui::{actions, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render for KeybindingStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations =
["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
.child(Story::title_for::<KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
.child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
.child(KeyBinding::new(binding("ctrl-c")))
.child(KeyBinding::new(binding("alt-c")))
.child(KeyBinding::new(binding("cmd-c")))
.child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
div().flex().flex_col().children(
all_modifier_permutations
.chunks(4)
.into_iter()
.map(|chunk| {
div()
.flex()
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
.child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
.child(KeyBinding::new(binding("ctrl-a shift-z")))
.child(KeyBinding::new(binding("fn-s")))
}
}
}

View file

@ -230,35 +230,3 @@ struct Run {
pub text: String,
pub color: Hsla,
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct LabelStory;
impl Render for LabelStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Label>(cx))
.child(Story::label(cx, "Default"))
.child(Label::new("Hello, world!"))
.child(Story::label(cx, "Highlighted"))
.child(HighlightedLabel::new(
"Hello, world!",
vec![0, 1, 2, 7, 8, 12],
))
.child(HighlightedLabel::new(
"Héllo, world!",
vec![0, 1, 3, 8, 9, 13],
))
}
}
}

View file

@ -1,85 +0,0 @@
use gpui::{AnyElement, Div, RenderOnce};
use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
#[derive(RenderOnce)]
pub struct Modal {
id: ElementId,
title: Option<SharedString>,
primary_action: Option<Button>,
secondary_action: Option<Button>,
children: SmallVec<[AnyElement; 2]>,
}
impl Component for Modal {
type Rendered = gpui::Stateful<Div>;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
v_stack()
.id(self.id.clone())
.w_96()
// .rounded_xl()
.bg(cx.theme().colors().background)
.border()
.border_color(cx.theme().colors().border)
.shadow_2xl()
.child(
h_stack()
.justify_between()
.p_1()
.border_b()
.border_color(cx.theme().colors().border)
.child(div().children(self.title.clone().map(|t| Label::new(t))))
.child(IconButton::new("close", Icon::Close)),
)
.child(v_stack().p_1().children(self.children))
.when(
self.primary_action.is_some() || self.secondary_action.is_some(),
|this| {
this.child(
h_stack()
.border_t()
.border_color(cx.theme().colors().border)
.p_1()
.justify_end()
.children(self.secondary_action)
.children(self.primary_action),
)
},
)
}
}
impl Modal {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
pub fn primary_action(mut self, action: Button) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button) -> Self {
self.secondary_action = Some(action);
self
}
}
impl ParentElement for Modal {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}

View file

@ -1,40 +0,0 @@
use gpui::rems;
use crate::prelude::*;
use crate::{h_stack, Icon};
// #[derive(RenderOnce)]
pub struct NotificationToast {
label: SharedString,
icon: Option<Icon>,
}
impl NotificationToast {
pub fn new(label: SharedString) -> Self {
Self { label, icon: None }
}
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Option<Icon>>,
{
self.icon = icon.into();
self
}
fn render(self, cx: &mut WindowContext) -> impl Element {
h_stack()
.z_index(5)
.absolute()
.top_1()
.right_1()
.w(rems(9999.))
.max_w_56()
.py_1()
.px_1p5()
.rounded_lg()
.shadow_md()
.bg(cx.theme().colors().elevated_surface_background)
.child(div().size_full().child(self.label.clone()))
}
}

View file

@ -1,212 +0,0 @@
use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
use gpui::prelude::*;
use gpui::Div;
#[derive(RenderOnce)]
pub struct Palette {
id: ElementId,
input_placeholder: SharedString,
empty_string: SharedString,
items: Vec<PaletteItem>,
default_order: OrderMethod,
}
impl Component for Palette {
type Rendered = gpui::Stateful<Div>;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
v_stack()
.id(self.id)
.w_96()
.rounded_lg()
.bg(cx.theme().colors().elevated_surface_background)
.border()
.border_color(cx.theme().colors().border)
.child(
v_stack()
.gap_px()
.child(v_stack().py_0p5().px_1().child(
div().px_2().py_0p5().child(
Label::new(self.input_placeholder).color(TextColor::Placeholder),
),
))
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().colors().element_background),
)
.child(
v_stack()
.id("items")
.py_0p5()
.px_1()
.grow()
.max_h_96()
.overflow_y_scroll()
.children(
vec![if self.items.is_empty() {
Some(h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string).color(TextColor::Muted),
))
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.into_iter().enumerate().map(|(index, item)| {
h_stack()
.id(index)
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.active(|style| {
style.bg(cx.theme().colors().ghost_element_active)
})
.child(item)
})),
),
)
}
}
impl Palette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
input_placeholder: "Find something...".into(),
empty_string: "No items found.".into(),
items: vec![],
default_order: OrderMethod::default(),
}
}
pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
self.items = items;
self
}
pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
self.input_placeholder = input_placeholder.into();
self
}
pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
self.empty_string = empty_string.into();
self
}
// TODO: Hook up sort order
pub fn default_order(mut self, default_order: OrderMethod) -> Self {
self.default_order = default_order;
self
}
}
#[derive(RenderOnce)]
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
pub key_binding: Option<KeyBinding>,
}
impl Component for PaletteItem {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div()
.flex()
.flex_row()
.grow()
.justify_between()
.child(
v_stack()
.child(Label::new(self.label))
.children(self.sublabel.map(|sublabel| Label::new(sublabel))),
)
.children(self.key_binding)
}
}
impl PaletteItem {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
sublabel: None,
key_binding: None,
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = label.into();
self
}
pub fn sublabel(mut self, sublabel: impl Into<Option<SharedString>>) -> Self {
self.sublabel = sublabel.into();
self
}
pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
self.key_binding = key_binding.into();
self
}
}
use gpui::ElementId;
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use crate::{binding, Story};
use super::*;
pub struct PaletteStory;
impl Render for PaletteStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
{
Story::container(cx)
.child(Story::title_for::<Palette>(cx))
.child(Story::label(cx, "Default"))
.child(Palette::new("palette-1"))
.child(Story::label(cx, "With Items"))
.child(
Palette::new("palette-2")
.placeholder("Execute a command...")
.items(vec![
PaletteItem::new("theme selector: toggle")
.key_binding(KeyBinding::new(binding("cmd-k cmd-t"))),
PaletteItem::new("assistant: inline assist")
.key_binding(KeyBinding::new(binding("cmd-enter"))),
PaletteItem::new("assistant: quote selection")
.key_binding(KeyBinding::new(binding("cmd-<"))),
PaletteItem::new("assistant: toggle focus")
.key_binding(KeyBinding::new(binding("cmd-?"))),
PaletteItem::new("auto update: check"),
PaletteItem::new("auto update: view release notes"),
PaletteItem::new("branches: open recent")
.key_binding(KeyBinding::new(binding("cmd-alt-b"))),
PaletteItem::new("chat panel: toggle focus"),
PaletteItem::new("cli: install"),
PaletteItem::new("client: sign in"),
PaletteItem::new("client: sign out"),
PaletteItem::new("editor: cancel")
.key_binding(KeyBinding::new(binding("escape"))),
]),
)
}
}
}
}

View file

@ -1,152 +0,0 @@
use gpui::px;
use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce};
use smallvec::SmallVec;
use crate::prelude::*;
use crate::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(RenderOnce)]
pub struct Panel {
id: ElementId,
current_side: PanelSide,
/// Defaults to PanelAllowedSides::LeftAndRight
allowed_sides: PanelAllowedSides,
initial_width: AbsoluteLength,
width: Option<AbsoluteLength>,
children: SmallVec<[AnyElement; 2]>,
}
impl Component for Panel {
type Rendered = gpui::Stateful<Div>;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let current_size = self.width.unwrap_or(self.initial_width);
v_stack()
.id(self.id.clone())
.flex_initial()
.map(|this| match self.current_side {
PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
PanelSide::Bottom => this,
})
.map(|this| match self.current_side {
PanelSide::Left => this.border_r(),
PanelSide::Right => this.border_l(),
PanelSide::Bottom => this.border_b().w_full().h(current_size),
})
.bg(cx.theme().colors().surface_background)
.border_color(cx.theme().colors().border)
.children(self.children)
}
}
impl Panel {
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
Self {
id: id.into(),
current_side: PanelSide::default(),
allowed_sides: PanelAllowedSides::default(),
initial_width: px(320.).into(),
width: None,
children: SmallVec::new(),
}
}
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
}
}
impl ParentElement for Panel {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{Label, Story};
use gpui::{Div, InteractiveElement, Render};
pub struct PanelStory;
impl Render for PanelStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Panel>(cx))
.child(Story::label(cx, "Default"))
.child(
Panel::new("panel", cx).child(
div()
.id("panel-contents")
.overflow_y_scroll()
.children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))),
),
)
}
}
}

View file

@ -1,174 +0,0 @@
use gpui::Hsla;
use crate::prelude::*;
/// Represents a person with a Zed account's public profile.
/// All data in this struct should be considered public.
pub struct PublicPlayer {
pub username: SharedString,
pub avatar: SharedString,
pub is_contact: bool,
}
impl PublicPlayer {
pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
Self {
username: username.into(),
avatar: avatar.into(),
is_contact: false,
}
}
}
#[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,
}
impl MicStatus {
pub fn inverse(&self) -> Self {
match self {
Self::Muted => Self::Unmuted,
Self::Unmuted => Self::Muted,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum VideoStatus {
On,
#[default]
Off,
}
impl VideoStatus {
pub fn inverse(&self) -> Self {
match self {
Self::On => Self::Off,
Self::Off => Self::On,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum ScreenShareStatus {
Shared,
#[default]
NotShared,
}
impl ScreenShareStatus {
pub fn inverse(&self) -> Self {
match self {
Self::Shared => Self::NotShared,
Self::NotShared => Self::Shared,
}
}
}
#[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(PartialEq, Clone)]
pub struct Player {
index: usize,
avatar_src: String,
username: String,
status: PlayerStatus,
}
#[derive(Clone)]
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(&self, cx: &mut WindowContext) -> Hsla {
cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].cursor
}
pub fn selection_color(&self, cx: &mut WindowContext) -> Hsla {
cx.theme().styles.player.0[self.index % cx.theme().styles.player.0.len()].selection
}
pub fn avatar_src(&self) -> &str {
&self.avatar_src
}
pub fn index(&self) -> usize {
self.index
}
}

View file

@ -1,67 +0,0 @@
use gpui::{Div, RenderOnce};
use crate::prelude::*;
use crate::{Avatar, Facepile, PlayerWithCallStatus};
#[derive(RenderOnce)]
pub struct PlayerStack {
player_with_call_status: PlayerWithCallStatus,
}
impl Component for PlayerStack {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let player = self.player_with_call_status.get_player();
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_0p5().rounded_sm().bg(player.cursor_color(cx))),
)
.child(
div()
.flex()
.items_center()
.justify_center()
.h_6()
.pl_1()
.rounded_lg()
.bg(if followers.is_none() {
cx.theme().styles.system.transparent
} else {
player.selection_color(cx)
})
.child(Avatar::new(player.avatar_src().to_string()))
.children(followers.map(|followers| {
div().neg_ml_2().child(Facepile::new(followers.into_iter()))
})),
)
}
}
impl PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
player_with_call_status,
}
}
}

View file

@ -0,0 +1,27 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct AvatarStory;
impl Render for AvatarStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Avatar>(cx))
.child(Story::label(cx, "Default"))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/326587?v=4",
))
}
}
}

View file

@ -0,0 +1,167 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, v_stack, Story, TextColor};
use gpui::{rems, Div, Render};
use strum::IntoEnumIterator;
pub struct ButtonStory;
impl Render for ButtonStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let states = InteractionState::iter();
Story::container(cx)
.child(Story::title_for::<Button>(cx))
.child(
div()
.flex()
.gap_8()
.child(
div()
.child(Story::label(cx, "Ghost (Default)"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Ghost Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Filled"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
)
})))
.child(Story::label(cx, "Filled Left Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Left), // .state(state),
)
})))
.child(Story::label(cx, "Filled Right Button"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
.icon(Icon::Plus)
.icon_position(IconPosition::Right), // .state(state),
)
}))),
)
.child(
div()
.child(Story::label(cx, "Fixed With"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Left Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Left)
.width(Some(rems(6.).into())),
)
})))
.child(Story::label(cx, "Fixed With Right Icon"))
.child(h_stack().gap_2().children(states.clone().map(|state| {
v_stack()
.gap_1()
.child(
Label::new(state.to_string()).color(TextColor::Muted),
)
.child(
Button::new("Label")
.variant(ButtonVariant::Filled)
// .state(state)
.icon(Icon::Plus)
.icon_position(IconPosition::Right)
.width(Some(rems(6.).into())),
)
}))),
),
)
.child(Story::label(cx, "Button with `on_click`"))
.child(
Button::new("Label")
.variant(ButtonVariant::Ghost)
.on_click(|_, cx| println!("Button clicked.")),
)
}
}
}

View file

@ -0,0 +1,59 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{h_stack, Story};
use gpui::{Div, Render, ViewContext};
pub struct CheckboxStory;
impl Render for CheckboxStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Checkbox>(cx))
.child(Story::label(cx, "Default"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(Checkbox::new("checkbox-enabled", Selection::Unselected))
.child(Checkbox::new(
"checkbox-intermediate",
Selection::Indeterminate,
))
.child(Checkbox::new("checkbox-selected", Selection::Selected)),
)
.child(Story::label(cx, "Disabled"))
.child(
h_stack()
.p_2()
.gap_2()
.rounded_md()
.border()
.border_color(cx.theme().colors().border)
.child(
Checkbox::new("checkbox-disabled", Selection::Unselected)
.disabled(true),
)
.child(
Checkbox::new(
"checkbox-disabled-intermediate",
Selection::Indeterminate,
)
.disabled(true),
)
.child(
Checkbox::new("checkbox-disabled-selected", Selection::Selected)
.disabled(true),
),
)
}
}
}

View file

@ -0,0 +1,112 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{story::Story, Label};
use gpui::{actions, Div, Render};
actions!(PrintCurrentDate, PrintBestFood);
fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<ContextMenu> {
ContextMenu::build(cx, |menu, _| {
menu.header(header)
.separator()
.entry(
ListItem::new("Print current time", Label::new("Print current time")),
|v, cx| {
println!("dispatching PrintCurrentTime action");
cx.dispatch_action(PrintCurrentDate.boxed_clone())
},
)
.entry(
ListItem::new("Print best food", Label::new("Print best food")),
|v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
)
})
}
pub struct ContextMenuStory;
impl Render for ContextMenuStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.on_action(|_: &PrintCurrentDate, _| {
println!("printing unix time!");
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
println!("Current Unix time is {:?}", unix_time.as_secs());
}
})
.on_action(|_: &PrintBestFood, _| {
println!("burrito");
})
.flex()
.flex_row()
.justify_between()
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test2")
.child(|is_open| {
Label::new(if is_open {
"TOP LEFT"
} else {
"RIGHT CLICK ME"
})
})
.menu(move |cx| build_menu(cx, "top left")),
)
.child(
menu_handle("test1")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM LEFT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft)
.menu(move |cx| build_menu(cx, "bottom left")),
),
)
.child(
div()
.flex()
.flex_col()
.justify_between()
.child(
menu_handle("test3")
.child(|is_open| {
Label::new(if is_open {
"TOP RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "top right")),
)
.child(
menu_handle("test4")
.child(|is_open| {
Label::new(if is_open {
"BOTTOM RIGHT"
} else {
"RIGHT CLICK ME"
})
})
.anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight)
.menu(move |cx| build_menu(cx, "bottom right")),
),
)
}
}
}

View file

@ -0,0 +1,27 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use strum::IntoEnumIterator;
use crate::Story;
use super::*;
pub struct IconStory;
impl Render for IconStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let icons = Icon::iter();
Story::container(cx)
.child(Story::title_for::<IconElement>(cx))
.child(Story::label(cx, "All Icons"))
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
}
}
}

View file

@ -0,0 +1,22 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct InputStory;
impl Render for InputStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Input>(cx))
.child(Story::label(cx, "Default"))
.child(div().flex().child(Input::new("Search")))
}
}
}

View file

@ -0,0 +1,66 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
pub use crate::KeyBinding;
use crate::Story;
use gpui::{actions, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render for KeybindingStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations =
["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
.child(Story::title_for::<KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
.child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
.child(KeyBinding::new(binding("ctrl-c")))
.child(KeyBinding::new(binding("alt-c")))
.child(KeyBinding::new(binding("cmd-c")))
.child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
div().flex().flex_col().children(
all_modifier_permutations
.chunks(4)
.into_iter()
.map(|chunk| {
div()
.flex()
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
.child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
.child(KeyBinding::new(binding("ctrl-a shift-z")))
.child(KeyBinding::new(binding("fn-s")))
}
}
}

View file

@ -0,0 +1,31 @@
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct LabelStory;
impl Render for LabelStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Label>(cx))
.child(Story::label(cx, "Default"))
.child(Label::new("Hello, world!"))
.child(Story::label(cx, "Highlighted"))
.child(HighlightedLabel::new(
"Hello, world!",
vec![0, 1, 2, 7, 8, 12],
))
.child(HighlightedLabel::new(
"Héllo, world!",
vec![0, 1, 3, 8, 9, 13],
))
}
}
}

View file

@ -0,0 +1,8 @@
mod avatar;
mod button;
mod checkbox;
mod context_menu;
mod icon;
mod input;
mod keybinding;
mod label;

View file

@ -1,276 +0,0 @@
// use crate::prelude::*;
// use crate::{Icon, IconElement, Label, TextColor};
// use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, View};
// #[derive(RenderOnce, Clone)]
// pub struct Tab {
// id: ElementId,
// title: String,
// icon: Option<Icon>,
// current: bool,
// dirty: bool,
// fs_status: FileSystemStatus,
// git_status: GitStatus,
// diagnostic_status: DiagnosticStatus,
// close_side: IconSide,
// }
// #[derive(Clone, Debug)]
// struct TabDragState {
// title: String,
// }
// impl Render for TabDragState {
// type Element = Div;
// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
// div().w_8().h_4().bg(red())
// }
// }
// impl Component for Tab {
// type Rendered = gpui::Stateful<Div>;
// fn render(self, cx: &mut WindowContext) -> Self::Rendered {
// 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(TextColor::Hidden)
// .set_strikethrough(true),
// (GitStatus::None, false) => Label::new(self.title.clone()),
// (GitStatus::Created, false) => Label::new(self.title.clone()).color(TextColor::Created),
// (GitStatus::Modified, false) => {
// Label::new(self.title.clone()).color(TextColor::Modified)
// }
// (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(TextColor::Accent),
// (GitStatus::Conflict, false) => Label::new(self.title.clone()),
// };
// let close_icon = || IconElement::new(Icon::Close).color(TextColor::Muted);
// let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
// false => (
// cx.theme().colors().tab_inactive_background,
// cx.theme().colors().ghost_element_hover,
// cx.theme().colors().ghost_element_active,
// ),
// true => (
// cx.theme().colors().tab_active_background,
// cx.theme().colors().element_hover,
// cx.theme().colors().element_active,
// ),
// };
// let drag_state = TabDragState {
// title: self.title.clone(),
// };
// div()
// .id(self.id.clone())
// .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
// .drag_over::<TabDragState>(|d| d.bg(cx.theme().colors().drop_target_background))
// .on_drop(|_view, state: View<TabDragState>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
// .px_2()
// .py_0p5()
// .flex()
// .items_center()
// .justify_center()
// .bg(tab_bg)
// .hover(|h| h.bg(tab_hover_bg))
// .active(|a| a.bg(tab_active_bg))
// .child(
// div()
// .px_1()
// .flex()
// .items_center()
// .gap_1p5()
// .children(has_fs_conflict.then(|| {
// IconElement::new(Icon::ExclamationTriangle)
// .size(crate::IconSize::Small)
// .color(TextColor::Warning)
// }))
// .children(self.icon.map(IconElement::new))
// .children(if self.close_side == IconSide::Left {
// Some(close_icon())
// } else {
// None
// })
// .child(label)
// .children(if self.close_side == IconSide::Right {
// Some(close_icon())
// } else {
// None
// }),
// )
// }
// }
// impl Tab {
// pub fn new(id: impl Into<ElementId>) -> Self {
// Self {
// id: id.into(),
// 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
// }
// }
// #[cfg(feature = "stories")]
// pub use stories::*;
// #[cfg(feature = "stories")]
// mod stories {
// use super::*;
// use crate::{h_stack, v_stack, Icon, Story};
// use strum::IntoEnumIterator;
// pub struct TabStory;
// impl Render for TabStory {
// type Element = Div;
// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
// let git_statuses = GitStatus::iter();
// let fs_statuses = FileSystemStatus::iter();
// Story::container(cx)
// .child(Story::title_for::<_, Tab>(cx))
// .child(
// h_stack().child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "Default"))
// .child(Tab::new("default")),
// ),
// )
// .child(
// h_stack().child(
// v_stack().gap_2().child(Story::label(cx, "Current")).child(
// h_stack()
// .gap_4()
// .child(
// Tab::new("current")
// .title("Current".to_string())
// .current(true),
// )
// .child(
// Tab::new("not_current")
// .title("Not Current".to_string())
// .current(false),
// ),
// ),
// ),
// )
// .child(
// h_stack().child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "Titled"))
// .child(Tab::new("titled").title("label".to_string())),
// ),
// )
// .child(
// h_stack().child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "With Icon"))
// .child(
// Tab::new("with_icon")
// .title("label".to_string())
// .icon(Some(Icon::Envelope)),
// ),
// ),
// )
// .child(
// h_stack().child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "Close Side"))
// .child(
// h_stack()
// .gap_4()
// .child(
// Tab::new("left")
// .title("Left".to_string())
// .close_side(IconSide::Left),
// )
// .child(Tab::new("right").title("Right".to_string())),
// ),
// ),
// )
// .child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "Git Status"))
// .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
// Tab::new("git_status")
// .title(git_status.to_string())
// .git_status(git_status)
// }))),
// )
// .child(
// v_stack()
// .gap_2()
// .child(Story::label(cx, "File System Status"))
// .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
// Tab::new("file_system_status")
// .title(fs_status.to_string())
// .fs_status(fs_status)
// }))),
// )
// }
// }
// }

View file

@ -1,117 +0,0 @@
use crate::prelude::*;
use gpui::{prelude::*, AnyElement, RenderOnce};
use gpui::{Div, Element};
use smallvec::SmallVec;
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ToastOrigin {
#[default]
Bottom,
BottomRight,
}
/// Don't use toast directly:
///
/// - For messages with a required action, use a `NotificationToast`.
/// - For messages that convey information, use a `StatusToast`.
///
/// A toast is a small, temporary window that appears to show a message to the user
/// or indicate a required action.
///
/// Toasts should not persist on the screen for more than a few seconds unless
/// they are actively showing the a process in progress.
///
/// Only one toast may be visible at a time.
#[derive(RenderOnce)]
pub struct Toast {
origin: ToastOrigin,
children: SmallVec<[AnyElement; 2]>,
}
impl Component for Toast {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let mut div = div();
if self.origin == ToastOrigin::Bottom {
div = div.right_1_2();
} else {
div = div.right_2();
}
div.z_index(5)
.absolute()
.bottom_9()
.flex()
.py_1()
.px_1p5()
.rounded_lg()
.shadow_md()
.overflow_hidden()
.bg(cx.theme().colors().elevated_surface_background)
.children(self.children)
}
}
impl Toast {
pub fn new(origin: ToastOrigin) -> Self {
Self {
origin,
children: SmallVec::new(),
}
}
fn render(self, cx: &mut WindowContext) -> impl Element {
let mut div = div();
if self.origin == ToastOrigin::Bottom {
div = div.right_1_2();
} else {
div = div.right_2();
}
div.z_index(5)
.absolute()
.bottom_9()
.flex()
.py_1()
.px_1p5()
.rounded_lg()
.shadow_md()
.overflow_hidden()
.bg(cx.theme().colors().elevated_surface_background)
.children(self.children)
}
}
impl ParentElement for Toast {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use gpui::{Div, Render};
use crate::{Label, Story};
use super::*;
pub struct ToastStory;
impl Render for ToastStory {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx)
.child(Story::title_for::<Toast>(cx))
.child(Story::label(cx, "Default"))
.child(Toast::new(ToastOrigin::Bottom).child(Label::new("label")))
}
}
}

View file

@ -1,23 +0,0 @@
use crate::prelude::*;
use gpui::{Div, RenderOnce};
#[derive(RenderOnce)]
pub struct ToolDivider;
impl Component for ToolDivider {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
div().w_px().h_3().bg(cx.theme().colors().border)
}
}
impl ToolDivider {
pub fn new() -> Self {
Self
}
fn render(self, cx: &mut WindowContext) -> impl Element {
div().w_px().h_3().bg(cx.theme().colors().border)
}
}

View file

@ -5,7 +5,6 @@ pub use gpui::{
ViewContext, WindowContext,
};
pub use crate::elevation::*;
pub use crate::StyledExt;
pub use crate::{ButtonVariant, TextColor};
pub use theme2::ActiveTheme;

2
crates/ui2/src/styles.rs Normal file
View file

@ -0,0 +1,2 @@
mod elevation;
pub use elevation::*;

View file

@ -1,7 +1,7 @@
use gpui::{hsla, point, px, BoxShadow};
use smallvec::{smallvec, SmallVec};
#[doc = include_str!("elevation.md")]
#[doc = include_str!("docs/elevation.md")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Elevation {
ElevationIndex(ElevationIndex),

View file

@ -15,15 +15,15 @@
#![allow(dead_code, unused_variables)]
mod components;
mod elevation;
pub mod prelude;
mod styled_ext;
mod styles;
pub mod utils;
pub use components::*;
pub use prelude::*;
// pub use static_data::*;
pub use styled_ext::*;
pub use styles::*;
#[cfg(feature = "stories")]
mod story;