Add an initial set of GPUI2 components to the storybook (#2990)
This PR adds an initial set of components to `crates/storybook/src/ui`. All changes still are contained to inside storybook. Merging to keep up to date with main.
This commit is contained in:
commit
f7696114bb
35 changed files with 1530 additions and 499 deletions
5
assets/icons/stop_sharing.svg
Normal file
5
assets/icons/stop_sharing.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.6 KiB |
|
@ -1,12 +1,12 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use super::scene::{Path, PathVertex};
|
use super::scene::{Path, PathVertex};
|
||||||
use crate::{color::Color, json::ToJson};
|
use crate::{color::Color, json::ToJson};
|
||||||
|
use derive_more::Neg;
|
||||||
pub use pathfinder_geometry::*;
|
pub use pathfinder_geometry::*;
|
||||||
use rect::RectF;
|
use rect::RectF;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::fmt::Debug;
|
||||||
use vector::{vec2f, Vector2F};
|
use vector::{vec2f, Vector2F};
|
||||||
|
|
||||||
pub struct PathBuilder {
|
pub struct PathBuilder {
|
||||||
|
@ -194,8 +194,8 @@ where
|
||||||
impl Size<DefiniteLength> {
|
impl Size<DefiniteLength> {
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
width: pixels(0.),
|
width: pixels(0.).into(),
|
||||||
height: pixels(0.),
|
height: pixels(0.).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +235,17 @@ pub struct Edges<T: Clone + Default + Debug> {
|
||||||
pub left: T,
|
pub left: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Default + Debug> Edges<T> {
|
||||||
|
pub fn uniform(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
top: value.clone(),
|
||||||
|
right: value.clone(),
|
||||||
|
bottom: value.clone(),
|
||||||
|
left: value.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Edges<Length> {
|
impl Edges<Length> {
|
||||||
pub fn auto() -> Self {
|
pub fn auto() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -247,10 +258,10 @@ impl Edges<Length> {
|
||||||
|
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
top: pixels(0.),
|
top: pixels(0.).into(),
|
||||||
right: pixels(0.),
|
right: pixels(0.).into(),
|
||||||
bottom: pixels(0.),
|
bottom: pixels(0.).into(),
|
||||||
left: pixels(0.),
|
left: pixels(0.).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,10 +281,10 @@ impl Edges<Length> {
|
||||||
impl Edges<DefiniteLength> {
|
impl Edges<DefiniteLength> {
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
top: pixels(0.),
|
top: pixels(0.).into(),
|
||||||
right: pixels(0.),
|
right: pixels(0.).into(),
|
||||||
bottom: pixels(0.),
|
bottom: pixels(0.).into(),
|
||||||
left: pixels(0.),
|
left: pixels(0.).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +333,7 @@ impl Edges<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Neg)]
|
||||||
pub enum AbsoluteLength {
|
pub enum AbsoluteLength {
|
||||||
Pixels(f32),
|
Pixels(f32),
|
||||||
Rems(f32),
|
Rems(f32),
|
||||||
|
@ -360,7 +371,7 @@ impl Default for AbsoluteLength {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Neg)]
|
||||||
pub enum DefiniteLength {
|
pub enum DefiniteLength {
|
||||||
Absolute(AbsoluteLength),
|
Absolute(AbsoluteLength),
|
||||||
Relative(f32), // 0. to 1.
|
Relative(f32), // 0. to 1.
|
||||||
|
@ -404,7 +415,7 @@ impl Default for DefiniteLength {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Neg)]
|
||||||
pub enum Length {
|
pub enum Length {
|
||||||
Definite(DefiniteLength),
|
Definite(DefiniteLength),
|
||||||
Auto,
|
Auto,
|
||||||
|
@ -419,16 +430,16 @@ impl std::fmt::Debug for Length {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
|
pub fn relative(fraction: f32) -> DefiniteLength {
|
||||||
DefiniteLength::Relative(fraction).into()
|
DefiniteLength::Relative(fraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
|
pub fn rems(rems: f32) -> AbsoluteLength {
|
||||||
AbsoluteLength::Rems(rems).into()
|
AbsoluteLength::Rems(rems)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
|
pub fn pixels(pixels: f32) -> AbsoluteLength {
|
||||||
AbsoluteLength::Pixels(pixels).into()
|
AbsoluteLength::Pixels(pixels)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto() -> Length {
|
pub fn auto() -> Length {
|
||||||
|
|
|
@ -34,6 +34,27 @@ pub trait Element<V: 'static>: 'static + IntoElement<V> {
|
||||||
phase: ElementPhase::Init,
|
phase: ElementPhase::Init,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies a given function `then` to the current element if `condition` is true.
|
||||||
|
/// This function is used to conditionally modify the element based on a given condition.
|
||||||
|
/// If `condition` is false, it just returns the current element as it is.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `self`: The current element
|
||||||
|
/// - `condition`: The boolean condition based on which `then` is applied to the element.
|
||||||
|
/// - `then`: A function that takes in the current element and returns a possibly modified element.
|
||||||
|
///
|
||||||
|
/// # Return
|
||||||
|
/// It returns the potentially modified element.
|
||||||
|
fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if condition {
|
||||||
|
self = then(self);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
|
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
|
||||||
|
|
|
@ -73,10 +73,15 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
|
||||||
if bounds.contains_point(event.position) {
|
if bounds.contains_point(event.position) {
|
||||||
pressed.set(true);
|
pressed.set(true);
|
||||||
cx.repaint();
|
cx.repaint();
|
||||||
|
} else {
|
||||||
|
cx.bubble_event();
|
||||||
}
|
}
|
||||||
} else if pressed.get() {
|
} else {
|
||||||
pressed.set(false);
|
if pressed.get() {
|
||||||
cx.repaint();
|
pressed.set(false);
|
||||||
|
cx.repaint();
|
||||||
|
}
|
||||||
|
cx.bubble_event();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,8 @@ pub trait Styleable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate as gpui2;
|
||||||
|
|
||||||
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
|
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
|
@ -322,33 +324,12 @@ pub trait Styleable {
|
||||||
pub trait StyleHelpers: Styleable<Style = Style> {
|
pub trait StyleHelpers: Styleable<Style = Style> {
|
||||||
styleable_helpers!();
|
styleable_helpers!();
|
||||||
|
|
||||||
fn h(mut self, height: Length) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.declared_style().size.height = Some(height);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// size_{n}: Sets width & height to {n}
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// size_1: Sets width & height to 1
|
|
||||||
fn size(mut self, size: Length) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.declared_style().size.height = Some(size);
|
|
||||||
self.declared_style().size.width = Some(size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full(mut self) -> Self
|
fn full(mut self) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.declared_style().size.width = Some(relative(1.));
|
self.declared_style().size.width = Some(relative(1.).into());
|
||||||
self.declared_style().size.height = Some(relative(1.));
|
self.declared_style().size.height = Some(relative(1.).into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,7 +387,7 @@ pub trait StyleHelpers: Styleable<Style = Style> {
|
||||||
{
|
{
|
||||||
self.declared_style().flex_grow = Some(1.);
|
self.declared_style().flex_grow = Some(1.);
|
||||||
self.declared_style().flex_shrink = Some(1.);
|
self.declared_style().flex_shrink = Some(1.);
|
||||||
self.declared_style().flex_basis = Some(relative(0.));
|
self.declared_style().flex_basis = Some(relative(0.).into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,49 +28,100 @@ fn generate_methods() -> Vec<TokenStream2> {
|
||||||
let mut methods = Vec::new();
|
let mut methods = Vec::new();
|
||||||
|
|
||||||
for (prefix, auto_allowed, fields) in box_prefixes() {
|
for (prefix, auto_allowed, fields) in box_prefixes() {
|
||||||
|
methods.push(generate_custom_value_setter(
|
||||||
|
prefix,
|
||||||
|
if auto_allowed {
|
||||||
|
quote! { Length }
|
||||||
|
} else {
|
||||||
|
quote! { DefiniteLength }
|
||||||
|
},
|
||||||
|
&fields,
|
||||||
|
));
|
||||||
|
|
||||||
for (suffix, length_tokens, doc_string) in box_suffixes() {
|
for (suffix, length_tokens, doc_string) in box_suffixes() {
|
||||||
if auto_allowed || suffix != "auto" {
|
if suffix != "auto" || auto_allowed {
|
||||||
let method = generate_method(prefix, suffix, &fields, length_tokens, doc_string);
|
methods.push(generate_predefined_setter(
|
||||||
methods.push(method);
|
prefix,
|
||||||
|
suffix,
|
||||||
|
&fields,
|
||||||
|
&length_tokens,
|
||||||
|
false,
|
||||||
|
doc_string,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if suffix != "auto" {
|
||||||
|
methods.push(generate_predefined_setter(
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
|
&fields,
|
||||||
|
&length_tokens,
|
||||||
|
true,
|
||||||
|
doc_string,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (prefix, fields) in corner_prefixes() {
|
for (prefix, fields) in corner_prefixes() {
|
||||||
|
methods.push(generate_custom_value_setter(
|
||||||
|
prefix,
|
||||||
|
quote! { AbsoluteLength },
|
||||||
|
&fields,
|
||||||
|
));
|
||||||
|
|
||||||
for (suffix, radius_tokens, doc_string) in corner_suffixes() {
|
for (suffix, radius_tokens, doc_string) in corner_suffixes() {
|
||||||
let method = generate_method(prefix, suffix, &fields, radius_tokens, doc_string);
|
methods.push(generate_predefined_setter(
|
||||||
methods.push(method);
|
prefix,
|
||||||
|
suffix,
|
||||||
|
&fields,
|
||||||
|
&radius_tokens,
|
||||||
|
false,
|
||||||
|
doc_string,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (prefix, fields) in border_prefixes() {
|
for (prefix, fields) in border_prefixes() {
|
||||||
for (suffix, width_tokens, doc_string) in border_suffixes() {
|
for (suffix, width_tokens, doc_string) in border_suffixes() {
|
||||||
let method = generate_method(prefix, suffix, &fields, width_tokens, doc_string);
|
methods.push(generate_predefined_setter(
|
||||||
methods.push(method);
|
prefix,
|
||||||
|
suffix,
|
||||||
|
&fields,
|
||||||
|
&width_tokens,
|
||||||
|
false,
|
||||||
|
doc_string,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
methods
|
methods
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_method(
|
fn generate_predefined_setter(
|
||||||
prefix: &'static str,
|
name: &'static str,
|
||||||
suffix: &'static str,
|
length: &'static str,
|
||||||
fields: &Vec<TokenStream2>,
|
fields: &Vec<TokenStream2>,
|
||||||
length_tokens: TokenStream2,
|
length_tokens: &TokenStream2,
|
||||||
|
negate: bool,
|
||||||
doc_string: &'static str,
|
doc_string: &'static str,
|
||||||
) -> TokenStream2 {
|
) -> TokenStream2 {
|
||||||
let method_name = if suffix.is_empty() {
|
let (negation_prefix, negation_token) = if negate {
|
||||||
format_ident!("{}", prefix)
|
("neg_", quote! { - })
|
||||||
} else {
|
} else {
|
||||||
format_ident!("{}_{}", prefix, suffix)
|
("", quote! {})
|
||||||
|
};
|
||||||
|
|
||||||
|
let method_name = if length.is_empty() {
|
||||||
|
format_ident!("{}{}", negation_prefix, name)
|
||||||
|
} else {
|
||||||
|
format_ident!("{}{}_{}", negation_prefix, name, length)
|
||||||
};
|
};
|
||||||
|
|
||||||
let field_assignments = fields
|
let field_assignments = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field_tokens| {
|
.map(|field_tokens| {
|
||||||
quote! {
|
quote! {
|
||||||
style.#field_tokens = Some(gpui::geometry::#length_tokens);
|
style.#field_tokens = Some((#negation_token gpui2::geometry::#length_tokens).into());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -84,6 +135,41 @@ fn generate_method(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if negate {
|
||||||
|
dbg!(method.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
method
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_custom_value_setter(
|
||||||
|
prefix: &'static str,
|
||||||
|
length_type: TokenStream2,
|
||||||
|
fields: &Vec<TokenStream2>,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let method_name = format_ident!("{}", prefix);
|
||||||
|
|
||||||
|
let mut iter = fields.into_iter();
|
||||||
|
let last = iter.next_back().unwrap();
|
||||||
|
let field_assignments = iter
|
||||||
|
.map(|field_tokens| {
|
||||||
|
quote! {
|
||||||
|
style.#field_tokens = Some(length.clone().into());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.chain(std::iter::once(quote! {
|
||||||
|
style.#last = Some(length.into());
|
||||||
|
}))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let method = quote! {
|
||||||
|
fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui2::geometry::#length_type>) -> Self where Self: std::marker::Sized {
|
||||||
|
let mut style = self.declared_style();
|
||||||
|
#(#field_assignments)*
|
||||||
|
self
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
method
|
method
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,10 +182,10 @@ fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
|
||||||
true,
|
true,
|
||||||
vec![quote! {size.width}, quote! {size.height}],
|
vec![quote! {size.width}, quote! {size.height}],
|
||||||
),
|
),
|
||||||
("min_w", false, vec![quote! { min_size.width }]),
|
("min_w", true, vec![quote! { min_size.width }]),
|
||||||
("min_h", false, vec![quote! { min_size.height }]),
|
("min_h", true, vec![quote! { min_size.height }]),
|
||||||
("max_w", false, vec![quote! { max_size.width }]),
|
("max_w", true, vec![quote! { max_size.width }]),
|
||||||
("max_h", false, vec![quote! { max_size.height }]),
|
("max_h", true, vec![quote! { max_size.height }]),
|
||||||
(
|
(
|
||||||
"m",
|
"m",
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -4,12 +4,6 @@ use gpui2::{
|
||||||
};
|
};
|
||||||
use std::{marker::PhantomData, rc::Rc};
|
use std::{marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
mod icon_button;
|
|
||||||
mod tab;
|
|
||||||
|
|
||||||
pub(crate) use icon_button::{icon_button, ButtonVariant};
|
|
||||||
pub(crate) use tab::tab;
|
|
||||||
|
|
||||||
struct ButtonHandlers<V, D> {
|
struct ButtonHandlers<V, D> {
|
||||||
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod tab_bar;
|
|
||||||
|
|
||||||
pub(crate) use tab_bar::tab_bar;
|
|
55
crates/storybook/src/prelude.rs
Normal file
55
crates/storybook/src/prelude.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#[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]
|
||||||
|
Circle,
|
||||||
|
RoundedRectangle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone, Copy)]
|
||||||
|
pub enum InteractionState {
|
||||||
|
#[default]
|
||||||
|
Enabled,
|
||||||
|
Hovered,
|
||||||
|
Active,
|
||||||
|
Focused,
|
||||||
|
Dragged,
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteractionState {
|
||||||
|
pub fn if_enabled(&self, enabled: bool) -> Self {
|
||||||
|
if enabled {
|
||||||
|
*self
|
||||||
|
} else {
|
||||||
|
InteractionState::Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq)]
|
||||||
|
pub enum SelectedState {
|
||||||
|
#[default]
|
||||||
|
Unselected,
|
||||||
|
PartiallySelected,
|
||||||
|
Selected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ToggleState {
|
||||||
|
Toggled,
|
||||||
|
NotToggled,
|
||||||
|
}
|
|
@ -12,8 +12,9 @@ use simplelog::SimpleLogger;
|
||||||
mod collab_panel;
|
mod collab_panel;
|
||||||
mod components;
|
mod components;
|
||||||
mod element_ext;
|
mod element_ext;
|
||||||
mod modules;
|
mod prelude;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
mod ui;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
gpui2::actions! {
|
gpui2::actions! {
|
||||||
|
|
23
crates/storybook/src/ui.rs
Normal file
23
crates/storybook/src/ui.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
mod element;
|
||||||
|
pub use element::avatar::*;
|
||||||
|
pub use element::details::*;
|
||||||
|
pub use element::icon::*;
|
||||||
|
pub use element::icon_button::*;
|
||||||
|
pub use element::indicator::*;
|
||||||
|
pub use element::input::*;
|
||||||
|
pub use element::label::*;
|
||||||
|
pub use element::text_button::*;
|
||||||
|
pub use element::tool_divider::*;
|
||||||
|
|
||||||
|
mod component;
|
||||||
|
pub use component::facepile::*;
|
||||||
|
pub use component::follow_group::*;
|
||||||
|
pub use component::list_item::*;
|
||||||
|
pub use component::tab::*;
|
||||||
|
|
||||||
|
mod module;
|
||||||
|
pub use module::chat_panel::*;
|
||||||
|
pub use module::project_panel::*;
|
||||||
|
pub use module::status_bar::*;
|
||||||
|
pub use module::tab_bar::*;
|
||||||
|
pub use module::title_bar::*;
|
4
crates/storybook/src/ui/component.rs
Normal file
4
crates/storybook/src/ui/component.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub(crate) mod facepile;
|
||||||
|
pub(crate) mod follow_group;
|
||||||
|
pub(crate) mod list_item;
|
||||||
|
pub(crate) mod tab;
|
27
crates/storybook/src/ui/component/facepile.rs
Normal file
27
crates/storybook/src/ui/component/facepile.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::{theme::theme, ui::Avatar};
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Facepile {
|
||||||
|
players: Vec<Avatar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn facepile(players: Vec<Avatar>) -> Facepile {
|
||||||
|
Facepile { players }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Facepile {
|
||||||
|
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())
|
||||||
|
});
|
||||||
|
div().p_1().flex().items_center().children(player_list)
|
||||||
|
}
|
||||||
|
}
|
52
crates/storybook/src/ui/component/follow_group.rs
Normal file
52
crates/storybook/src/ui/component/follow_group.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::ui::{facepile, indicator, Avatar};
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[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())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
88
crates/storybook/src/ui/component/list_item.rs
Normal file
88
crates/storybook/src/ui/component/list_item.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::prelude::{InteractionState, ToggleState};
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::ui::{icon, IconAsset, Label};
|
||||||
|
use gpui2::geometry::rems;
|
||||||
|
use gpui2::style::{StyleHelpers, Styleable};
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct ListItem {
|
||||||
|
label: Label,
|
||||||
|
left_icon: Option<IconAsset>,
|
||||||
|
indent_level: u32,
|
||||||
|
state: InteractionState,
|
||||||
|
toggle: Option<ToggleState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_item(label: Label) -> ListItem {
|
||||||
|
ListItem {
|
||||||
|
label,
|
||||||
|
indent_level: 0,
|
||||||
|
left_icon: None,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.fill(theme.middle.base.default.background)
|
||||||
|
.hover()
|
||||||
|
.fill(theme.middle.base.hovered.background)
|
||||||
|
.active()
|
||||||
|
.fill(theme.middle.base.pressed.background)
|
||||||
|
.relative()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h_7()
|
||||||
|
.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_px()
|
||||||
|
.h_full()
|
||||||
|
.fill(theme.middle.base.default.border)
|
||||||
|
.hover()
|
||||||
|
.fill(theme.middle.warning.default.border)
|
||||||
|
.active()
|
||||||
|
.fill(theme.middle.negative.default.border),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
.items_center()
|
||||||
|
.children(match self.toggle {
|
||||||
|
Some(ToggleState::NotToggled) => Some(icon(IconAsset::ChevronRight)),
|
||||||
|
Some(ToggleState::Toggled) => Some(icon(IconAsset::ChevronDown)),
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
.children(self.left_icon.map(|i| icon(i)))
|
||||||
|
.child(self.label.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use gpui2::{elements::div, IntoElement};
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub(crate) struct Tab {
|
pub struct Tab {
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
9
crates/storybook/src/ui/element.rs
Normal file
9
crates/storybook/src/ui/element.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub(crate) mod avatar;
|
||||||
|
pub(crate) mod details;
|
||||||
|
pub(crate) mod icon;
|
||||||
|
pub(crate) mod icon_button;
|
||||||
|
pub(crate) mod indicator;
|
||||||
|
pub(crate) mod input;
|
||||||
|
pub(crate) mod label;
|
||||||
|
pub(crate) mod text_button;
|
||||||
|
pub(crate) mod tool_divider;
|
42
crates/storybook/src/ui/element/avatar.rs
Normal file
42
crates/storybook/src/ui/element/avatar.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::prelude::Shape;
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::elements::img;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{ArcCow, IntoElement};
|
||||||
|
use gpui2::{Element, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct Avatar {
|
||||||
|
src: ArcCow<'static, str>,
|
||||||
|
shape: Shape,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avatar(src: impl Into<ArcCow<'static, str>>) -> Avatar {
|
||||||
|
Avatar {
|
||||||
|
src: src.into(),
|
||||||
|
shape: Shape::Circle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Avatar {
|
||||||
|
pub fn shape(mut self, shape: Shape) -> Self {
|
||||||
|
self.shape = shape;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let mut img = img();
|
||||||
|
|
||||||
|
if self.shape == Shape::Circle {
|
||||||
|
img = img.rounded_full();
|
||||||
|
} else {
|
||||||
|
img = img.rounded_md();
|
||||||
|
}
|
||||||
|
|
||||||
|
img.uri(self.src.clone())
|
||||||
|
.size_4()
|
||||||
|
.fill(theme.middle.warning.default.foreground)
|
||||||
|
}
|
||||||
|
}
|
36
crates/storybook/src/ui/element/details.rs
Normal file
36
crates/storybook/src/ui/element/details.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::elements::div;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{Element, ViewContext};
|
||||||
|
use gpui2::{IntoElement, ParentElement};
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct Details {
|
||||||
|
text: &'static str,
|
||||||
|
meta: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn details(text: &'static str) -> Details {
|
||||||
|
Details { text, meta: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Details {
|
||||||
|
pub fn meta_text(mut self, meta: &'static str) -> Self {
|
||||||
|
self.meta = Some(meta);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
// .flex()
|
||||||
|
// .w_full()
|
||||||
|
.p_1()
|
||||||
|
.gap_0p5()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
|
.child(self.text.clone())
|
||||||
|
.children(self.meta.map(|m| m))
|
||||||
|
}
|
||||||
|
}
|
73
crates/storybook/src/ui/element/icon.rs
Normal file
73
crates/storybook/src/ui/element/icon.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::elements::svg;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::IntoElement;
|
||||||
|
use gpui2::{Element, ViewContext};
|
||||||
|
|
||||||
|
// Icon::Hash
|
||||||
|
// icon(IconAsset::Hash).color(IconColor::Warning)
|
||||||
|
// Icon::new(IconAsset::Hash).color(IconColor::Warning)
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
|
pub enum IconAsset {
|
||||||
|
Ai,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
#[default]
|
||||||
|
ArrowUpRight,
|
||||||
|
Bolt,
|
||||||
|
Hash,
|
||||||
|
File,
|
||||||
|
Folder,
|
||||||
|
FolderOpen,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IconAsset {
|
||||||
|
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::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::File => "icons/file_icons/file.svg",
|
||||||
|
IconAsset::Folder => "icons/file_icons/folder.svg",
|
||||||
|
IconAsset::FolderOpen => "icons/file_icons/folder_open.svg",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct Icon {
|
||||||
|
asset: IconAsset,
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
.path(self.asset.path())
|
||||||
|
.size_4()
|
||||||
|
.fill(theme.lowest.base.default.foreground)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::prelude::{ButtonVariant, InteractionState};
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use gpui2::elements::svg;
|
use gpui2::elements::svg;
|
||||||
use gpui2::style::{StyleHelpers, Styleable};
|
use gpui2::style::{StyleHelpers, Styleable};
|
||||||
|
@ -5,25 +6,42 @@ use gpui2::{elements::div, IntoElement};
|
||||||
use gpui2::{Element, ParentElement, ViewContext};
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub(crate) struct IconButton {
|
pub struct IconButton {
|
||||||
path: &'static str,
|
path: &'static str,
|
||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
|
state: InteractionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
pub fn icon_button(path: &'static str) -> IconButton {
|
||||||
pub enum ButtonVariant {
|
IconButton {
|
||||||
Ghost,
|
path,
|
||||||
Filled,
|
variant: ButtonVariant::default(),
|
||||||
}
|
state: InteractionState::default(),
|
||||||
|
}
|
||||||
pub fn icon_button<V: 'static>(path: &'static str, variant: ButtonVariant) -> impl Element<V> {
|
|
||||||
IconButton { path, variant }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconButton {
|
impl IconButton {
|
||||||
|
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> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let icon_color;
|
||||||
|
|
||||||
|
if self.state == InteractionState::Disabled {
|
||||||
|
icon_color = theme.highest.base.disabled.foreground;
|
||||||
|
} else {
|
||||||
|
icon_color = theme.highest.base.default.foreground;
|
||||||
|
}
|
||||||
|
|
||||||
let mut div = div();
|
let mut div = div();
|
||||||
if self.variant == ButtonVariant::Filled {
|
if self.variant == ButtonVariant::Filled {
|
||||||
div = div.fill(theme.highest.on.default.background);
|
div = div.fill(theme.highest.on.default.background);
|
||||||
|
@ -39,12 +57,6 @@ impl IconButton {
|
||||||
.fill(theme.highest.base.hovered.background)
|
.fill(theme.highest.base.hovered.background)
|
||||||
.active()
|
.active()
|
||||||
.fill(theme.highest.base.pressed.background)
|
.fill(theme.highest.base.pressed.background)
|
||||||
.child(
|
.child(svg().path(self.path).w_4().h_4().fill(icon_color))
|
||||||
svg()
|
|
||||||
.path(self.path)
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.highest.variant.default.foreground),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
32
crates/storybook/src/ui/element/indicator.rs
Normal file
32
crates/storybook/src/ui/element/indicator.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ViewContext};
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
99
crates/storybook/src/ui/element/input.rs
Normal file
99
crates/storybook/src/ui/element/input.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use crate::prelude::{InputVariant, InteractionState};
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::style::{StyleHelpers, Styleable};
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct Input {
|
||||||
|
placeholder: &'static str,
|
||||||
|
value: String,
|
||||||
|
state: InteractionState,
|
||||||
|
variant: InputVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input(placeholder: &'static str) -> Input {
|
||||||
|
Input {
|
||||||
|
placeholder,
|
||||||
|
value: "".to_string(),
|
||||||
|
state: InteractionState::default(),
|
||||||
|
variant: InputVariant::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let text_el;
|
||||||
|
let text_color;
|
||||||
|
let background_color_default;
|
||||||
|
let background_color_active;
|
||||||
|
|
||||||
|
let mut border_color_default = theme.middle.base.default.border;
|
||||||
|
let mut border_color_hover = theme.middle.base.hovered.border;
|
||||||
|
let mut border_color_active = theme.middle.base.pressed.border;
|
||||||
|
let border_color_focus = theme.middle.base.pressed.background;
|
||||||
|
|
||||||
|
match self.variant {
|
||||||
|
InputVariant::Ghost => {
|
||||||
|
background_color_default = theme.middle.base.default.background;
|
||||||
|
background_color_active = theme.middle.base.active.background;
|
||||||
|
}
|
||||||
|
InputVariant::Filled => {
|
||||||
|
background_color_default = theme.middle.on.default.background;
|
||||||
|
background_color_active = theme.middle.on.active.background;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.state == InteractionState::Focused {
|
||||||
|
border_color_default = theme.players[0].cursor;
|
||||||
|
border_color_hover = theme.players[0].cursor;
|
||||||
|
border_color_active = theme.players[0].cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.state == InteractionState::Focused || self.state == InteractionState::Active {
|
||||||
|
text_el = self.value.clone();
|
||||||
|
text_color = theme.lowest.base.default.foreground;
|
||||||
|
} else {
|
||||||
|
text_el = self.placeholder.to_string().clone();
|
||||||
|
text_color = theme.lowest.base.disabled.foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
div()
|
||||||
|
.h_7()
|
||||||
|
.px_2()
|
||||||
|
.border()
|
||||||
|
.border_color(border_color_default)
|
||||||
|
.fill(background_color_default)
|
||||||
|
.hover()
|
||||||
|
.border_color(border_color_hover)
|
||||||
|
.active()
|
||||||
|
.border_color(border_color_active)
|
||||||
|
.fill(background_color_active)
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(text_color)
|
||||||
|
.child(text_el)
|
||||||
|
.child(div().text_color(theme.players[0].cursor).child("|")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
49
crates/storybook/src/ui/element/label.rs
Normal file
49
crates/storybook/src/ui/element/label.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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 {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Created,
|
||||||
|
Modified,
|
||||||
|
Deleted,
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element, Clone)]
|
||||||
|
pub struct Label {
|
||||||
|
label: &'static str,
|
||||||
|
color: LabelColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(label: &'static str) -> Label {
|
||||||
|
Label {
|
||||||
|
label,
|
||||||
|
color: LabelColor::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
pub fn color(mut self, color: LabelColor) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
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::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,
|
||||||
|
};
|
||||||
|
|
||||||
|
div().text_sm().text_color(color).child(self.label.clone())
|
||||||
|
}
|
||||||
|
}
|
81
crates/storybook/src/ui/element/text_button.rs
Normal file
81
crates/storybook/src/ui/element/text_button.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use crate::prelude::{ButtonVariant, InteractionState};
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::style::{StyleHelpers, Styleable};
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[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())
|
||||||
|
}
|
||||||
|
}
|
19
crates/storybook/src/ui/element/tool_divider.rs
Normal file
19
crates/storybook/src/ui/element/tool_divider.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use crate::theme::theme;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct ToolDivider {}
|
||||||
|
|
||||||
|
pub fn tool_divider<V: 'static>() -> impl Element<V> {
|
||||||
|
ToolDivider {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolDivider {
|
||||||
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div().w_px().h_3().fill(theme.lowest.base.default.border)
|
||||||
|
}
|
||||||
|
}
|
5
crates/storybook/src/ui/module.rs
Normal file
5
crates/storybook/src/ui/module.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub(crate) mod chat_panel;
|
||||||
|
pub(crate) mod project_panel;
|
||||||
|
pub(crate) mod status_bar;
|
||||||
|
pub(crate) mod tab_bar;
|
||||||
|
pub(crate) mod title_bar;
|
65
crates/storybook/src/ui/module/chat_panel.rs
Normal file
65
crates/storybook/src/ui/module/chat_panel.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::ui::icon_button;
|
||||||
|
use gpui2::elements::div::ScrollState;
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct ChatPanel<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
scroll_state: ScrollState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chat_panel<V: 'static>(scroll_state: ScrollState) -> ChatPanel<V> {
|
||||||
|
ChatPanel {
|
||||||
|
view_type: PhantomData,
|
||||||
|
scroll_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> ChatPanel<V> {
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.flex()
|
||||||
|
// Header
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
// Nav Buttons
|
||||||
|
.child("#gpui2"),
|
||||||
|
)
|
||||||
|
// Chat Body
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
|
.child("body"),
|
||||||
|
)
|
||||||
|
// Composer
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.flex()
|
||||||
|
.gap_2()
|
||||||
|
// Nav Buttons
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_px()
|
||||||
|
.child(icon_button("icons/plus.svg"))
|
||||||
|
.child(icon_button("icons/split.svg")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
97
crates/storybook/src/ui/module/project_panel.rs
Normal file
97
crates/storybook/src/ui/module/project_panel.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::{
|
||||||
|
prelude::{InteractionState, ToggleState},
|
||||||
|
theme::theme,
|
||||||
|
ui::{details, input, label, list_item, IconAsset, LabelColor},
|
||||||
|
};
|
||||||
|
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(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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
146
crates/storybook/src/ui/module/status_bar.rs
Normal file
146
crates/storybook/src/ui/module/status_bar.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::theme::{theme, Theme};
|
||||||
|
use crate::ui::{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 {
|
||||||
|
#[default]
|
||||||
|
ProjectPanel,
|
||||||
|
CollaborationPanel,
|
||||||
|
Terminal,
|
||||||
|
Assistant,
|
||||||
|
Feedback,
|
||||||
|
Diagnostics,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ToolGroup {
|
||||||
|
active_index: Option<usize>,
|
||||||
|
tools: Vec<Tool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ToolGroup {
|
||||||
|
fn default() -> Self {
|
||||||
|
ToolGroup {
|
||||||
|
active_index: None,
|
||||||
|
tools: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct StatusBar<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
left_tools: Option<ToolGroup>,
|
||||||
|
right_tools: Option<ToolGroup>,
|
||||||
|
bottom_tools: Option<ToolGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_bar<V: 'static>() -> StatusBar<V> {
|
||||||
|
StatusBar {
|
||||||
|
view_type: PhantomData,
|
||||||
|
left_tools: None,
|
||||||
|
right_tools: None,
|
||||||
|
bottom_tools: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> StatusBar<V> {
|
||||||
|
pub fn left_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||||
|
self.left_tools = {
|
||||||
|
let mut tools = vec![tool];
|
||||||
|
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||||
|
Some(ToolGroup {
|
||||||
|
active_index,
|
||||||
|
tools,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||||
|
self.right_tools = {
|
||||||
|
let mut tools = vec![tool];
|
||||||
|
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||||
|
Some(ToolGroup {
|
||||||
|
active_index,
|
||||||
|
tools,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bottom_tool(mut self, tool: Tool, active_index: Option<usize>) -> Self {
|
||||||
|
self.bottom_tools = {
|
||||||
|
let mut tools = vec![tool];
|
||||||
|
tools.extend(self.left_tools.take().unwrap_or_default().tools);
|
||||||
|
Some(ToolGroup {
|
||||||
|
active_index,
|
||||||
|
tools,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.py_0p5()
|
||||||
|
.px_1()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.w_full()
|
||||||
|
.fill(theme.lowest.base.default.background)
|
||||||
|
.child(self.left_tools(theme))
|
||||||
|
.child(self.right_tools(theme))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_tools(&self, theme: &Theme) -> impl Element<V> {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(icon_button("icons/project.svg"))
|
||||||
|
.child(icon_button("icons/hash.svg"))
|
||||||
|
.child(tool_divider())
|
||||||
|
.child(icon_button("icons/error.svg"))
|
||||||
|
}
|
||||||
|
fn right_tools(&self, theme: &Theme) -> impl Element<V> {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(text_button("116:25"))
|
||||||
|
.child(text_button("Rust")),
|
||||||
|
)
|
||||||
|
.child(tool_divider())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(icon_button("icons/copilot.svg"))
|
||||||
|
.child(icon_button("icons/feedback.svg")),
|
||||||
|
)
|
||||||
|
.child(tool_divider())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(icon_button("icons/terminal.svg"))
|
||||||
|
.child(icon_button("icons/conversations.svg"))
|
||||||
|
.child(icon_button("icons/ai.svg")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::components::{icon_button, tab, ButtonVariant};
|
use crate::prelude::InteractionState;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
|
use crate::ui::{icon_button, tab};
|
||||||
use gpui2::elements::div::ScrollState;
|
use gpui2::elements::div::ScrollState;
|
||||||
use gpui2::style::StyleHelpers;
|
use gpui2::style::StyleHelpers;
|
||||||
use gpui2::{elements::div, IntoElement};
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
@ -23,7 +24,8 @@ pub fn tab_bar<V: 'static>(scroll_state: ScrollState) -> TabBar<V> {
|
||||||
impl<V: 'static> TabBar<V> {
|
impl<V: 'static> TabBar<V> {
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
let can_navigate_back = true;
|
||||||
|
let can_navigate_forward = false;
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -40,15 +42,22 @@ impl<V: 'static> TabBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(icon_button("icons/arrow_left.svg", ButtonVariant::Filled))
|
.child(
|
||||||
.child(icon_button("icons/arrow_right.svg", ButtonVariant::Ghost)),
|
icon_button("icons/arrow_left.svg")
|
||||||
|
.state(InteractionState::Enabled.if_enabled(can_navigate_back)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
icon_button("icons/arrow_right.svg").state(
|
||||||
|
InteractionState::Enabled.if_enabled(can_navigate_forward),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().w_0().flex_1().h_full().child(
|
div().w_0().flex_1().h_full().child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_px()
|
.gap_1()
|
||||||
.overflow_x_scroll(self.scroll_state.clone())
|
.overflow_x_scroll(self.scroll_state.clone())
|
||||||
.child(tab("Cargo.toml", false))
|
.child(tab("Cargo.toml", false))
|
||||||
.child(tab("Channels Panel", true))
|
.child(tab("Channels Panel", true))
|
||||||
|
@ -74,8 +83,8 @@ impl<V: 'static> TabBar<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_px()
|
.gap_px()
|
||||||
.child(icon_button("icons/plus.svg", ButtonVariant::Ghost))
|
.child(icon_button("icons/plus.svg"))
|
||||||
.child(icon_button("icons/split.svg", ButtonVariant::Ghost)),
|
.child(icon_button("icons/split.svg")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
117
crates/storybook/src/ui/module/title_bar.rs
Normal file
117
crates/storybook/src/ui/module/title_bar.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::prelude::Shape;
|
||||||
|
use crate::theme::theme;
|
||||||
|
use crate::ui::{avatar, follow_group, icon_button, text_button, tool_divider};
|
||||||
|
use gpui2::style::StyleHelpers;
|
||||||
|
use gpui2::{elements::div, IntoElement};
|
||||||
|
use gpui2::{Element, ParentElement, ViewContext};
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct TitleBar<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_bar<V: 'static>() -> TitleBar<V> {
|
||||||
|
TitleBar {
|
||||||
|
view_type: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> TitleBar<V> {
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
let player_list = vec![
|
||||||
|
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
||||||
|
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
|
||||||
|
];
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.w_full()
|
||||||
|
.h_8()
|
||||||
|
.fill(theme.lowest.base.default.background)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.h_full()
|
||||||
|
.gap_4()
|
||||||
|
.px_2()
|
||||||
|
// === Traffic Lights === //
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_3()
|
||||||
|
.h_3()
|
||||||
|
.rounded_full()
|
||||||
|
.fill(theme.lowest.positive.default.foreground),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_3()
|
||||||
|
.h_3()
|
||||||
|
.rounded_full()
|
||||||
|
.fill(theme.lowest.warning.default.foreground),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_3()
|
||||||
|
.h_3()
|
||||||
|
.rounded_full()
|
||||||
|
.fill(theme.lowest.negative.default.foreground),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// === Project Info === //
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(text_button("maxbrunsfeld"))
|
||||||
|
.child(text_button("zed"))
|
||||||
|
.child(text_button("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)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(icon_button("icons/stop_sharing.svg"))
|
||||||
|
.child(icon_button("icons/exit.svg")),
|
||||||
|
)
|
||||||
|
.child(tool_divider())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_1()
|
||||||
|
.child(icon_button("icons/mic.svg"))
|
||||||
|
.child(icon_button("icons/speaker-loud.svg"))
|
||||||
|
.child(icon_button("icons/desktop.svg")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().px_2().flex().items_center().child(
|
||||||
|
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||||
|
.shape(Shape::RoundedRectangle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
133
crates/storybook/src/ui/tracker.md
Normal file
133
crates/storybook/src/ui/tracker.md
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
* = Not in the app today
|
||||||
|
|
||||||
|
## Template
|
||||||
|
- [ ] Workspace
|
||||||
|
- [ ] Title Bar
|
||||||
|
- [ ] Project Panel
|
||||||
|
- [ ] Collab Panel
|
||||||
|
- [ ] Project Diagnosics
|
||||||
|
- [ ] Project Search
|
||||||
|
- [ ] Feedback Editor
|
||||||
|
- [ ] Terminal
|
||||||
|
- [ ] Assistant
|
||||||
|
- [ ] Chat*
|
||||||
|
- [ ] Notifications*
|
||||||
|
- [ ] Status Bar
|
||||||
|
- [ ] Panes
|
||||||
|
- [ ] Pane
|
||||||
|
- [ ] Editor
|
||||||
|
- [ ] Tab Bar
|
||||||
|
- [ ] Tool Bar
|
||||||
|
- [ ] Buffer
|
||||||
|
- [ ] Zoomed Editor (Modal)
|
||||||
|
|
||||||
|
### Palettes
|
||||||
|
- [ ] Project Files Palette (⌘-P)
|
||||||
|
- [ ] Command Palette (⌘-SHIFT-P)
|
||||||
|
- [ ] Recent Projects Palette (⌘-OPT-O)
|
||||||
|
- [ ] Recent Branches Palette (⌘-OPT-B)
|
||||||
|
- [ ] Project Symbols (⌘-T)
|
||||||
|
- [ ] Theme Palette (⌘-K, ⌘-T)
|
||||||
|
- [ ] Outline View (⌘-SHIFT-O)
|
||||||
|
|
||||||
|
### Debug Views
|
||||||
|
- [ ] LSP Tool
|
||||||
|
- [ ] Syntax Tree
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
### Title Bar
|
||||||
|
- [ ] Traffic Lights
|
||||||
|
- [ ] Host Menu
|
||||||
|
- [ ] Project Menu
|
||||||
|
- [ ] Branch Menu
|
||||||
|
- [ ] Collaborators
|
||||||
|
- [ ] Add Collaborator*
|
||||||
|
- [ ] Project Controls
|
||||||
|
- [ ] Call Controls
|
||||||
|
- [ ] User Menu
|
||||||
|
|
||||||
|
### Project Panel
|
||||||
|
- [ ] Open Editors*
|
||||||
|
- [ ] Open Files (Non-project files)
|
||||||
|
- [ ] Project Files
|
||||||
|
- [ ] Root Folder - Context Menu
|
||||||
|
- [ ] Folder - Context Menu
|
||||||
|
- [ ] File - Context Menu
|
||||||
|
- [ ] Project Filter*
|
||||||
|
|
||||||
|
### Collab Panel
|
||||||
|
- [ ] Current Call
|
||||||
|
- [ ] Channels
|
||||||
|
- [ ] Channel - Context Menu
|
||||||
|
- [ ] Contacts
|
||||||
|
- [ ] Collab Filter
|
||||||
|
|
||||||
|
### Project Diagnosics
|
||||||
|
WIP
|
||||||
|
|
||||||
|
### Feedback Editor
|
||||||
|
- [ ] Feedback Header
|
||||||
|
- [ ] Editor
|
||||||
|
- [ ] Feedback Actions
|
||||||
|
|
||||||
|
### Terminal
|
||||||
|
- [ ] Terminal Toolbar*
|
||||||
|
- [ ] Terminal Line
|
||||||
|
- [ ] Terminal Input
|
||||||
|
|
||||||
|
### Assistant
|
||||||
|
- [ ] Toolbar
|
||||||
|
- [ ] History / Past Conversations
|
||||||
|
- [ ] Model Controls / Token Counter
|
||||||
|
- [ ] Chat Editor
|
||||||
|
|
||||||
|
### Chat
|
||||||
|
WIP
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
WIP
|
||||||
|
|
||||||
|
### Status Bar
|
||||||
|
- [ ] Status Bar Tool (Icon)
|
||||||
|
- [ ] Status Bar Tool (Text)
|
||||||
|
- [ ] Status Bar Tool - Context Menu
|
||||||
|
- [ ] Status Bar Tool - Popover Palette
|
||||||
|
- [ ] Status Bar Tool - Popover Menu
|
||||||
|
- [ ] Diagnostic Message
|
||||||
|
- [ ] LSP Message
|
||||||
|
- [ ] Update message (New version available, downloading, etc)
|
||||||
|
|
||||||
|
### Panes/Pane
|
||||||
|
|
||||||
|
- [ ] Editor
|
||||||
|
- [ ] Split Divider/Control
|
||||||
|
|
||||||
|
### Editor
|
||||||
|
- [ ] Editor
|
||||||
|
- [ ] Read-only Editor
|
||||||
|
- [ ] Rendered Markdown View*
|
||||||
|
|
||||||
|
### Tab Bar
|
||||||
|
- [ ] Navigation History / Control
|
||||||
|
- [ ] Tabs
|
||||||
|
- [ ] Editor Controls (New, Split, Zoom)
|
||||||
|
|
||||||
|
### Tool Bar
|
||||||
|
- [ ] Breadcrumb
|
||||||
|
- [ ] Editor Tool (Togglable)
|
||||||
|
- [ ] Buffer Search
|
||||||
|
|
||||||
|
### Buffer
|
||||||
|
|
||||||
|
### Zoomed Editor (Modal)
|
||||||
|
- [ ] Modal View
|
||||||
|
|
||||||
|
### Palette
|
||||||
|
- [ ] Input
|
||||||
|
- [ ] Section Title
|
||||||
|
- [ ] List
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
- [ ] Context Menu
|
|
@ -1,7 +1,10 @@
|
||||||
use crate::{collab_panel::collab_panel, modules::tab_bar, theme::theme};
|
use crate::{
|
||||||
|
theme::theme,
|
||||||
|
ui::{chat_panel, project_panel, status_bar, tab_bar, title_bar},
|
||||||
|
};
|
||||||
use gpui2::{
|
use gpui2::{
|
||||||
elements::{div, div::ScrollState, img, svg},
|
elements::{div, div::ScrollState},
|
||||||
style::{StyleHelpers, Styleable},
|
style::StyleHelpers,
|
||||||
Element, IntoElement, ParentElement, ViewContext,
|
Element, IntoElement, ParentElement, ViewContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,8 +32,8 @@ impl WorkspaceElement {
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.items_start()
|
.items_start()
|
||||||
.text_color(theme.lowest.base.default.foreground)
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
.fill(theme.middle.base.default.background)
|
.fill(theme.lowest.base.default.background)
|
||||||
.child(titlebar())
|
.child(title_bar())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
@ -38,7 +41,7 @@ impl WorkspaceElement {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(collab_panel(self.left_scroll_state.clone()))
|
.child(project_panel(self.left_scroll_state.clone()))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.h_full()
|
.h_full()
|
||||||
|
@ -52,397 +55,8 @@ impl WorkspaceElement {
|
||||||
.child(tab_bar(self.tab_bar_scroll_state.clone())),
|
.child(tab_bar(self.tab_bar_scroll_state.clone())),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(collab_panel(self.right_scroll_state.clone())),
|
.child(chat_panel(self.right_scroll_state.clone())),
|
||||||
)
|
|
||||||
.child(statusbar())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Element)]
|
|
||||||
struct TitleBar;
|
|
||||||
|
|
||||||
pub fn titlebar<V: 'static>() -> impl Element<V> {
|
|
||||||
TitleBar
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TitleBar {
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.w_full()
|
|
||||||
.h_8()
|
|
||||||
.fill(theme.lowest.base.default.background)
|
|
||||||
.child(self.left_group(cx))
|
|
||||||
.child(self.right_group(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.h_full()
|
|
||||||
.gap_4()
|
|
||||||
.px_2()
|
|
||||||
// === Traffic Lights === //
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_3()
|
|
||||||
.h_3()
|
|
||||||
.rounded_full()
|
|
||||||
.fill(theme.lowest.positive.default.foreground),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_3()
|
|
||||||
.h_3()
|
|
||||||
.rounded_full()
|
|
||||||
.fill(theme.lowest.warning.default.foreground),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_3()
|
|
||||||
.h_3()
|
|
||||||
.rounded_full()
|
|
||||||
.fill(theme.lowest.negative.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// === Project Info === //
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.px_2()
|
|
||||||
.rounded_md()
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(div().text_sm().child("project")),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.px_2()
|
|
||||||
.rounded_md()
|
|
||||||
.text_color(theme.lowest.variant.default.foreground)
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(div().text_sm().child("branch")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.h_full()
|
|
||||||
.gap_3()
|
|
||||||
.px_2()
|
|
||||||
// === Actions === //
|
|
||||||
.child(
|
|
||||||
div().child(
|
|
||||||
div().flex().items_center().gap_1().child(
|
|
||||||
div().size_4().flex().items_center().justify_center().child(
|
|
||||||
svg()
|
|
||||||
.path("icons/exit.svg")
|
|
||||||
.size_4()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
|
||||||
// === Comms === //
|
|
||||||
.child(
|
|
||||||
div().child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_px()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.rounded_md()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/microphone.svg")
|
|
||||||
.size_3p5()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.rounded_md()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/speaker-loud.svg")
|
|
||||||
.size_3p5()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.px_2()
|
|
||||||
.py_1()
|
|
||||||
.rounded_md()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/desktop.svg")
|
|
||||||
.size_3p5()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
|
||||||
// User Group
|
|
||||||
.child(
|
|
||||||
div().child(
|
|
||||||
div()
|
|
||||||
.px_1()
|
|
||||||
.py_1()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.rounded_md()
|
|
||||||
.gap_0p5()
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(
|
|
||||||
img()
|
|
||||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
|
||||||
.size_4()
|
|
||||||
.rounded_md()
|
|
||||||
.fill(theme.middle.on.default.foreground),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/caret_down.svg")
|
|
||||||
.w_2()
|
|
||||||
.h_2()
|
|
||||||
.fill(theme.lowest.variant.default.foreground),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================================================================================ //
|
|
||||||
|
|
||||||
#[derive(Element)]
|
|
||||||
struct StatusBar;
|
|
||||||
|
|
||||||
pub fn statusbar<V: 'static>() -> impl Element<V> {
|
|
||||||
StatusBar
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar {
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.w_full()
|
|
||||||
.h_8()
|
|
||||||
.fill(theme.lowest.base.default.background)
|
|
||||||
.child(self.left_group(cx))
|
|
||||||
.child(self.right_group(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.h_full()
|
|
||||||
.gap_4()
|
|
||||||
.px_2()
|
|
||||||
// === Tools === //
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_6()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/project.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_6()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/conversations.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_6()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/file_icons/notebook.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.accent.default.foreground),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// === Diagnostics === //
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.gap_0p5()
|
|
||||||
.px_1()
|
|
||||||
.text_color(theme.lowest.variant.default.foreground)
|
|
||||||
.hover()
|
|
||||||
.fill(theme.lowest.base.hovered.background)
|
|
||||||
.active()
|
|
||||||
.fill(theme.lowest.base.pressed.background)
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/error.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.negative.default.foreground),
|
|
||||||
)
|
|
||||||
.child(div().text_sm().child("2")),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.text_color(theme.lowest.variant.default.foreground)
|
|
||||||
.child("Something is wrong"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
let theme = theme(cx);
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.h_full()
|
|
||||||
.gap_4()
|
|
||||||
.px_2()
|
|
||||||
// === Tools === //
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_6()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/check_circle.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.base.default.foreground),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w_6()
|
|
||||||
.h_full()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.child(
|
|
||||||
svg()
|
|
||||||
.path("icons/copilot.svg")
|
|
||||||
.w_4()
|
|
||||||
.h_4()
|
|
||||||
.fill(theme.lowest.accent.default.foreground),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.child(status_bar())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
docs/ui/states.md
Normal file
43
docs/ui/states.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
## Interaction State
|
||||||
|
|
||||||
|
**Enabled**
|
||||||
|
|
||||||
|
An enabled state communicates an interactive component or element.
|
||||||
|
|
||||||
|
**Disabled**
|
||||||
|
|
||||||
|
A disabled state communicates a inoperable component or element.
|
||||||
|
|
||||||
|
**Hover**
|
||||||
|
|
||||||
|
A hover state communicates when a user has placed a cursor above an interactive element.
|
||||||
|
|
||||||
|
**Focused**
|
||||||
|
|
||||||
|
A focused state communicates when a user has highlighted an element, using an input method such as a keyboard or voice.
|
||||||
|
|
||||||
|
**Activated**
|
||||||
|
|
||||||
|
An activated state communicates a highlighted destination, whether initiated by the user or by default.
|
||||||
|
|
||||||
|
**Pressed**
|
||||||
|
|
||||||
|
A pressed state communicates a user tap.
|
||||||
|
|
||||||
|
**Dragged**
|
||||||
|
|
||||||
|
A dragged state communicates when a user presses and moves an element.
|
||||||
|
|
||||||
|
## Selected State
|
||||||
|
|
||||||
|
**Unselected**
|
||||||
|
|
||||||
|
dfa
|
||||||
|
|
||||||
|
**Partially Selected**
|
||||||
|
|
||||||
|
daf
|
||||||
|
|
||||||
|
**Selected**
|
||||||
|
|
||||||
|
dfa
|
Loading…
Add table
Add a link
Reference in a new issue