diff --git a/Cargo.lock b/Cargo.lock index b576750..d52206f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5621,7 +5621,6 @@ dependencies = [ "once_cell", "paste", "regex", - "rust-embed", "serde", "serde_json", "smallvec", diff --git a/crates/app/src/views/sidebar/inbox.rs b/crates/app/src/views/sidebar/inbox.rs index 49bd5f7..c2c1f64 100644 --- a/crates/app/src/views/sidebar/inbox.rs +++ b/crates/app/src/views/sidebar/inbox.rs @@ -60,44 +60,30 @@ impl Inbox { .justify_between() .text_xs() .rounded_md() - .hover(|this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .child( - div() - .font_medium() - .text_color(cx.theme().sidebar_accent_foreground) - .map(|this| { - if room.is_group { - this.flex() - .items_center() - .gap_2() - .child( - img("brand/avatar.png").size_6().rounded_full(), - ) - .child(room.name()) - } else { - this.when_some(room.members.first(), |this, sender| { - this.flex() - .items_center() - .gap_2() - .child( - img(sender.avatar()) - .size_6() - .rounded_full() - .flex_shrink_0(), - ) - .child(sender.name()) - }) - } - }), - ) - .child( - div() - .child(ago) - .text_color(cx.theme().sidebar_accent_foreground.opacity(0.7)), - ) + .hover(|this| this.bg(cx.theme().list_hover)) + .child(div().font_medium().map(|this| { + if room.is_group { + this.flex() + .items_center() + .gap_2() + .child(img("brand/avatar.png").size_6().rounded_full()) + .child(room.name()) + } else { + this.when_some(room.members.first(), |this, sender| { + this.flex() + .items_center() + .gap_2() + .child( + img(sender.avatar()) + .size_6() + .rounded_full() + .flex_shrink_0(), + ) + .child(sender.name()) + }) + } + })) + .child(div().child(ago)) .on_click(cx.listener(move |this, _, cx| { this.action(id, cx); })) @@ -143,8 +129,7 @@ impl Render for Inbox { .rounded_md() .text_xs() .font_semibold() - .text_color(cx.theme().sidebar_foreground.opacity(0.7)) - .hover(|this| this.bg(cx.theme().sidebar_accent.opacity(0.7))) + .hover(|this| this.bg(cx.theme().list_hover)) .on_click(cx.listener(move |view, _event, cx| { view.is_collapsed = !view.is_collapsed; cx.notify(); diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index db4a971..be80b8a 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -6,8 +6,6 @@ publish = false [dependencies] gpui.workspace = true - -rust-embed.workspace = true smol.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/ui/src/badge.rs b/crates/ui/src/badge.rs index 0e7e0f1..4597267 100644 --- a/crates/ui/src/badge.rs +++ b/crates/ui/src/badge.rs @@ -23,7 +23,7 @@ impl BadgeVariant { Self::Primary => cx.theme().primary, Self::Secondary => cx.theme().secondary, Self::Outline => gpui::transparent_black(), - Self::Destructive => cx.theme().destructive, + Self::Destructive => cx.theme().danger, Self::Custom { color, .. } => *color, } } @@ -33,7 +33,7 @@ impl BadgeVariant { Self::Primary => cx.theme().primary, Self::Secondary => cx.theme().secondary, Self::Outline => cx.theme().border, - Self::Destructive => cx.theme().destructive, + Self::Destructive => cx.theme().danger, Self::Custom { border, .. } => *border, } } @@ -43,7 +43,7 @@ impl BadgeVariant { Self::Primary => cx.theme().primary_foreground, Self::Secondary => cx.theme().secondary_foreground, Self::Outline => cx.theme().foreground, - Self::Destructive => cx.theme().destructive_foreground, + Self::Destructive => cx.theme().danger_foreground, Self::Custom { foreground, .. } => *foreground, } } diff --git a/crates/ui/src/breadcrumb.rs b/crates/ui/src/breadcrumb.rs deleted file mode 100644 index 6337c80..0000000 --- a/crates/ui/src/breadcrumb.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::rc::Rc; - -use gpui::{ - div, prelude::FluentBuilder as _, ClickEvent, ElementId, InteractiveElement as _, IntoElement, - ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext, -}; - -use crate::{h_flex, theme::ActiveTheme, Icon, IconName}; - -#[derive(IntoElement)] -pub struct Breadcrumb { - items: Vec, -} - -type OnClick = Option>; - -#[derive(IntoElement)] -pub struct BreadcrumbItem { - id: ElementId, - text: SharedString, - on_click: OnClick, - disabled: bool, - is_last: bool, -} - -impl BreadcrumbItem { - pub fn new(id: impl Into, text: impl Into) -> Self { - Self { - id: id.into(), - text: text.into(), - on_click: None, - disabled: false, - is_last: false, - } - } - - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } - - pub fn on_click( - mut self, - on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.on_click = Some(Rc::new(on_click)); - self - } - - /// For internal use only. - #[allow(clippy::wrong_self_convention)] - fn is_last(mut self, is_last: bool) -> Self { - self.is_last = is_last; - self - } -} - -impl RenderOnce for BreadcrumbItem { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - div() - .id(self.id) - .child(self.text) - .text_color(cx.theme().muted_foreground) - .when(self.is_last, |this| this.text_color(cx.theme().foreground)) - .when(self.disabled, |this| { - this.text_color(cx.theme().muted_foreground) - }) - .when(!self.disabled, |this| { - this.when_some(self.on_click, |this, on_click| { - this.cursor_pointer().on_click(move |event, cx| { - on_click(event, cx); - }) - }) - }) - } -} - -impl Breadcrumb { - pub fn new() -> Self { - Self { items: Vec::new() } - } - - /// Add an item to the breadcrumb. - pub fn item(mut self, item: BreadcrumbItem) -> Self { - self.items.push(item); - self - } -} - -impl Default for Breadcrumb { - fn default() -> Self { - Self::new() - } -} - -#[derive(IntoElement)] -struct BreadcrumbSeparator; -impl RenderOnce for BreadcrumbSeparator { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - Icon::new(IconName::ChevronRight) - .text_color(cx.theme().muted_foreground) - .size_3p5() - .into_any_element() - } -} - -impl RenderOnce for Breadcrumb { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let items_count = self.items.len(); - - let mut children = vec![]; - for (ix, item) in self.items.into_iter().enumerate() { - let is_last = ix == items_count - 1; - - children.push(item.is_last(is_last).into_any_element()); - if !is_last { - children.push(BreadcrumbSeparator.into_any_element()); - } - } - - h_flex() - .gap_1p5() - .text_sm() - .text_color(cx.theme().muted_foreground) - .children(children) - } -} diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 32e353b..48c8292 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -1,15 +1,14 @@ -use gpui::{ - div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Corners, Div, Edges, - ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, - RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext, -}; - use crate::{ indicator::Indicator, theme::{ActiveTheme, Colorize as _}, tooltip::Tooltip, Disableable, Icon, Selectable, Sizable, Size, StyledExt, }; +use gpui::{ + div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Corners, Div, Edges, + ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, + RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext, +}; pub enum ButtonRounded { None, @@ -418,7 +417,7 @@ impl RenderOnce for Button { let hover_style = style.hovered(cx); this.bg(hover_style.bg) .border_color(hover_style.border) - .text_color(crate::red_400()) + .text_color(cx.theme().danger) }) .active(|this| { let active_style = style.active(cx); @@ -505,7 +504,7 @@ impl ButtonVariant { match self { ButtonVariant::Primary => cx.theme().primary, ButtonVariant::Secondary => cx.theme().secondary, - ButtonVariant::Danger => cx.theme().destructive, + ButtonVariant::Danger => cx.theme().danger, ButtonVariant::Outline | ButtonVariant::Ghost | ButtonVariant::Link @@ -520,7 +519,7 @@ impl ButtonVariant { ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { cx.theme().secondary_foreground } - ButtonVariant::Danger => cx.theme().destructive_foreground, + ButtonVariant::Danger => cx.theme().danger_foreground, ButtonVariant::Link => cx.theme().link, ButtonVariant::Text => cx.theme().foreground, ButtonVariant::Custom(colors) => colors.foreground, @@ -531,7 +530,7 @@ impl ButtonVariant { match self { ButtonVariant::Primary => cx.theme().primary, ButtonVariant::Secondary => cx.theme().border, - ButtonVariant::Danger => cx.theme().destructive, + ButtonVariant::Danger => cx.theme().danger, ButtonVariant::Outline => cx.theme().border, ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => { cx.theme().transparent @@ -572,7 +571,7 @@ impl ButtonVariant { let bg = match self { ButtonVariant::Primary => cx.theme().primary_hover, ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_hover, - ButtonVariant::Danger => cx.theme().destructive_hover, + ButtonVariant::Danger => cx.theme().danger_hover, ButtonVariant::Ghost => { if cx.theme().mode.is_dark() { cx.theme().secondary.lighten(0.1).opacity(0.8) @@ -612,7 +611,7 @@ impl ButtonVariant { cx.theme().secondary.darken(0.2).opacity(0.8) } } - ButtonVariant::Danger => cx.theme().destructive_active, + ButtonVariant::Danger => cx.theme().danger_active, ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Custom(colors) => colors.active, @@ -646,7 +645,7 @@ impl ButtonVariant { cx.theme().secondary.darken(0.2).opacity(0.8) } } - ButtonVariant::Danger => cx.theme().destructive_active, + ButtonVariant::Danger => cx.theme().danger_active, ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Custom(colors) => colors.active, @@ -676,7 +675,7 @@ impl ButtonVariant { | ButtonVariant::Outline | ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Primary => cx.theme().primary.opacity(0.15), - ButtonVariant::Danger => cx.theme().destructive.opacity(0.15), + ButtonVariant::Danger => cx.theme().danger.opacity(0.15), ButtonVariant::Secondary => cx.theme().secondary.opacity(1.5), ButtonVariant::Custom(style) => style.color.opacity(0.15), }; diff --git a/crates/ui/src/color_picker.rs b/crates/ui/src/color_picker.rs deleted file mode 100644 index 1a77603..0000000 --- a/crates/ui/src/color_picker.rs +++ /dev/null @@ -1,371 +0,0 @@ -use gpui::{ - anchored, canvas, deferred, div, prelude::FluentBuilder as _, px, relative, AppContext, Bounds, - Corner, ElementId, EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement as _, - IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, Render, SharedString, - StatefulInteractiveElement as _, Styled, View, ViewContext, VisualContext, -}; - -use crate::{ - divider::Divider, - h_flex, - input::{InputEvent, TextInput}, - popover::Escape, - theme::{ActiveTheme as _, Colorize}, - tooltip::Tooltip, - v_flex, ColorExt as _, Sizable, Size, StyleSized, -}; - -const KEY_CONTEXT: &str = "ColorPicker"; - -pub fn init(cx: &mut AppContext) { - cx.bind_keys([KeyBinding::new("escape", Escape, Some(KEY_CONTEXT))]) -} - -#[derive(Clone)] -pub enum ColorPickerEvent { - Change(Option), -} - -fn color_palettes() -> Vec> { - use crate::colors::DEFAULT_COLOR; - use itertools::Itertools as _; - - macro_rules! c { - ($color:tt) => { - DEFAULT_COLOR - .$color - .keys() - .sorted() - .map(|k| DEFAULT_COLOR.$color.get(k).map(|c| c.hsla).unwrap()) - .collect::>() - }; - } - - vec![ - c!(stone), - c!(red), - c!(orange), - c!(yellow), - c!(green), - c!(cyan), - c!(blue), - c!(purple), - c!(pink), - ] -} - -pub struct ColorPicker { - id: ElementId, - focus_handle: FocusHandle, - value: Option, - featured_colors: Vec, - hovered_color: Option, - label: Option, - size: Size, - anchor: Corner, - color_input: View, - - open: bool, - bounds: Bounds, -} - -impl ColorPicker { - pub fn new(id: impl Into, cx: &mut ViewContext) -> Self { - let color_input = cx.new_view(|cx| TextInput::new(cx).xsmall()); - - cx.subscribe(&color_input, |this, _, ev: &InputEvent, cx| match ev { - InputEvent::Change(value) => { - if let Ok(color) = Hsla::parse_hex_string(value) { - this.value = Some(color); - this.hovered_color = Some(color); - } - } - InputEvent::PressEnter => { - let val = this.color_input.read(cx).text(); - if let Ok(color) = Hsla::parse_hex_string(&val) { - this.open = false; - this.update_value(Some(color), true, cx); - } - } - _ => {} - }) - .detach(); - - Self { - id: id.into(), - focus_handle: cx.focus_handle(), - featured_colors: vec![ - crate::black(), - crate::gray_600(), - crate::gray_400(), - crate::white(), - crate::red_600(), - crate::orange_600(), - crate::yellow_600(), - crate::green_600(), - crate::blue_600(), - crate::indigo_600(), - crate::purple_600(), - ], - value: None, - hovered_color: None, - size: Size::Medium, - label: None, - anchor: Corner::TopLeft, - color_input, - open: false, - bounds: Bounds::default(), - } - } - - /// Set the featured colors to be displayed in the color picker. - /// - /// This is used to display a set of colors that the user can quickly select from, - /// for example provided user's last used colors. - pub fn featured_colors(mut self, colors: Vec) -> Self { - self.featured_colors = colors; - self - } - - /// Set current color value. - pub fn set_value(&mut self, value: Hsla, cx: &mut ViewContext) { - self.update_value(Some(value), false, cx) - } - - /// Set the size of the color picker, default is `Size::Medium`. - pub fn size(mut self, size: Size) -> Self { - self.size = size; - self - } - - /// Set the label to be displayed above the color picker. - /// - /// Default is `None`. - pub fn label(mut self, label: impl Into) -> Self { - self.label = Some(label.into()); - self - } - - /// Set the anchor corner of the color picker. - /// - /// Default is `Corner::TopLeft`. - pub fn anchor(mut self, anchor: Corner) -> Self { - self.anchor = anchor; - self - } - - fn on_escape(&mut self, _: &Escape, cx: &mut ViewContext) { - cx.propagate(); - - self.open = false; - cx.notify(); - } - - fn toggle_picker(&mut self, _: &gpui::ClickEvent, cx: &mut ViewContext) { - self.open = !self.open; - cx.notify(); - } - - fn update_value(&mut self, value: Option, emit: bool, cx: &mut ViewContext) { - self.value = value; - self.hovered_color = value; - self.color_input.update(cx, |view, cx| { - if let Some(value) = value { - view.set_text(value.to_hex_string(), cx); - } else { - view.set_text("", cx); - } - }); - if emit { - cx.emit(ColorPickerEvent::Change(value)); - } - cx.notify(); - } - - fn render_item( - &self, - color: Hsla, - clickable: bool, - cx: &mut ViewContext, - ) -> impl IntoElement { - div() - .id(SharedString::from(format!( - "color-{}", - color.to_hex_string() - ))) - .h_5() - .w_5() - .bg(color) - .border_1() - .border_color(color.darken(0.1)) - .when(clickable, |this| { - this.cursor_pointer() - .hover(|this| { - this.border_color(color.darken(0.3)) - .bg(color.lighten(0.1)) - .shadow_sm() - }) - .active(|this| this.border_color(color.darken(0.5)).bg(color.darken(0.2))) - .on_mouse_move(cx.listener(move |view, _, cx| { - view.hovered_color = Some(color); - cx.notify(); - })) - .on_click(cx.listener(move |view, _, cx| { - view.update_value(Some(color), true, cx); - view.open = false; - cx.notify(); - })) - }) - } - - fn render_colors(&self, cx: &mut ViewContext) -> impl IntoElement { - v_flex() - .gap_3() - .child( - h_flex().gap_1().children( - self.featured_colors - .iter() - .map(|color| self.render_item(*color, true, cx)), - ), - ) - .child(Divider::horizontal()) - .child( - v_flex() - .gap_1() - .children(color_palettes().iter().map(|sub_colors| { - h_flex().gap_1().children( - sub_colors - .iter() - .rev() - .map(|color| self.render_item(*color, true, cx)), - ) - })), - ) - .when_some(self.hovered_color, |this, hovered_color| { - this.child(Divider::horizontal()).child( - h_flex() - .gap_2() - .items_center() - .child( - div() - .bg(hovered_color) - .flex_shrink_0() - .border_1() - .border_color(hovered_color.darken(0.2)) - .size_5() - .rounded(px(cx.theme().radius)), - ) - .child(self.color_input.clone()), - ) - }) - } - - fn resolved_corner(&self, bounds: Bounds) -> Point { - bounds.corner(match self.anchor { - Corner::TopLeft => Corner::BottomLeft, - Corner::TopRight => Corner::BottomRight, - Corner::BottomLeft => Corner::TopLeft, - Corner::BottomRight => Corner::TopRight, - }) - } -} - -impl Sizable for ColorPicker { - fn with_size(mut self, size: impl Into) -> Self { - self.size = size.into(); - self - } -} -impl EventEmitter for ColorPicker {} -impl FocusableView for ColorPicker { - fn focus_handle(&self, _: &AppContext) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Render for ColorPicker { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let display_title: SharedString = if let Some(value) = self.value { - value.to_hex_string() - } else { - "".to_string() - } - .into(); - - let view = cx.view().clone(); - - div() - .id(self.id.clone()) - .key_context(KEY_CONTEXT) - .track_focus(&self.focus_handle) - .on_action(cx.listener(Self::on_escape)) - .child( - h_flex() - .id("color-picker-input") - .cursor_pointer() - .gap_2() - .items_center() - .input_text_size(self.size) - .line_height(relative(1.)) - .child( - div() - .id("color-picker-square") - .bg(cx.theme().background) - .border_1() - .border_color(cx.theme().input) - .rounded(px(cx.theme().radius)) - .bg(cx.theme().background) - .shadow_sm() - .overflow_hidden() - .size_with(self.size) - .when_some(self.value, |this, value| { - this.bg(value).border_color(value.darken(0.3)) - }) - .tooltip(move |cx| Tooltip::new(display_title.clone(), cx)), - ) - .when_some(self.label.clone(), |this, label| this.child(label)) - .on_click(cx.listener(Self::toggle_picker)) - .child( - canvas( - move |bounds, cx| view.update(cx, |r, _| r.bounds = bounds), - |_, _, _| {}, - ) - .absolute() - .size_full(), - ), - ) - .when(self.open, |this| { - this.child( - deferred( - anchored() - .anchor(self.anchor) - .snap_to_window_with_margin(px(8.)) - .position(self.resolved_corner(self.bounds)) - .child( - div() - .occlude() - .map(|this| match self.anchor { - Corner::TopLeft | Corner::TopRight => this.mt_1p5(), - Corner::BottomLeft | Corner::BottomRight => this.mb_1p5(), - }) - .w_72() - .overflow_hidden() - .rounded_lg() - .p_3() - .border_1() - .border_color(cx.theme().border) - .shadow_lg() - .rounded_lg() - .bg(cx.theme().background) - .on_mouse_up_out( - MouseButton::Left, - cx.listener(|view, _, cx| view.on_escape(&Escape, cx)), - ) - .child(self.render_colors(cx)), - ), - ) - .with_priority(1), - ) - }) - } -} diff --git a/crates/ui/src/colors.rs b/crates/ui/src/colors.rs deleted file mode 100644 index b0b1e09..0000000 --- a/crates/ui/src/colors.rs +++ /dev/null @@ -1,297 +0,0 @@ -use std::collections::HashMap; - -use gpui::Hsla; -use serde::{de::Error, Deserialize, Deserializer}; - -use crate::theme::hsl; -use anyhow::Result; - -pub(crate) trait ColorExt { - fn to_hex_string(&self) -> String; - fn parse_hex_string(hex: &str) -> Result; -} - -impl ColorExt for Hsla { - fn to_hex_string(&self) -> String { - let rgb = self.to_rgb(); - - if rgb.a < 1. { - return format!( - "#{:02X}{:02X}{:02X}{:02X}", - ((rgb.r * 255.) as u32), - ((rgb.g * 255.) as u32), - ((rgb.b * 255.) as u32), - ((self.a * 255.) as u32) - ); - } - - format!( - "#{:02X}{:02X}{:02X}", - ((rgb.r * 255.) as u32), - ((rgb.g * 255.) as u32), - ((rgb.b * 255.) as u32) - ) - } - - fn parse_hex_string(hex: &str) -> Result { - let hex = hex.trim_start_matches('#'); - let len = hex.len(); - if len != 6 && len != 8 { - return Err(anyhow::anyhow!("invalid hex color")); - } - - let r = u8::from_str_radix(&hex[0..2], 16)? as f32 / 255.; - let g = u8::from_str_radix(&hex[2..4], 16)? as f32 / 255.; - let b = u8::from_str_radix(&hex[4..6], 16)? as f32 / 255.; - let a = if len == 8 { - u8::from_str_radix(&hex[6..8], 16)? as f32 / 255. - } else { - 1. - }; - - let v = gpui::Rgba { r, g, b, a }; - let color: Hsla = v.into(); - Ok(color) - } -} - -pub(crate) static DEFAULT_COLOR: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(|| { - serde_json::from_str(include_str!("../colors.json")).expect("failed to parse default-json") - }); - -type ColorScales = HashMap; - -mod color_scales { - use std::collections::HashMap; - - use super::{ColorScales, ShadcnColor}; - - use serde::de::{Deserialize, Deserializer}; - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mut map = HashMap::new(); - for color in Vec::::deserialize(deserializer)? { - map.insert(color.scale, color); - } - Ok(map) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] -pub(crate) struct ShadcnColors { - pub(crate) black: ShadcnColor, - pub(crate) white: ShadcnColor, - #[serde(with = "color_scales")] - pub(crate) slate: ColorScales, - #[serde(with = "color_scales")] - pub(crate) gray: ColorScales, - #[serde(with = "color_scales")] - pub(crate) zinc: ColorScales, - #[serde(with = "color_scales")] - pub(crate) neutral: ColorScales, - #[serde(with = "color_scales")] - pub(crate) stone: ColorScales, - #[serde(with = "color_scales")] - pub(crate) red: ColorScales, - #[serde(with = "color_scales")] - pub(crate) orange: ColorScales, - #[serde(with = "color_scales")] - pub(crate) amber: ColorScales, - #[serde(with = "color_scales")] - pub(crate) yellow: ColorScales, - #[serde(with = "color_scales")] - pub(crate) lime: ColorScales, - #[serde(with = "color_scales")] - pub(crate) green: ColorScales, - #[serde(with = "color_scales")] - pub(crate) emerald: ColorScales, - #[serde(with = "color_scales")] - pub(crate) teal: ColorScales, - #[serde(with = "color_scales")] - pub(crate) cyan: ColorScales, - #[serde(with = "color_scales")] - pub(crate) sky: ColorScales, - #[serde(with = "color_scales")] - pub(crate) blue: ColorScales, - #[serde(with = "color_scales")] - pub(crate) indigo: ColorScales, - #[serde(with = "color_scales")] - pub(crate) violet: ColorScales, - #[serde(with = "color_scales")] - pub(crate) purple: ColorScales, - #[serde(with = "color_scales")] - pub(crate) fuchsia: ColorScales, - #[serde(with = "color_scales")] - pub(crate) pink: ColorScales, - #[serde(with = "color_scales")] - pub(crate) rose: ColorScales, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)] -pub(crate) struct ShadcnColor { - #[serde(default)] - pub(crate) scale: usize, - #[serde(deserialize_with = "from_hsl_channel", alias = "hslChannel")] - pub(crate) hsla: Hsla, -} - -/// Deserialize Hsla from a string in the format "210 40% 98%" -fn from_hsl_channel<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let s: String = Deserialize::deserialize(deserializer).unwrap(); - - let mut parts = s.split_whitespace(); - if parts.clone().count() != 3 { - return Err(D::Error::custom( - "expected hslChannel has 3 parts, e.g: '210 40% 98%'", - )); - } - - fn parse_number(s: &str) -> f32 { - s.trim_end_matches('%') - .parse() - .expect("failed to parse number") - } - - let (h, s, l) = ( - parse_number(parts.next().unwrap()), - parse_number(parts.next().unwrap()), - parse_number(parts.next().unwrap()), - ); - - Ok(hsl(h, s, l)) -} - -macro_rules! color_method { - ($color:tt, $scale:tt) => { - paste::paste! { - #[inline] - #[allow(unused)] - pub fn [<$color _ $scale>]() -> Hsla { - if let Some(color) = DEFAULT_COLOR.$color.get(&($scale as usize)) { - return color.hsla; - } - - black() - } - } - }; -} - -macro_rules! color_methods { - ($color:tt) => { - paste::paste! { - /// Get color by scale number. - /// - /// The possible scale numbers are: - /// 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950 - /// - /// If the scale number is not found, it will return black color. - #[inline] - pub fn [<$color>](scale: usize) -> Hsla { - if let Some(color) = DEFAULT_COLOR.$color.get(&scale) { - return color.hsla; - } - - black() - } - } - - color_method!($color, 50); - color_method!($color, 100); - color_method!($color, 200); - color_method!($color, 300); - color_method!($color, 400); - color_method!($color, 500); - color_method!($color, 600); - color_method!($color, 700); - color_method!($color, 800); - color_method!($color, 900); - color_method!($color, 950); - }; -} - -pub fn black() -> Hsla { - DEFAULT_COLOR.black.hsla -} - -pub fn white() -> Hsla { - DEFAULT_COLOR.white.hsla -} - -color_methods!(slate); -color_methods!(gray); -color_methods!(zinc); -color_methods!(neutral); -color_methods!(stone); -color_methods!(red); -color_methods!(orange); -color_methods!(amber); -color_methods!(yellow); -color_methods!(lime); -color_methods!(green); -color_methods!(emerald); -color_methods!(teal); -color_methods!(cyan); -color_methods!(sky); -color_methods!(blue); -color_methods!(indigo); -color_methods!(violet); -color_methods!(purple); -color_methods!(fuchsia); -color_methods!(pink); -color_methods!(rose); - -#[cfg(test)] -mod tests { - use gpui::{rgb, rgba}; - - use super::*; - - #[test] - fn test_default_colors() { - assert_eq!(white(), hsl(0.0, 0.0, 100.0)); - assert_eq!(black(), hsl(0.0, 0.0, 0.0)); - - assert_eq!(slate_50(), hsl(210.0, 40.0, 98.0)); - assert_eq!(slate_100(), hsl(210.0, 40.0, 96.1)); - assert_eq!(slate_900(), hsl(222.2, 47.4, 11.2)); - - assert_eq!(red_50(), hsl(0.0, 85.7, 97.3)); - assert_eq!(yellow_100(), hsl(54.9, 96.7, 88.0)); - assert_eq!(green_200(), hsl(141.0, 78.9, 85.1)); - assert_eq!(cyan_300(), hsl(187.0, 92.4, 69.0)); - assert_eq!(blue_400(), hsl(213.1, 93.9, 67.8)); - assert_eq!(indigo_500(), hsl(238.7, 83.5, 66.7)); - } - - #[test] - fn test_to_hex_string() { - let color: Hsla = rgb(0xf8fafc).into(); - assert_eq!(color.to_hex_string(), "#F8FAFC"); - - let color: Hsla = rgb(0xfef2f2).into(); - assert_eq!(color.to_hex_string(), "#FEF2F2"); - - let color: Hsla = rgba(0x0413fcaa).into(); - assert_eq!(color.to_hex_string(), "#0413FCAA"); - } - - #[test] - fn test_from_hex_string() { - let color: Hsla = Hsla::parse_hex_string("#F8FAFC").unwrap(); - assert_eq!(color, rgb(0xf8fafc).into()); - - let color: Hsla = Hsla::parse_hex_string("#FEF2F2").unwrap(); - assert_eq!(color, rgb(0xfef2f2).into()); - - let color: Hsla = Hsla::parse_hex_string("#0413FCAA").unwrap(); - assert_eq!(color, rgba(0x0413fcaa).into()); - } -} diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 1d1a0b2..ed27d35 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -856,8 +856,8 @@ impl Render for DockArea { .h_full() // Left dock .when_some(self.left_dock.clone(), |this, dock| { - this.bg(cx.theme().sidebar) - .text_color(cx.theme().sidebar_foreground) + this.bg(cx.theme().muted) + .text_color(cx.theme().muted_foreground) .child(div().flex().flex_none().child(dock)) }) // Center diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index 47f4140..f5220fe 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -11,7 +11,7 @@ use crate::{ dock::PanelInfo, h_flex, popup_menu::{PopupMenu, PopupMenuExt}, - tab::{Tab, TabBar}, + tab::{tab_bar::TabBar, Tab}, theme::ActiveTheme, v_flex, AxisExt, IconName, Placement, Selectable, Sizable, }; diff --git a/crates/ui/src/indicator.rs b/crates/ui/src/indicator.rs index dd545a5..728a884 100644 --- a/crates/ui/src/indicator.rs +++ b/crates/ui/src/indicator.rs @@ -1,10 +1,9 @@ -use std::time::Duration; - use crate::{Icon, IconName, Sizable, Size}; use gpui::{ div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, Hsla, IntoElement, ParentElement, RenderOnce, Styled as _, Transformation, WindowContext, }; +use std::time::Duration; #[derive(IntoElement)] pub struct Indicator { diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 2947b65..4b75831 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -1,3 +1,5 @@ +use super::TextInput; +use crate::theme::ActiveTheme as _; use gpui::{ fill, point, px, relative, size, Bounds, Corners, Element, ElementId, ElementInputHandler, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, PaintQuad, Path, Pixels, @@ -5,10 +7,6 @@ use gpui::{ }; use smallvec::SmallVec; -use crate::theme::ActiveTheme as _; - -use super::TextInput; - const RIGHT_MARGIN: Pixels = px(5.); const CURSOR_INSET: Pixels = px(0.5); @@ -146,7 +144,7 @@ impl TextElement { ), size(px(1.5), line_height), ), - crate::blue_500(), + cx.theme().primary, )) }; } diff --git a/crates/ui/src/input/mod.rs b/crates/ui/src/input/mod.rs index 779dd67..2abac4d 100644 --- a/crates/ui/src/input/mod.rs +++ b/crates/ui/src/input/mod.rs @@ -4,8 +4,6 @@ mod clear_button; mod element; #[allow(clippy::module_inception)] mod input; -mod otp_input; pub(crate) use clear_button::*; pub use input::*; -pub use otp_input::*; diff --git a/crates/ui/src/input/otp_input.rs b/crates/ui/src/input/otp_input.rs deleted file mode 100644 index fc7fd2e..0000000 --- a/crates/ui/src/input/otp_input.rs +++ /dev/null @@ -1,274 +0,0 @@ -use gpui::{ - div, prelude::FluentBuilder, px, AnyElement, Context, EventEmitter, FocusHandle, FocusableView, - InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, MouseDownEvent, - ParentElement as _, Render, SharedString, Styled as _, ViewContext, -}; - -use crate::{h_flex, theme::ActiveTheme, v_flex, Icon, IconName, Sizable, Size}; - -use super::{blink_cursor::BlinkCursor, InputEvent}; - -pub enum InputOptEvent { - /// When all OTP input have filled, this event will be triggered. - Change(SharedString), -} - -/// A One Time Password (OTP) input element. -/// -/// This can accept a fixed length number and can be masked. -/// -/// Use case example: -/// -/// - SMS OTP -/// - Authenticator OTP -pub struct OtpInput { - focus_handle: FocusHandle, - length: usize, - number_of_groups: usize, - masked: bool, - value: SharedString, - blink_cursor: Model, - size: Size, -} - -impl OtpInput { - pub fn new(length: usize, cx: &mut ViewContext) -> Self { - let focus_handle = cx.focus_handle(); - let blink_cursor = cx.new_model(|_| BlinkCursor::new()); - let input = Self { - focus_handle: focus_handle.clone(), - length, - number_of_groups: 2, - value: SharedString::default(), - masked: false, - blink_cursor: blink_cursor.clone(), - size: Size::Medium, - }; - - // Observe the blink cursor to repaint the view when it changes. - cx.observe(&blink_cursor, |_, _, cx| cx.notify()).detach(); - // Blink the cursor when the window is active, pause when it's not. - cx.observe_window_activation(|this, cx| { - if cx.is_window_active() { - let focus_handle = this.focus_handle.clone(); - if focus_handle.is_focused(cx) { - this.blink_cursor.update(cx, |blink_cursor, cx| { - blink_cursor.start(cx); - }); - } - } - }) - .detach(); - - cx.on_focus(&focus_handle, Self::on_focus).detach(); - cx.on_blur(&focus_handle, Self::on_blur).detach(); - - input - } - - /// Set number of groups in the OTP Input. - pub fn groups(mut self, n: usize) -> Self { - self.number_of_groups = n; - self - } - - /// Set default value of the OTP Input. - pub fn default_value(mut self, value: impl Into) -> Self { - self.value = value.into(); - self - } - - /// Set value of the OTP Input. - pub fn set_value(&mut self, value: impl Into, cx: &mut ViewContext) { - self.value = value.into(); - cx.notify(); - } - - /// Return the value of the OTP Input. - pub fn value(&self) -> SharedString { - self.value.clone() - } - - /// Set masked to true use masked input. - pub fn masked(mut self, masked: bool) -> Self { - self.masked = masked; - self - } - - /// Set masked to true use masked input. - pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext) { - self.masked = masked; - cx.notify(); - } - - pub fn focus(&self, cx: &mut ViewContext) { - self.focus_handle.focus(cx); - } - - fn on_input_mouse_down(&mut self, _: &MouseDownEvent, cx: &mut ViewContext) { - cx.focus(&self.focus_handle); - } - - fn on_key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext) { - let mut chars: Vec = self.value.chars().collect(); - let ix = chars.len(); - - let key = event.keystroke.key.as_str(); - - match key { - "backspace" => { - if ix > 0 { - let ix = ix - 1; - chars.remove(ix); - } - - cx.prevent_default(); - cx.stop_propagation(); - } - _ => { - let c = key.chars().next().unwrap(); - if !c.is_ascii_digit() { - return; - } - if ix >= self.length { - return; - } - - chars.push(c); - - cx.prevent_default(); - cx.stop_propagation(); - } - } - - self.pause_blink_cursor(cx); - self.value = SharedString::from(chars.iter().collect::()); - - if self.value.chars().count() == self.length { - cx.emit(InputEvent::Change(self.value.clone())); - } - cx.notify() - } - - fn on_focus(&mut self, cx: &mut ViewContext) { - self.blink_cursor.update(cx, |cursor, cx| { - cursor.start(cx); - }); - cx.emit(InputEvent::Focus); - } - - fn on_blur(&mut self, cx: &mut ViewContext) { - self.blink_cursor.update(cx, |cursor, cx| { - cursor.stop(cx); - }); - cx.emit(InputEvent::Blur); - } - - fn pause_blink_cursor(&mut self, cx: &mut ViewContext) { - self.blink_cursor.update(cx, |cursor, cx| { - cursor.pause(cx); - }); - } -} - -impl Sizable for OtpInput { - fn with_size(mut self, size: impl Into) -> Self { - self.size = size.into(); - self - } -} - -impl FocusableView for OtpInput { - fn focus_handle(&self, _: &gpui::AppContext) -> FocusHandle { - self.focus_handle.clone() - } -} -impl EventEmitter for OtpInput {} - -impl Render for OtpInput { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let blink_show = self.blink_cursor.read(cx).visible(); - let is_focused = self.focus_handle.is_focused(cx); - - let text_size = match self.size { - Size::XSmall => px(14.), - Size::Small => px(14.), - Size::Medium => px(16.), - Size::Large => px(18.), - Size::Size(v) => v * 0.5, - }; - - let mut groups: Vec> = Vec::with_capacity(self.number_of_groups); - let mut group_ix = 0; - let group_items_count = self.length / self.number_of_groups; - for _ in 0..self.number_of_groups { - groups.push(vec![]); - } - - for i in 0..self.length { - let c = self.value.chars().nth(i); - if i % group_items_count == 0 && i != 0 { - group_ix += 1; - } - - let is_input_focused = i == self.value.chars().count() && is_focused; - - groups[group_ix].push( - h_flex() - .id(("input-otp", i)) - .border_1() - .border_color(cx.theme().input) - .bg(cx.theme().background) - .when(is_input_focused, |this| this.border_color(cx.theme().ring)) - .when(cx.theme().shadow, |this| this.shadow_sm()) - .items_center() - .justify_center() - .rounded_md() - .text_size(text_size) - .map(|this| match self.size { - Size::XSmall => this.w_6().h_6(), - Size::Small => this.w_6().h_6(), - Size::Medium => this.w_8().h_8(), - Size::Large => this.w_11().h_11(), - Size::Size(px) => this.w(px).h(px), - }) - .on_mouse_down(MouseButton::Left, cx.listener(Self::on_input_mouse_down)) - .map(|this| match c { - Some(c) => { - if self.masked { - this.child( - Icon::new(IconName::Asterisk) - .text_color(cx.theme().secondary_foreground) - .with_size(text_size), - ) - } else { - this.child(c.to_string()) - } - } - None => this.when(is_input_focused && blink_show, |this| { - this.child( - div() - .h_4() - .w_0() - .border_l_3() - .border_color(crate::blue_500()), - ) - }), - }) - .into_any_element(), - ); - } - - v_flex() - .track_focus(&self.focus_handle) - .on_key_down(cx.listener(Self::on_key_down)) - .items_center() - .child( - h_flex().items_center().gap_5().children( - groups - .into_iter() - .map(|inputs| h_flex().items_center().gap_1().children(inputs)), - ), - ) - } -} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index afaace2..a584a9e 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -1,11 +1,9 @@ pub mod animation; pub mod badge; -pub mod breadcrumb; pub mod button; pub mod button_group; pub mod checkbox; pub mod clipboard; -pub mod color_picker; pub mod context_menu; pub mod divider; pub mod dock; @@ -14,11 +12,9 @@ pub mod history; pub mod indicator; pub mod input; pub mod label; -pub mod link; pub mod list; pub mod modal; pub mod notification; -pub mod number_input; pub mod popover; pub mod popup_menu; pub mod prelude; @@ -26,7 +22,6 @@ pub mod progress; pub mod radio; pub mod resizable; pub mod scroll; -pub mod sidebar; pub mod skeleton; pub mod slider; pub mod switch; @@ -34,7 +29,7 @@ pub mod tab; pub mod theme; pub mod tooltip; -pub use colors::*; +pub use crate::Disableable; pub use event::InteractiveElementExt; pub use focusable::FocusableCycle; pub use icon::*; @@ -43,9 +38,6 @@ pub use styled::*; pub use title_bar::*; pub use window_border::{window_border, WindowBorder}; -pub use crate::Disableable; - -mod colors; mod event; mod focusable; mod icon; @@ -54,12 +46,6 @@ mod styled; mod title_bar; mod window_border; -use rust_embed::RustEmbed; - -#[derive(RustEmbed)] -#[folder = "../../assets"] -pub struct Assets; - /// Initialize the UI module. /// /// This must be called before using any of the UI components. @@ -69,7 +55,6 @@ pub fn init(cx: &mut gpui::AppContext) { dock::init(cx); dropdown::init(cx); input::init(cx); - number_input::init(cx); list::init(cx); modal::init(cx); popover::init(cx); diff --git a/crates/ui/src/link.rs b/crates/ui/src/link.rs deleted file mode 100644 index 81a95f2..0000000 --- a/crates/ui/src/link.rs +++ /dev/null @@ -1,95 +0,0 @@ -use gpui::{ - div, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, - RenderOnce, SharedString, Stateful, StatefulInteractiveElement, Styled, -}; - -use crate::theme::ActiveTheme as _; - -type OnClick = Option>; - -/// A Link element like a `` tag in HTML. -#[derive(IntoElement)] -pub struct Link { - base: Stateful
, - href: Option, - disabled: bool, - on_click: OnClick, -} - -impl Link { - pub fn new(id: impl Into) -> Self { - Self { - base: div().id(id), - href: None, - on_click: None, - disabled: false, - } - } - - pub fn href(mut self, href: impl Into) -> Self { - self.href = Some(href.into()); - self - } - - pub fn on_click( - mut self, - handler: impl Fn(&ClickEvent, &mut gpui::WindowContext) + 'static, - ) -> Self { - self.on_click = Some(Box::new(handler)); - self - } - - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} - -impl Styled for Link { - fn style(&mut self) -> &mut gpui::StyleRefinement { - self.base.style() - } -} - -impl ParentElement for Link { - fn extend(&mut self, elements: impl IntoIterator) { - self.base.extend(elements) - } -} - -impl RenderOnce for Link { - fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { - let href = self.href.clone(); - let on_click = self.on_click; - - div() - .text_color(cx.theme().link) - .text_decoration_1() - .text_decoration_color(cx.theme().link) - .hover(|this| { - this.text_color(cx.theme().link.opacity(0.8)) - .text_decoration_1() - }) - .cursor_pointer() - .child( - self.base - .active(|this| { - this.text_color(cx.theme().link.opacity(0.6)) - .text_decoration_1() - }) - .on_mouse_down(MouseButton::Left, |_, cx| { - cx.stop_propagation(); - }) - .on_click({ - move |e, cx| { - if let Some(href) = &href { - cx.open_url(&href.clone()); - } - if let Some(on_click) = &on_click { - on_click(e, cx); - } - } - }), - ) - } -} diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index c8934f5..85452c1 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -1,19 +1,21 @@ -use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration}; - +use crate::{ + animation::cubic_bezier, + button::{Button, ButtonVariants as _}, + h_flex, + theme::{ + colors::{blue, green, red, yellow}, + scale::ColorScaleStep, + ActiveTheme as _, + }, + v_flex, Icon, IconName, Sizable as _, StyledExt, +}; use gpui::{ div, prelude::FluentBuilder, px, Animation, AnimationExt, ClickEvent, DismissEvent, ElementId, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext, }; use smol::Timer; - -use crate::{ - animation::cubic_bezier, - button::{Button, ButtonVariants as _}, - h_flex, - theme::ActiveTheme as _, - v_flex, Icon, IconName, Sizable as _, StyledExt, -}; +use std::{any::TypeId, collections::VecDeque, sync::Arc, time::Duration}; pub enum NotificationType { Info, @@ -206,16 +208,16 @@ impl Render for Notification { let icon = match self.icon.clone() { Some(icon) => icon, None => match self.type_ { - NotificationType::Info => Icon::new(IconName::Info).text_color(crate::blue_500()), - NotificationType::Success => { - Icon::new(IconName::CircleCheck).text_color(crate::green_500()) - } - NotificationType::Warning => { - Icon::new(IconName::TriangleAlert).text_color(crate::yellow_500()) + NotificationType::Info => { + Icon::new(IconName::Info).text_color(blue().step(cx, ColorScaleStep::FIVE)) } NotificationType::Error => { - Icon::new(IconName::CircleX).text_color(crate::red_500()) + Icon::new(IconName::CircleX).text_color(red().step(cx, ColorScaleStep::FIVE)) } + NotificationType::Success => Icon::new(IconName::CircleCheck) + .text_color(green().step(cx, ColorScaleStep::FIVE)), + NotificationType::Warning => Icon::new(IconName::TriangleAlert) + .text_color(yellow().step(cx, ColorScaleStep::FIVE)), }, }; diff --git a/crates/ui/src/number_input.rs b/crates/ui/src/number_input.rs deleted file mode 100644 index 3746282..0000000 --- a/crates/ui/src/number_input.rs +++ /dev/null @@ -1,168 +0,0 @@ -use gpui::{ - actions, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement, - KeyBinding, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, - VisualContext, -}; -use regex::Regex; - -use crate::{ - button::{Button, ButtonVariants as _}, - h_flex, - input::{InputEvent, TextInput}, - prelude::FluentBuilder, - theme::ActiveTheme, - IconName, Sizable, Size, StyledExt, -}; - -actions!(number_input, [Increment, Decrement]); - -const KEY_CONTENT: &str = "NumberInput"; - -pub fn init(cx: &mut AppContext) { - cx.bind_keys(vec![ - KeyBinding::new("up", Increment, Some(KEY_CONTENT)), - KeyBinding::new("down", Decrement, Some(KEY_CONTENT)), - ]); -} - -pub struct NumberInput { - input: View, - _subscriptions: Vec, -} - -impl NumberInput { - pub fn new(cx: &mut ViewContext) -> Self { - // Default pattern for the number input. - let pattern = Regex::new(r"^-?(\d+)?\.?(\d+)?$").unwrap(); - - let input = cx.new_view(|cx| TextInput::new(cx).pattern(pattern).appearance(false)); - - let _subscriptions = vec![cx.subscribe(&input, |_, _, event: &InputEvent, cx| { - cx.emit(NumberInputEvent::Input(event.clone())); - })]; - - Self { - input, - _subscriptions, - } - } - - pub fn placeholder( - self, - placeholder: impl Into, - cx: &mut ViewContext, - ) -> Self { - self.input - .update(cx, |input, _| input.set_placeholder(placeholder)); - self - } - - pub fn set_placeholder(&self, text: impl Into, cx: &mut ViewContext) { - self.input.update(cx, |input, _| { - input.set_placeholder(text); - }); - } - - pub fn pattern(self, pattern: regex::Regex, cx: &mut ViewContext) -> Self { - self.input.update(cx, |input, _| input.set_pattern(pattern)); - self - } - - pub fn set_size(self, size: Size, cx: &mut ViewContext) -> Self { - self.input.update(cx, |input, cx| input.set_size(size, cx)); - self - } - - pub fn small(self, cx: &mut ViewContext) -> Self { - self.set_size(Size::Small, cx) - } - - pub fn xsmall(self, cx: &mut ViewContext) -> Self { - self.set_size(Size::XSmall, cx) - } - - pub fn large(self, cx: &mut ViewContext) -> Self { - self.set_size(Size::Large, cx) - } - - pub fn set_value(&self, text: impl Into, cx: &mut ViewContext) { - self.input.update(cx, |input, cx| input.set_text(text, cx)) - } - - pub fn set_disabled(&self, disabled: bool, cx: &mut ViewContext) { - self.input - .update(cx, |input, cx| input.set_disabled(disabled, cx)); - } - - pub fn increment(&mut self, cx: &mut ViewContext) { - self.handle_increment(&Increment, cx); - } - - pub fn decrement(&mut self, cx: &mut ViewContext) { - self.handle_decrement(&Decrement, cx); - } - - fn handle_increment(&mut self, _: &Increment, cx: &mut ViewContext) { - self.on_step(StepAction::Increment, cx); - } - - fn handle_decrement(&mut self, _: &Decrement, cx: &mut ViewContext) { - self.on_step(StepAction::Decrement, cx); - } - - fn on_step(&mut self, action: StepAction, cx: &mut ViewContext) { - cx.emit(NumberInputEvent::Step(action)); - } -} - -impl FocusableView for NumberInput { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.input.focus_handle(cx) - } -} - -pub enum StepAction { - Decrement, - Increment, -} - -pub enum NumberInputEvent { - Input(InputEvent), - Step(StepAction), -} - -impl EventEmitter for NumberInput {} - -impl Render for NumberInput { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let focused = self.input.focus_handle(cx).is_focused(cx); - - h_flex() - .key_context(KEY_CONTENT) - .on_action(cx.listener(Self::handle_increment)) - .on_action(cx.listener(Self::handle_decrement)) - .flex_1() - .px_1() - .gap_x_3() - .bg(cx.theme().background) - .border_color(cx.theme().border) - .border_1() - .rounded_md() - .when(focused, |this| this.outline(cx)) - .child( - Button::new("minus") - .ghost() - .xsmall() - .icon(IconName::Minus) - .on_click(cx.listener(|this, _, cx| this.on_step(StepAction::Decrement, cx))), - ) - .child(self.input.clone()) - .child( - Button::new("plus") - .ghost() - .xsmall() - .icon(IconName::Plus) - .on_click(cx.listener(|this, _, cx| this.on_step(StepAction::Increment, cx))), - ) - } -} diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs index 40d92f5..3edea09 100644 --- a/crates/ui/src/popup_menu.rs +++ b/crates/ui/src/popup_menu.rs @@ -1,13 +1,12 @@ -use gpui::*; -use prelude::FluentBuilder; -use std::{cell::Cell, ops::Deref, rc::Rc}; - use crate::scroll::{Scrollbar, ScrollbarState}; use crate::StyledExt; use crate::{ button::Button, h_flex, list::ListItem, popover::Popover, theme::ActiveTheme, v_flex, Icon, IconName, Selectable, Sizable as _, }; +use gpui::*; +use prelude::FluentBuilder; +use std::{cell::Cell, ops::Deref, rc::Rc}; actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]); diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index f4d0f00..fd1ecbd 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,7 +1,4 @@ -//! The prelude of this crate. When building UI in Zed you almost always want to import this. - pub use gpui::prelude::*; -#[allow(unused_imports)] pub use gpui::{ div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext, diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index 4c63fe9..1d22456 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -1,7 +1,7 @@ use gpui::{Axis, ViewContext}; - mod panel; mod resize_handle; + pub use panel::*; pub(crate) use resize_handle::*; diff --git a/crates/ui/src/sidebar/footer.rs b/crates/ui/src/sidebar/footer.rs deleted file mode 100644 index aa4b650..0000000 --- a/crates/ui/src/sidebar/footer.rs +++ /dev/null @@ -1,89 +0,0 @@ -use gpui::{ - prelude::FluentBuilder as _, Div, ElementId, InteractiveElement, IntoElement, ParentElement, - RenderOnce, SharedString, Styled, -}; - -use crate::{h_flex, popup_menu::PopupMenuExt, theme::ActiveTheme as _, Collapsible, Selectable}; - -#[derive(IntoElement)] -pub struct SidebarFooter { - id: ElementId, - base: Div, - selected: bool, - is_collapsed: bool, -} - -impl SidebarFooter { - pub fn new() -> Self { - Self { - id: SharedString::from("sidebar-footer").into(), - base: h_flex().gap_2().w_full(), - selected: false, - is_collapsed: false, - } - } -} - -impl Default for SidebarFooter { - fn default() -> Self { - Self::new() - } -} - -impl Selectable for SidebarFooter { - fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - fn element_id(&self) -> &gpui::ElementId { - &self.id - } -} - -impl Collapsible for SidebarFooter { - fn is_collapsed(&self) -> bool { - self.is_collapsed - } - - fn collapsed(mut self, collapsed: bool) -> Self { - self.is_collapsed = collapsed; - self - } -} - -impl ParentElement for SidebarFooter { - fn extend(&mut self, elements: impl IntoIterator) { - self.base.extend(elements); - } -} - -impl Styled for SidebarFooter { - fn style(&mut self) -> &mut gpui::StyleRefinement { - self.base.style() - } -} - -impl PopupMenuExt for SidebarFooter {} - -impl RenderOnce for SidebarFooter { - fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement { - h_flex() - .id(self.id) - .gap_2() - .p_2() - .w_full() - .justify_between() - .cursor_pointer() - .rounded_md() - .hover(|this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .when(self.selected, |this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .child(self.base) - } -} diff --git a/crates/ui/src/sidebar/group.rs b/crates/ui/src/sidebar/group.rs deleted file mode 100644 index e336a06..0000000 --- a/crates/ui/src/sidebar/group.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::{theme::ActiveTheme, v_flex, Collapsible}; -use gpui::{ - div, prelude::FluentBuilder as _, Div, IntoElement, ParentElement, RenderOnce, SharedString, - Styled as _, WindowContext, -}; - -/// A sidebar group -#[derive(IntoElement)] -pub struct SidebarGroup { - base: Div, - label: SharedString, - is_collapsed: bool, - children: Vec, -} - -impl SidebarGroup { - pub fn new(label: impl Into) -> Self { - Self { - base: div().gap_2().flex_col(), - label: label.into(), - is_collapsed: false, - children: Vec::new(), - } - } - - pub fn child(mut self, child: E) -> Self { - self.children.push(child); - self - } - - pub fn children(mut self, children: impl IntoIterator) -> Self { - self.children.extend(children); - self - } -} - -impl Collapsible for SidebarGroup { - fn is_collapsed(&self) -> bool { - self.is_collapsed - } - - fn collapsed(mut self, collapsed: bool) -> Self { - self.is_collapsed = collapsed; - self - } -} - -impl RenderOnce for SidebarGroup { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - v_flex() - .relative() - .p_2() - .when(!self.is_collapsed, |this| { - this.child( - div() - .flex_shrink_0() - .px_2() - .rounded_md() - .text_xs() - .text_color(cx.theme().sidebar_foreground.opacity(0.7)) - .h_8() - .child(self.label), - ) - }) - .child( - self.base.children( - self.children - .into_iter() - .map(|child| child.collapsed(self.is_collapsed)), - ), - ) - } -} diff --git a/crates/ui/src/sidebar/header.rs b/crates/ui/src/sidebar/header.rs deleted file mode 100644 index a1e21fb..0000000 --- a/crates/ui/src/sidebar/header.rs +++ /dev/null @@ -1,89 +0,0 @@ -use gpui::{ - prelude::FluentBuilder as _, Div, ElementId, InteractiveElement, IntoElement, ParentElement, - RenderOnce, SharedString, Styled, -}; - -use crate::{h_flex, popup_menu::PopupMenuExt, theme::ActiveTheme as _, Collapsible, Selectable}; - -#[derive(IntoElement)] -pub struct SidebarHeader { - id: ElementId, - base: Div, - selected: bool, - is_collapsed: bool, -} - -impl SidebarHeader { - pub fn new() -> Self { - Self { - id: SharedString::from("sidebar-header").into(), - base: h_flex().gap_2().w_full(), - selected: false, - is_collapsed: false, - } - } -} - -impl Default for SidebarHeader { - fn default() -> Self { - Self::new() - } -} - -impl Selectable for SidebarHeader { - fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - fn element_id(&self) -> &gpui::ElementId { - &self.id - } -} - -impl Collapsible for SidebarHeader { - fn is_collapsed(&self) -> bool { - self.is_collapsed - } - - fn collapsed(mut self, collapsed: bool) -> Self { - self.is_collapsed = collapsed; - self - } -} - -impl ParentElement for SidebarHeader { - fn extend(&mut self, elements: impl IntoIterator) { - self.base.extend(elements); - } -} - -impl Styled for SidebarHeader { - fn style(&mut self) -> &mut gpui::StyleRefinement { - self.base.style() - } -} - -impl PopupMenuExt for SidebarHeader {} - -impl RenderOnce for SidebarHeader { - fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement { - h_flex() - .id(self.id) - .gap_2() - .p_2() - .w_full() - .justify_between() - .cursor_pointer() - .rounded_md() - .hover(|this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .when(self.selected, |this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .child(self.base) - } -} diff --git a/crates/ui/src/sidebar/menu.rs b/crates/ui/src/sidebar/menu.rs deleted file mode 100644 index 079e948..0000000 --- a/crates/ui/src/sidebar/menu.rs +++ /dev/null @@ -1,246 +0,0 @@ -use crate::{h_flex, theme::ActiveTheme as _, v_flex, Collapsible, Icon, IconName, StyledExt}; -use gpui::{ - div, percentage, prelude::FluentBuilder as _, ClickEvent, InteractiveElement as _, IntoElement, - ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, - WindowContext, -}; -use std::rc::Rc; - -#[derive(IntoElement)] -pub struct SidebarMenu { - is_collapsed: bool, - items: Vec, -} - -impl SidebarMenu { - pub fn new() -> Self { - Self { - items: Vec::new(), - is_collapsed: false, - } - } - - pub fn menu( - mut self, - label: impl Into, - icon: Option, - active: bool, - handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.items.push(SidebarMenuItem::Item { - icon, - label: label.into(), - handler: Rc::new(handler), - active, - is_collapsed: self.is_collapsed, - }); - self - } - - pub fn submenu( - mut self, - label: impl Into, - icon: Option, - open: bool, - items: impl FnOnce(SidebarMenu) -> Self, - handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - let menu = SidebarMenu::new(); - let menu = items(menu); - self.items.push(SidebarMenuItem::Submenu { - icon, - label: label.into(), - items: menu.items, - is_open: open, - is_collapsed: self.is_collapsed, - handler: Rc::new(handler), - }); - self - } -} - -impl Default for SidebarMenu { - fn default() -> Self { - Self::new() - } -} - -impl Collapsible for SidebarMenu { - fn is_collapsed(&self) -> bool { - self.is_collapsed - } - - fn collapsed(mut self, collapsed: bool) -> Self { - self.is_collapsed = collapsed; - self - } -} -impl RenderOnce for SidebarMenu { - fn render(self, _: &mut WindowContext) -> impl IntoElement { - v_flex() - .gap_2() - .children(self.items.into_iter().map(|mut item| { - match &mut item { - SidebarMenuItem::Item { is_collapsed, .. } => *is_collapsed = self.is_collapsed, - SidebarMenuItem::Submenu { is_collapsed, .. } => { - *is_collapsed = self.is_collapsed - } - } - item - })) - } -} - -type Handler = Rc; - -/// A sidebar menu item -#[derive(IntoElement)] -enum SidebarMenuItem { - Item { - icon: Option, - label: SharedString, - handler: Handler, - active: bool, - is_collapsed: bool, - }, - Submenu { - icon: Option, - label: SharedString, - handler: Handler, - items: Vec, - is_open: bool, - is_collapsed: bool, - }, -} - -impl SidebarMenuItem { - fn is_submenu(&self) -> bool { - matches!(self, SidebarMenuItem::Submenu { .. }) - } - - fn icon(&self) -> Option { - match self { - SidebarMenuItem::Item { icon, .. } => icon.clone(), - SidebarMenuItem::Submenu { icon, .. } => icon.clone(), - } - } - - fn label(&self) -> SharedString { - match self { - SidebarMenuItem::Item { label, .. } => label.clone(), - SidebarMenuItem::Submenu { label, .. } => label.clone(), - } - } - - fn is_active(&self) -> bool { - match self { - SidebarMenuItem::Item { active, .. } => *active, - SidebarMenuItem::Submenu { .. } => false, - } - } - - fn is_open(&self) -> bool { - match self { - SidebarMenuItem::Item { .. } => false, - SidebarMenuItem::Submenu { is_open, items, .. } => { - *is_open || items.iter().any(|item| item.is_active()) - } - } - } - - fn is_collapsed(&self) -> bool { - match self { - SidebarMenuItem::Item { is_collapsed, .. } => *is_collapsed, - SidebarMenuItem::Submenu { is_collapsed, .. } => *is_collapsed, - } - } - - fn render_menu_item( - &self, - is_submenu: bool, - is_active: bool, - is_open: bool, - cx: &WindowContext, - ) -> impl IntoElement { - let handler = match &self { - SidebarMenuItem::Item { handler, .. } => Some(handler.clone()), - SidebarMenuItem::Submenu { handler, .. } => Some(handler.clone()), - }; - let is_collapsed = self.is_collapsed(); - - h_flex() - .id("sidebar-menu-item") - .overflow_hidden() - .flex_shrink_0() - .p_2() - .gap_2() - .items_center() - .rounded_md() - .text_sm() - .cursor_pointer() - .hover(|this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .when(is_active, |this| { - this.font_medium() - .bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .when_some(self.icon(), |this, icon| this.child(icon.size_4())) - .when(is_collapsed, |this| { - this.justify_center().size_7().mx_auto() - }) - .when(!is_collapsed, |this| { - this.h_7() - .child(div().flex_1().child(self.label())) - .when(is_submenu, |this| { - this.child( - Icon::new(IconName::ChevronRight) - .size_4() - .when(is_open, |this| this.rotate(percentage(90. / 360.))), - ) - }) - }) - .when_some(handler, |this, handler| { - this.on_click(move |ev, cx| handler(ev, cx)) - }) - } -} - -impl RenderOnce for SidebarMenuItem { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let is_submenu = self.is_submenu(); - let is_active = self.is_active(); - let is_open = self.is_open(); - - div() - .w_full() - .child(self.render_menu_item(is_submenu, is_active, is_open, cx)) - .when(is_open, |this| { - this.map(|this| match self { - SidebarMenuItem::Submenu { - items, - is_collapsed, - .. - } => { - if is_collapsed { - this - } else { - this.child( - v_flex() - .border_l_1() - .border_color(cx.theme().sidebar_border) - .gap_1() - .mx_3p5() - .px_2p5() - .py_0p5() - .children(items), - ) - } - } - _ => this, - }) - }) - } -} diff --git a/crates/ui/src/sidebar/mod.rs b/crates/ui/src/sidebar/mod.rs deleted file mode 100644 index 41031b1..0000000 --- a/crates/ui/src/sidebar/mod.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::{ - button::{Button, ButtonVariants}, - h_flex, - scroll::ScrollbarAxis, - theme::ActiveTheme, - v_flex, Collapsible, Icon, IconName, Side, Sizable, StyledExt, -}; -use gpui::{ - div, prelude::FluentBuilder, px, AnyElement, ClickEvent, Entity, EntityId, - InteractiveElement as _, IntoElement, ParentElement, Pixels, Render, RenderOnce, Styled, View, - WindowContext, -}; -use std::rc::Rc; - -mod footer; -mod group; -mod header; -mod menu; -pub use footer::*; -pub use group::*; -pub use header::*; -pub use menu::*; - -const DEFAULT_WIDTH: Pixels = px(255.); -const COLLAPSED_WIDTH: Pixels = px(48.); - -/// A sidebar -#[derive(IntoElement)] -pub struct Sidebar { - /// The parent view id - view_id: EntityId, - content: Vec, - /// header view - header: Option, - /// footer view - footer: Option, - /// The side of the sidebar - side: Side, - collapsible: bool, - width: Pixels, - is_collapsed: bool, -} - -impl Sidebar { - fn new(view_id: EntityId, side: Side) -> Self { - Self { - view_id, - content: vec![], - header: None, - footer: None, - side, - collapsible: true, - width: DEFAULT_WIDTH, - is_collapsed: false, - } - } - - pub fn left(view: &View) -> Self { - Self::new(view.entity_id(), Side::Left) - } - - pub fn right(view: &View) -> Self { - Self::new(view.entity_id(), Side::Right) - } - - /// Set the width of the sidebar - pub fn width(mut self, width: Pixels) -> Self { - self.width = width; - self - } - - /// Set the sidebar to be collapsible, default is true - pub fn collapsible(mut self, collapsible: bool) -> Self { - self.collapsible = collapsible; - self - } - - /// Set the sidebar to be collapsed - pub fn collapsed(mut self, collapsed: bool) -> Self { - self.is_collapsed = collapsed; - self - } - - /// Set the header of the sidebar. - pub fn header(mut self, header: impl IntoElement) -> Self { - self.header = Some(header.into_any_element()); - self - } - - /// Set the footer of the sidebar. - pub fn footer(mut self, footer: impl IntoElement) -> Self { - self.footer = Some(footer.into_any_element()); - self - } - - /// Add a child element to the sidebar, the child must implement `Collapsible` - pub fn child(mut self, child: E) -> Self { - self.content.push(child); - self - } - - /// Add multiple children to the sidebar, the children must implement `Collapsible` - pub fn children(mut self, children: impl IntoIterator) -> Self { - self.content.extend(children); - self - } -} - -type OnClick = Option>; - -/// Sidebar collapse button with Icon. -#[derive(IntoElement)] -pub struct SidebarToggleButton { - btn: Button, - is_collapsed: bool, - side: Side, - on_click: OnClick, -} - -impl SidebarToggleButton { - fn new(side: Side) -> Self { - Self { - btn: Button::new("sidebar-collapse").ghost().small(), - is_collapsed: false, - side, - on_click: None, - } - } - - pub fn left() -> Self { - Self::new(Side::Left) - } - - pub fn right() -> Self { - Self::new(Side::Right) - } - - pub fn collapsed(mut self, is_collapsed: bool) -> Self { - self.is_collapsed = is_collapsed; - self - } - - pub fn on_click( - mut self, - on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.on_click = Some(Rc::new(on_click)); - self - } -} - -impl RenderOnce for SidebarToggleButton { - fn render(self, _: &mut WindowContext) -> impl IntoElement { - let is_collapsed = self.is_collapsed; - let on_click = self.on_click.clone(); - - let icon = if is_collapsed { - if self.side.is_left() { - IconName::PanelLeftOpen - } else { - IconName::PanelRightOpen - } - } else if self.side.is_left() { - IconName::PanelLeftClose - } else { - IconName::PanelRightClose - }; - - self.btn - .when_some(on_click, |this, on_click| { - this.on_click(move |ev, cx| { - on_click(ev, cx); - }) - }) - .icon(Icon::new(icon).size_4()) - } -} - -impl RenderOnce for Sidebar { - fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { - let is_collaped = self.is_collapsed; - v_flex() - .id("sidebar") - .w(self.width) - .when(self.is_collapsed, |this| this.w(COLLAPSED_WIDTH)) - .flex_shrink_0() - .h_full() - .overflow_hidden() - .relative() - .bg(cx.theme().sidebar) - .text_color(cx.theme().sidebar_foreground) - .border_color(cx.theme().sidebar_border) - .map(|this| match self.side { - Side::Left => this.border_r_1(), - Side::Right => this.text_2xl(), - }) - .when_some(self.header.take(), |this, header| { - this.child(h_flex().id("header").p_2().gap_2().child(header)) - }) - .child( - v_flex().id("content").flex_1().min_h_0().child( - div() - .children(self.content.into_iter().map(|c| c.collapsed(is_collaped))) - .gap_2() - .scrollable(self.view_id, ScrollbarAxis::Vertical), - ), - ) - .when_some(self.footer.take(), |this, footer| { - this.child(h_flex().id("footer").gap_2().p_2().child(footer)) - }) - } -} diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index b7448cb..e854699 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -1,13 +1,10 @@ -use gpui::{ - div, px, Axis, Div, Element, ElementId, EntityId, FocusHandle, Pixels, Styled, WindowContext, -}; -use serde::{Deserialize, Serialize}; -use std::fmt::{self, Display, Formatter}; - use crate::{ scroll::{Scrollable, ScrollbarAxis}, theme::ActiveTheme, }; +use gpui::{div, px, Axis, Div, Element, ElementId, EntityId, Pixels, Styled, WindowContext}; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; /// Returns a `Div` as horizontal flex layout. pub fn h_flex() -> Div { @@ -40,64 +37,6 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } - /// Render a border with a width of 1px, color red - fn debug_red(self) -> Self { - if cfg!(debug_assertions) { - self.border_1().border_color(crate::red_500()) - } else { - self - } - } - - /// Render a border with a width of 1px, color blue - fn debug_blue(self) -> Self { - if cfg!(debug_assertions) { - self.border_1().border_color(crate::blue_500()) - } else { - self - } - } - - /// Render a border with a width of 1px, color yellow - fn debug_yellow(self) -> Self { - if cfg!(debug_assertions) { - self.border_1().border_color(crate::yellow_500()) - } else { - self - } - } - - /// Render a border with a width of 1px, color green - fn debug_green(self) -> Self { - if cfg!(debug_assertions) { - self.border_1().border_color(crate::green_500()) - } else { - self - } - } - - /// Render a border with a width of 1px, color pink - fn debug_pink(self) -> Self { - if cfg!(debug_assertions) { - self.border_1().border_color(crate::pink_500()) - } else { - self - } - } - - /// Render a 1px blue border, when if the element is focused - fn debug_focused(self, focus_handle: &FocusHandle, cx: &WindowContext) -> Self { - if cfg!(debug_assertions) { - if focus_handle.contains_focused(cx) { - self.debug_blue() - } else { - self - } - } else { - self - } - } - /// Render a border with a width of 1px, color ring color fn outline(self, cx: &WindowContext) -> Self { self.border_color(cx.theme().ring) diff --git a/crates/ui/src/tab.rs b/crates/ui/src/tab.rs deleted file mode 100644 index bff7e60..0000000 --- a/crates/ui/src/tab.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[allow(clippy::module_inception)] -mod tab; -mod tab_bar; - -pub use tab::*; -pub use tab_bar::*; diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/mod.rs similarity index 99% rename from crates/ui/src/tab/tab.rs rename to crates/ui/src/tab/mod.rs index 5433937..38a429c 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/mod.rs @@ -4,6 +4,8 @@ use gpui::prelude::FluentBuilder; use gpui::*; use nostr_sdk::prelude::*; +pub mod tab_bar; + #[derive(IntoElement)] pub struct Tab { id: ElementId, diff --git a/crates/ui/src/theme/colors.rs b/crates/ui/src/theme/colors.rs new file mode 100644 index 0000000..24f23f3 --- /dev/null +++ b/crates/ui/src/theme/colors.rs @@ -0,0 +1,2195 @@ +use super::scale::{ColorScaleSet, ColorScales}; +use crate::theme::scale::ColorScale; +use gpui::{hsla, Hsla, Rgba}; + +/// Make a [gpui::Hsla] color. +/// +/// - h: 0..360.0 +/// - s: 0.0..100.0 +/// - l: 0.0..100.0 +pub fn hsl(h: f32, s: f32, l: f32) -> Hsla { + hsla(h / 360., s / 100.0, l / 100.0, 1.0) +} + +type StaticColorScale = [&'static str; 12]; + +struct StaticColorScaleSet { + scale: &'static str, + light: StaticColorScale, + light_alpha: StaticColorScale, + dark: StaticColorScale, + dark_alpha: StaticColorScale, +} + +impl TryFrom for ColorScaleSet { + type Error = anyhow::Error; + + fn try_from(value: StaticColorScaleSet) -> Result { + fn to_color_scale(scale: StaticColorScale) -> Result { + scale + .into_iter() + .map(|color| Rgba::try_from(color).map(Hsla::from)) + .collect::, _>>() + .map(ColorScale::from_iter) + } + + Ok(Self::new( + value.scale, + to_color_scale(value.light)?, + to_color_scale(value.light_alpha)?, + to_color_scale(value.dark)?, + to_color_scale(value.dark_alpha)?, + )) + } +} + +/// Color scales used to build the default themes. +pub fn default_color_scales() -> ColorScales { + ColorScales { + gray: gray(), + mauve: mauve(), + slate: slate(), + sage: sage(), + olive: olive(), + sand: sand(), + gold: gold(), + bronze: bronze(), + brown: brown(), + yellow: yellow(), + amber: amber(), + orange: orange(), + tomato: tomato(), + red: red(), + ruby: ruby(), + crimson: crimson(), + pink: pink(), + plum: plum(), + purple: purple(), + violet: violet(), + iris: iris(), + indigo: indigo(), + blue: blue(), + cyan: cyan(), + teal: teal(), + jade: jade(), + green: green(), + grass: grass(), + lime: lime(), + mint: mint(), + sky: sky(), + black: black(), + white: white(), + } +} + +pub(crate) fn gray() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Gray", + light: [ + "#fcfcfcff", + "#f9f9f9ff", + "#f0f0f0ff", + "#e8e8e8ff", + "#e0e0e0ff", + "#d9d9d9ff", + "#cececeff", + "#bbbbbbff", + "#8d8d8dff", + "#838383ff", + "#646464ff", + "#202020ff", + ], + light_alpha: [ + "#00000003", + "#00000006", + "#0000000f", + "#00000017", + "#0000001f", + "#00000026", + "#00000031", + "#00000044", + "#00000072", + "#0000007c", + "#0000009b", + "#000000df", + ], + dark: [ + "#111111ff", + "#191919ff", + "#222222ff", + "#2a2a2aff", + "#313131ff", + "#3a3a3aff", + "#484848ff", + "#606060ff", + "#6e6e6eff", + "#7b7b7bff", + "#b4b4b4ff", + "#eeeeeeff", + ], + dark_alpha: [ + "#00000000", + "#ffffff09", + "#ffffff12", + "#ffffff1b", + "#ffffff22", + "#ffffff2c", + "#ffffff3b", + "#ffffff55", + "#ffffff64", + "#ffffff72", + "#ffffffaf", + "#ffffffed", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn mauve() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Mauve", + light: [ + "#fdfcfdff", + "#faf9fbff", + "#f2eff3ff", + "#eae7ecff", + "#e3dfe6ff", + "#dbd8e0ff", + "#d0cdd7ff", + "#bcbac7ff", + "#8e8c99ff", + "#84828eff", + "#65636dff", + "#211f26ff", + ], + light_alpha: [ + "#55005503", + "#2b005506", + "#30004010", + "#20003618", + "#20003820", + "#14003527", + "#10003332", + "#08003145", + "#05001d73", + "#0500197d", + "#0400119c", + "#020008e0", + ], + dark: [ + "#121113ff", + "#1a191bff", + "#232225ff", + "#2b292dff", + "#323035ff", + "#3c393fff", + "#49474eff", + "#625f69ff", + "#6f6d78ff", + "#7c7a85ff", + "#b5b2bcff", + "#eeeef0ff", + ], + dark_alpha: [ + "#00000000", + "#f5f4f609", + "#ebeaf814", + "#eee5f81d", + "#efe6fe25", + "#f1e6fd30", + "#eee9ff40", + "#eee7ff5d", + "#eae6fd6e", + "#ece9fd7c", + "#f5f1ffb7", + "#fdfdffef", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn slate() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Slate", + light: [ + "#fcfcfdff", + "#f9f9fbff", + "#f0f0f3ff", + "#e8e8ecff", + "#e0e1e6ff", + "#d9d9e0ff", + "#cdced6ff", + "#b9bbc6ff", + "#8b8d98ff", + "#80838dff", + "#60646cff", + "#1c2024ff", + ], + light_alpha: [ + "#00005503", + "#00005506", + "#0000330f", + "#00002d17", + "#0009321f", + "#00002f26", + "#00062e32", + "#00083046", + "#00051d74", + "#00071b7f", + "#0007149f", + "#000509e3", + ], + dark: [ + "#111113ff", + "#18191bff", + "#212225ff", + "#272a2dff", + "#2e3135ff", + "#363a3fff", + "#43484eff", + "#5a6169ff", + "#696e77ff", + "#777b84ff", + "#b0b4baff", + "#edeef0ff", + ], + dark_alpha: [ + "#00000000", + "#d8f4f609", + "#ddeaf814", + "#d3edf81d", + "#d9edfe25", + "#d6ebfd30", + "#d9edff40", + "#d9edff5d", + "#dfebfd6d", + "#e5edfd7b", + "#f1f7feb5", + "#fcfdffef", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn sage() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Sage", + light: [ + "#fbfdfcff", + "#f7f9f8ff", + "#eef1f0ff", + "#e6e9e8ff", + "#dfe2e0ff", + "#d7dad9ff", + "#cbcfcdff", + "#b8bcbaff", + "#868e8bff", + "#7c8481ff", + "#5f6563ff", + "#1a211eff", + ], + light_alpha: [ + "#00804004", + "#00402008", + "#002d1e11", + "#001f1519", + "#00180820", + "#00140d28", + "#00140a34", + "#000f0847", + "#00110b79", + "#00100a83", + "#000a07a0", + "#000805e5", + ], + dark: [ + "#101211ff", + "#171918ff", + "#202221ff", + "#272a29ff", + "#2e3130ff", + "#373b39ff", + "#444947ff", + "#5b625fff", + "#63706bff", + "#717d79ff", + "#adb5b2ff", + "#eceeedff", + ], + dark_alpha: [ + "#00000000", + "#f0f2f108", + "#f3f5f412", + "#f2fefd1a", + "#f1fbfa22", + "#edfbf42d", + "#edfcf73c", + "#ebfdf657", + "#dffdf266", + "#e5fdf674", + "#f4fefbb0", + "#fdfffeed", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn olive() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Olive", + light: [ + "#fcfdfcff", + "#f8faf8ff", + "#eff1efff", + "#e7e9e7ff", + "#dfe2dfff", + "#d7dad7ff", + "#cccfccff", + "#b9bcb8ff", + "#898e87ff", + "#7f847dff", + "#60655fff", + "#1d211cff", + ], + light_alpha: [ + "#00550003", + "#00490007", + "#00200010", + "#00160018", + "#00180020", + "#00140028", + "#000f0033", + "#040f0047", + "#050f0078", + "#040e0082", + "#020a00a0", + "#010600e3", + ], + dark: [ + "#111210ff", + "#181917ff", + "#212220ff", + "#282a27ff", + "#2f312eff", + "#383a36ff", + "#454843ff", + "#5c625bff", + "#687066ff", + "#767d74ff", + "#afb5adff", + "#eceeecff", + ], + dark_alpha: [ + "#00000000", + "#f1f2f008", + "#f4f5f312", + "#f3fef21a", + "#f2fbf122", + "#f4faed2c", + "#f2fced3b", + "#edfdeb57", + "#ebfde766", + "#f0fdec74", + "#f6fef4b0", + "#fdfffded", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn sand() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Sand", + light: [ + "#fdfdfcff", + "#f9f9f8ff", + "#f1f0efff", + "#e9e8e6ff", + "#e2e1deff", + "#dad9d6ff", + "#cfcecaff", + "#bcbbb5ff", + "#8d8d86ff", + "#82827cff", + "#63635eff", + "#21201cff", + ], + light_alpha: [ + "#55550003", + "#25250007", + "#20100010", + "#1f150019", + "#1f180021", + "#19130029", + "#19140035", + "#1915014a", + "#0f0f0079", + "#0c0c0083", + "#080800a1", + "#060500e3", + ], + dark: [ + "#111110ff", + "#191918ff", + "#222221ff", + "#2a2a28ff", + "#31312eff", + "#3b3a37ff", + "#494844ff", + "#62605bff", + "#6f6d66ff", + "#7c7b74ff", + "#b5b3adff", + "#eeeeecff", + ], + dark_alpha: [ + "#00000000", + "#f4f4f309", + "#f6f6f513", + "#fefef31b", + "#fbfbeb23", + "#fffaed2d", + "#fffbed3c", + "#fff9eb57", + "#fffae965", + "#fffdee73", + "#fffcf4b0", + "#fffffded", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn gold() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Gold", + light: [ + "#fdfdfcff", + "#faf9f2ff", + "#f2f0e7ff", + "#eae6dbff", + "#e1dccfff", + "#d8d0bfff", + "#cbc0aaff", + "#b9a88dff", + "#978365ff", + "#8c7a5eff", + "#71624bff", + "#3b352bff", + ], + light_alpha: [ + "#55550003", + "#9d8a000d", + "#75600018", + "#6b4e0024", + "#60460030", + "#64440040", + "#63420055", + "#633d0072", + "#5332009a", + "#492d00a1", + "#362100b4", + "#130c00d4", + ], + dark: [ + "#121211ff", + "#1b1a17ff", + "#24231fff", + "#2d2b26ff", + "#38352eff", + "#444039ff", + "#544f46ff", + "#696256ff", + "#978365ff", + "#a39073ff", + "#cbb99fff", + "#e8e2d9ff", + ], + dark_alpha: [ + "#91911102", + "#f9e29d0b", + "#f8ecbb15", + "#ffeec41e", + "#feecc22a", + "#feebcb37", + "#ffedcd48", + "#fdeaca5f", + "#ffdba690", + "#fedfb09d", + "#fee7c6c8", + "#fef7ede7", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn bronze() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Bronze", + light: [ + "#fdfcfcff", + "#fdf7f5ff", + "#f6edeaff", + "#efe4dfff", + "#e7d9d3ff", + "#dfcdc5ff", + "#d3bcb3ff", + "#c2a499ff", + "#a18072ff", + "#957468ff", + "#7d5e54ff", + "#43302bff", + ], + light_alpha: [ + "#55000003", + "#cc33000a", + "#92250015", + "#80280020", + "#7423002c", + "#7324003a", + "#6c1f004c", + "#671c0066", + "#551a008d", + "#4c150097", + "#3d0f00ab", + "#1d0600d4", + ], + dark: [ + "#141110ff", + "#1c1917ff", + "#262220ff", + "#302a27ff", + "#3b3330ff", + "#493e3aff", + "#5a4c47ff", + "#6f5f58ff", + "#a18072ff", + "#ae8c7eff", + "#d4b3a5ff", + "#ede0d9ff", + ], + dark_alpha: [ + "#d1110004", + "#fbbc910c", + "#faceb817", + "#facdb622", + "#ffd2c12d", + "#ffd1c03c", + "#fdd0c04f", + "#ffd6c565", + "#fec7b09b", + "#fecab5a9", + "#ffd7c6d1", + "#fff1e9ec", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn brown() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Brown", + light: [ + "#fefdfcff", + "#fcf9f6ff", + "#f6eee7ff", + "#f0e4d9ff", + "#ebdacaff", + "#e4cdb7ff", + "#dcbc9fff", + "#cea37eff", + "#ad7f58ff", + "#a07553ff", + "#815e46ff", + "#3e332eff", + ], + light_alpha: [ + "#aa550003", + "#aa550009", + "#a04b0018", + "#9b4a0026", + "#9f4d0035", + "#a04e0048", + "#a34e0060", + "#9f4a0081", + "#823c00a7", + "#723300ac", + "#522100b9", + "#140600d1", + ], + dark: [ + "#12110fff", + "#1c1816ff", + "#28211dff", + "#322922ff", + "#3e3128ff", + "#4d3c2fff", + "#614a39ff", + "#7c5f46ff", + "#ad7f58ff", + "#b88c67ff", + "#dbb594ff", + "#f2e1caff", + ], + dark_alpha: [ + "#91110002", + "#fba67c0c", + "#fcb58c19", + "#fbbb8a24", + "#fcb88931", + "#fdba8741", + "#ffbb8856", + "#ffbe8773", + "#feb87da8", + "#ffc18cb3", + "#fed1aad9", + "#feecd4f2", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn yellow() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Yellow", + light: [ + "#fdfdf9ff", + "#fefce9ff", + "#fffab8ff", + "#fff394ff", + "#ffe770ff", + "#f3d768ff", + "#e4c767ff", + "#d5ae39ff", + "#ffe629ff", + "#ffdc00ff", + "#9e6c00ff", + "#473b1fff", + ], + light_alpha: [ + "#aaaa0006", + "#f4dd0016", + "#ffee0047", + "#ffe3016b", + "#ffd5008f", + "#ebbc0097", + "#d2a10098", + "#c99700c6", + "#ffe100d6", + "#ffdc00ff", + "#9e6c00ff", + "#2e2000e0", + ], + dark: [ + "#14120bff", + "#1b180fff", + "#2d2305ff", + "#362b00ff", + "#433500ff", + "#524202ff", + "#665417ff", + "#836a21ff", + "#ffe629ff", + "#ffff57ff", + "#f5e147ff", + "#f6eeb4ff", + ], + dark_alpha: [ + "#d1510004", + "#f9b4000b", + "#ffaa001e", + "#fdb70028", + "#febb0036", + "#fec40046", + "#fdcb225c", + "#fdca327b", + "#ffe629ff", + "#ffff57ff", + "#fee949f5", + "#fef6baf6", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn amber() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Amber", + light: [ + "#fefdfbff", + "#fefbe9ff", + "#fff7c2ff", + "#ffee9cff", + "#fbe577ff", + "#f3d673ff", + "#e9c162ff", + "#e2a336ff", + "#ffc53dff", + "#ffba18ff", + "#ab6400ff", + "#4f3422ff", + ], + light_alpha: [ + "#c0800004", + "#f4d10016", + "#ffde003d", + "#ffd40063", + "#f8cf0088", + "#eab5008c", + "#dc9b009d", + "#da8a00c9", + "#ffb300c2", + "#ffb300e7", + "#ab6400ff", + "#341500dd", + ], + dark: [ + "#16120cff", + "#1d180fff", + "#302008ff", + "#3f2700ff", + "#4d3000ff", + "#5c3d05ff", + "#714f19ff", + "#8f6424ff", + "#ffc53dff", + "#ffd60aff", + "#ffca16ff", + "#ffe7b3ff", + ], + dark_alpha: [ + "#e63c0006", + "#fd9b000d", + "#fa820022", + "#fc820032", + "#fd8b0041", + "#fd9b0051", + "#ffab2567", + "#ffae3587", + "#ffc53dff", + "#ffd60aff", + "#ffca16ff", + "#ffe7b3ff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn orange() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Orange", + light: [ + "#fefcfbff", + "#fff7edff", + "#ffefd6ff", + "#ffdfb5ff", + "#ffd19aff", + "#ffc182ff", + "#f5ae73ff", + "#ec9455ff", + "#f76b15ff", + "#ef5f00ff", + "#cc4e00ff", + "#582d1dff", + ], + light_alpha: [ + "#c0400004", + "#ff8e0012", + "#ff9c0029", + "#ff91014a", + "#ff8b0065", + "#ff81007d", + "#ed6c008c", + "#e35f00aa", + "#f65e00ea", + "#ef5f00ff", + "#cc4e00ff", + "#431200e2", + ], + dark: [ + "#17120eff", + "#1e160fff", + "#331e0bff", + "#462100ff", + "#562800ff", + "#66350cff", + "#7e451dff", + "#a35829ff", + "#f76b15ff", + "#ff801fff", + "#ffa057ff", + "#ffe0c2ff", + ], + dark_alpha: [ + "#ec360007", + "#fe6d000e", + "#fb6a0025", + "#ff590039", + "#ff61004a", + "#fd75045c", + "#ff832c75", + "#fe84389d", + "#fe6d15f7", + "#ff801fff", + "#ffa057ff", + "#ffe0c2ff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn tomato() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Tomato", + light: [ + "#fffcfcff", + "#fff8f7ff", + "#feebe7ff", + "#ffdcd3ff", + "#ffcdc2ff", + "#fdbdafff", + "#f5a898ff", + "#ec8e7bff", + "#e54d2eff", + "#dd4425ff", + "#d13415ff", + "#5c271fff", + ], + light_alpha: [ + "#ff000003", + "#ff200008", + "#f52b0018", + "#ff35002c", + "#ff2e003d", + "#f92d0050", + "#e7280067", + "#db250084", + "#df2600d1", + "#d72400da", + "#cd2200ea", + "#460900e0", + ], + dark: [ + "#181111ff", + "#1f1513ff", + "#391714ff", + "#4e1511ff", + "#5e1c16ff", + "#6e2920ff", + "#853a2dff", + "#ac4d39ff", + "#e54d2eff", + "#ec6142ff", + "#ff977dff", + "#fbd3cbff", + ], + dark_alpha: [ + "#f1121208", + "#ff55330f", + "#ff35232b", + "#fd201142", + "#fe332153", + "#ff4f3864", + "#fd644a7d", + "#fe6d4ea7", + "#fe5431e4", + "#ff6847eb", + "#ff977dff", + "#ffd6cefb", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn red() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Red", + light: [ + "#fffcfcff", + "#fff7f7ff", + "#feebecff", + "#ffdbdcff", + "#ffcdceff", + "#fdbdbeff", + "#f4a9aaff", + "#eb8e90ff", + "#e5484dff", + "#dc3e42ff", + "#ce2c31ff", + "#641723ff", + ], + light_alpha: [ + "#ff000003", + "#ff000008", + "#f3000d14", + "#ff000824", + "#ff000632", + "#f8000442", + "#df000356", + "#d2000571", + "#db0007b7", + "#d10005c1", + "#c40006d3", + "#55000de8", + ], + dark: [ + "#191111ff", + "#201314ff", + "#3b1219ff", + "#500f1cff", + "#611623ff", + "#72232dff", + "#8c333aff", + "#b54548ff", + "#e5484dff", + "#ec5d5eff", + "#ff9592ff", + "#ffd1d9ff", + ], + dark_alpha: [ + "#f4121209", + "#f22f3e11", + "#ff173f2d", + "#fe0a3b44", + "#ff204756", + "#ff3e5668", + "#ff536184", + "#ff5d61b0", + "#fe4e54e4", + "#ff6465eb", + "#ff9592ff", + "#ffd1d9ff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn ruby() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Ruby", + light: [ + "#fffcfdff", + "#fff7f8ff", + "#feeaedff", + "#ffdce1ff", + "#ffced6ff", + "#f8bfc8ff", + "#efacb8ff", + "#e592a3ff", + "#e54666ff", + "#dc3b5dff", + "#ca244dff", + "#64172bff", + ], + light_alpha: [ + "#ff005503", + "#ff002008", + "#f3002515", + "#ff002523", + "#ff002a31", + "#e4002440", + "#ce002553", + "#c300286d", + "#db002cb9", + "#d2002cc4", + "#c10030db", + "#550016e8", + ], + dark: [ + "#191113ff", + "#1e1517ff", + "#3a141eff", + "#4e1325ff", + "#5e1a2eff", + "#6f2539ff", + "#883447ff", + "#b3445aff", + "#e54666ff", + "#ec5a72ff", + "#ff949dff", + "#fed2e1ff", + ], + dark_alpha: [ + "#f4124a09", + "#fe5a7f0e", + "#ff235d2c", + "#fd195e42", + "#fe2d6b53", + "#ff447665", + "#ff577d80", + "#ff5c7cae", + "#fe4c70e4", + "#ff617beb", + "#ff949dff", + "#ffd3e2fe", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn crimson() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Crimson", + light: [ + "#fffcfdff", + "#fef7f9ff", + "#ffe9f0ff", + "#fedce7ff", + "#faceddff", + "#f3bed1ff", + "#eaacc3ff", + "#e093b2ff", + "#e93d82ff", + "#df3478ff", + "#cb1d63ff", + "#621639ff", + ], + light_alpha: [ + "#ff005503", + "#e0004008", + "#ff005216", + "#f8005123", + "#e5004f31", + "#d0004b41", + "#bf004753", + "#b6004a6c", + "#e2005bc2", + "#d70056cb", + "#c4004fe2", + "#530026e9", + ], + dark: [ + "#191114ff", + "#201318ff", + "#381525ff", + "#4d122fff", + "#5c1839ff", + "#6d2545ff", + "#873356ff", + "#b0436eff", + "#e93d82ff", + "#ee518aff", + "#ff92adff", + "#fdd3e8ff", + ], + dark_alpha: [ + "#f4126709", + "#f22f7a11", + "#fe2a8b2a", + "#fd158741", + "#fd278f51", + "#fe459763", + "#fd559b7f", + "#fe5b9bab", + "#fe418de8", + "#ff5693ed", + "#ff92adff", + "#ffd5eafd", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn pink() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Pink", + light: [ + "#fffcfeff", + "#fef7fbff", + "#fee9f5ff", + "#fbdcefff", + "#f6cee7ff", + "#efbfddff", + "#e7acd0ff", + "#dd93c2ff", + "#d6409fff", + "#cf3897ff", + "#c2298aff", + "#651249ff", + ], + light_alpha: [ + "#ff00aa03", + "#e0008008", + "#f4008c16", + "#e2008b23", + "#d1008331", + "#c0007840", + "#b6006f53", + "#af006f6c", + "#c8007fbf", + "#c2007ac7", + "#b60074d6", + "#59003bed", + ], + dark: [ + "#191117ff", + "#21121dff", + "#37172fff", + "#4b143dff", + "#591c47ff", + "#692955ff", + "#833869ff", + "#a84885ff", + "#d6409fff", + "#de51a8ff", + "#ff8dccff", + "#fdd1eaff", + ], + dark_alpha: [ + "#f412bc09", + "#f420bb12", + "#fe37cc29", + "#fc1ec43f", + "#fd35c24e", + "#fd51c75f", + "#fd62c87b", + "#ff68c8a2", + "#fe49bcd4", + "#ff5cc0dc", + "#ff8dccff", + "#ffd3ecfd", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn plum() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Plum", + light: [ + "#fefcffff", + "#fdf7fdff", + "#fbebfbff", + "#f7def8ff", + "#f2d1f3ff", + "#e9c2ecff", + "#deade3ff", + "#cf91d8ff", + "#ab4abaff", + "#a144afff", + "#953ea3ff", + "#53195dff", + ], + light_alpha: [ + "#aa00ff03", + "#c000c008", + "#cc00cc14", + "#c200c921", + "#b700bd2e", + "#a400b03d", + "#9900a852", + "#9000a56e", + "#89009eb5", + "#7f0092bb", + "#730086c1", + "#40004be6", + ], + dark: [ + "#181118ff", + "#201320ff", + "#351a35ff", + "#451d47ff", + "#512454ff", + "#5e3061ff", + "#734079ff", + "#92549cff", + "#ab4abaff", + "#b658c4ff", + "#e796f3ff", + "#f4d4f4ff", + ], + dark_alpha: [ + "#f112f108", + "#f22ff211", + "#fd4cfd27", + "#f646ff3a", + "#f455ff48", + "#f66dff56", + "#f07cfd70", + "#ee84ff95", + "#e961feb6", + "#ed70ffc0", + "#f19cfef3", + "#feddfef4", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn purple() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Purple", + light: [ + "#fefcfeff", + "#fbf7feff", + "#f7edfeff", + "#f2e2fcff", + "#ead5f9ff", + "#e0c4f4ff", + "#d1afecff", + "#be93e4ff", + "#8e4ec6ff", + "#8347b9ff", + "#8145b5ff", + "#402060ff", + ], + light_alpha: [ + "#aa00aa03", + "#8000e008", + "#8e00f112", + "#8d00e51d", + "#8000db2a", + "#7a01d03b", + "#6d00c350", + "#6600c06c", + "#5c00adb1", + "#53009eb8", + "#52009aba", + "#250049df", + ], + dark: [ + "#18111bff", + "#1e1523ff", + "#301c3bff", + "#3d224eff", + "#48295cff", + "#54346bff", + "#664282ff", + "#8457aaff", + "#8e4ec6ff", + "#9a5cd0ff", + "#d19dffff", + "#ecd9faff", + ], + dark_alpha: [ + "#b412f90b", + "#b744f714", + "#c150ff2d", + "#bb53fd42", + "#be5cfd51", + "#c16dfd61", + "#c378fd7a", + "#c47effa4", + "#b661ffc2", + "#bc6fffcd", + "#d19dffff", + "#f1ddfffa", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn violet() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Violet", + light: [ + "#fdfcfeff", + "#faf8ffff", + "#f4f0feff", + "#ebe4ffff", + "#e1d9ffff", + "#d4cafeff", + "#c2b5f5ff", + "#aa99ecff", + "#6e56cfff", + "#654dc4ff", + "#6550b9ff", + "#2f265fff", + ], + light_alpha: [ + "#5500aa03", + "#4900ff07", + "#4400ee0f", + "#4300ff1b", + "#3600ff26", + "#3100fb35", + "#2d01dd4a", + "#2b00d066", + "#2400b7a9", + "#2300abb2", + "#1f0099af", + "#0b0043d9", + ], + dark: [ + "#14121fff", + "#1b1525ff", + "#291f43ff", + "#33255bff", + "#3c2e69ff", + "#473876ff", + "#56468bff", + "#6958adff", + "#6e56cfff", + "#7d66d9ff", + "#baa7ffff", + "#e2ddfeff", + ], + dark_alpha: [ + "#4422ff0f", + "#853ff916", + "#8354fe36", + "#7d51fd50", + "#845ffd5f", + "#8f6cfd6d", + "#9879ff83", + "#977dfea8", + "#8668ffcc", + "#9176fed7", + "#baa7ffff", + "#e3defffe", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn iris() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Iris", + light: [ + "#fdfdffff", + "#f8f8ffff", + "#f0f1feff", + "#e6e7ffff", + "#dadcffff", + "#cbcdffff", + "#b8baf8ff", + "#9b9ef0ff", + "#5b5bd6ff", + "#5151cdff", + "#5753c6ff", + "#272962ff", + ], + light_alpha: [ + "#0000ff02", + "#0000ff07", + "#0011ee0f", + "#000bff19", + "#000eff25", + "#000aff34", + "#0008e647", + "#0008d964", + "#0000c0a4", + "#0000b6ae", + "#0600abac", + "#000246d8", + ], + dark: [ + "#13131eff", + "#171625ff", + "#202248ff", + "#262a65ff", + "#303374ff", + "#3d3e82ff", + "#4a4a95ff", + "#5958b1ff", + "#5b5bd6ff", + "#6e6adeff", + "#b1a9ffff", + "#e0dffeff", + ], + dark_alpha: [ + "#3636fe0e", + "#564bf916", + "#525bff3b", + "#4d58ff5a", + "#5b62fd6b", + "#6d6ffd7a", + "#7777fe8e", + "#7b7afeac", + "#6a6afed4", + "#7d79ffdc", + "#b1a9ffff", + "#e1e0fffe", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn indigo() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Indigo", + light: [ + "#fdfdfeff", + "#f7f9ffff", + "#edf2feff", + "#e1e9ffff", + "#d2deffff", + "#c1d0ffff", + "#abbdf9ff", + "#8da4efff", + "#3e63ddff", + "#3358d4ff", + "#3a5bc7ff", + "#1f2d5cff", + ], + light_alpha: [ + "#00008002", + "#0040ff08", + "#0047f112", + "#0044ff1e", + "#0044ff2d", + "#003eff3e", + "#0037ed54", + "#0034dc72", + "#0031d2c1", + "#002ec9cc", + "#002bb7c5", + "#001046e0", + ], + dark: [ + "#11131fff", + "#141726ff", + "#182449ff", + "#1d2e62ff", + "#253974ff", + "#304384ff", + "#3a4f97ff", + "#435db1ff", + "#3e63ddff", + "#5472e4ff", + "#9eb1ffff", + "#d6e1ffff", + ], + dark_alpha: [ + "#1133ff0f", + "#3354fa17", + "#2f62ff3c", + "#3566ff57", + "#4171fd6b", + "#5178fd7c", + "#5a7fff90", + "#5b81feac", + "#4671ffdb", + "#5c7efee3", + "#9eb1ffff", + "#d6e1ffff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn blue() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Blue", + light: [ + "#fbfdffff", + "#f4faffff", + "#e6f4feff", + "#d5efffff", + "#c2e5ffff", + "#acd8fcff", + "#8ec8f6ff", + "#5eb1efff", + "#0090ffff", + "#0588f0ff", + "#0d74ceff", + "#113264ff", + ], + light_alpha: [ + "#0080ff04", + "#008cff0b", + "#008ff519", + "#009eff2a", + "#0093ff3d", + "#0088f653", + "#0083eb71", + "#0084e6a1", + "#0090ffff", + "#0086f0fa", + "#006dcbf2", + "#002359ee", + ], + dark: [ + "#0d1520ff", + "#111927ff", + "#0d2847ff", + "#003362ff", + "#004074ff", + "#104d87ff", + "#205d9eff", + "#2870bdff", + "#0090ffff", + "#3b9effff", + "#70b8ffff", + "#c2e6ffff", + ], + dark_alpha: [ + "#004df211", + "#1166fb18", + "#0077ff3a", + "#0075ff57", + "#0081fd6b", + "#0f89fd7f", + "#2a91fe98", + "#3094feb9", + "#0090ffff", + "#3b9effff", + "#70b8ffff", + "#c2e6ffff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn cyan() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Cyan", + light: [ + "#fafdfeff", + "#f2fafbff", + "#def7f9ff", + "#caf1f6ff", + "#b5e9f0ff", + "#9ddde7ff", + "#7dcedcff", + "#3db9cfff", + "#00a2c7ff", + "#0797b9ff", + "#107d98ff", + "#0d3c48ff", + ], + light_alpha: [ + "#0099cc05", + "#009db10d", + "#00c2d121", + "#00bcd435", + "#01b4cc4a", + "#00a7c162", + "#009fbb82", + "#00a3c0c2", + "#00a2c7ff", + "#0094b7f8", + "#007491ef", + "#00323ef2", + ], + dark: [ + "#0b161aff", + "#101b20ff", + "#082c36ff", + "#003848ff", + "#004558ff", + "#045468ff", + "#12677eff", + "#11809cff", + "#00a2c7ff", + "#23afd0ff", + "#4ccce6ff", + "#b6ecf7ff", + ], + dark_alpha: [ + "#0091f70a", + "#02a7f211", + "#00befd28", + "#00baff3b", + "#00befd4d", + "#00c7fd5e", + "#14cdff75", + "#11cfff95", + "#00cfffc3", + "#28d6ffcd", + "#52e1fee5", + "#bbf3fef7", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn teal() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Teal", + light: [ + "#fafefdff", + "#f3fbf9ff", + "#e0f8f3ff", + "#ccf3eaff", + "#b8eae0ff", + "#a1ded2ff", + "#83cdc1ff", + "#53b9abff", + "#12a594ff", + "#0d9b8aff", + "#008573ff", + "#0d3d38ff", + ], + light_alpha: [ + "#00cc9905", + "#00aa800c", + "#00c69d1f", + "#00c39633", + "#00b49047", + "#00a6855e", + "#0099807c", + "#009783ac", + "#009e8ced", + "#009684f2", + "#008573ff", + "#00332df2", + ], + dark: [ + "#0d1514ff", + "#111c1bff", + "#0d2d2aff", + "#023b37ff", + "#084843ff", + "#145750ff", + "#1c6961ff", + "#207e73ff", + "#12a594ff", + "#0eb39eff", + "#0bd8b6ff", + "#adf0ddff", + ], + dark_alpha: [ + "#00deab05", + "#12fbe60c", + "#00ffe61e", + "#00ffe92d", + "#00ffea3b", + "#1cffe84b", + "#2efde85f", + "#32ffe775", + "#13ffe49f", + "#0dffe0ae", + "#0afed5d6", + "#b8ffebef", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn jade() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Jade", + light: [ + "#fbfefdff", + "#f4fbf7ff", + "#e6f7edff", + "#d6f1e3ff", + "#c3e9d7ff", + "#acdec8ff", + "#8bceb6ff", + "#56ba9fff", + "#29a383ff", + "#26997bff", + "#208368ff", + "#1d3b31ff", + ], + light_alpha: [ + "#00c08004", + "#00a3460b", + "#00ae4819", + "#00a85129", + "#00a2553c", + "#009a5753", + "#00945f74", + "#00976ea9", + "#00916bd6", + "#008764d9", + "#007152df", + "#002217e2", + ], + dark: [ + "#0d1512ff", + "#121c18ff", + "#0f2e22ff", + "#0b3b2cff", + "#114837ff", + "#1b5745ff", + "#246854ff", + "#2a7e68ff", + "#29a383ff", + "#27b08bff", + "#1fd8a4ff", + "#adf0d4ff", + ], + dark_alpha: [ + "#00de4505", + "#27fba60c", + "#02f99920", + "#00ffaa2d", + "#11ffb63b", + "#34ffc24b", + "#45fdc75e", + "#48ffcf75", + "#38feca9d", + "#31fec7ab", + "#21fec0d6", + "#b8ffe1ef", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn green() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Green", + light: [ + "#fbfefcff", + "#f4fbf6ff", + "#e6f6ebff", + "#d6f1dfff", + "#c4e8d1ff", + "#adddc0ff", + "#8eceaaff", + "#5bb98bff", + "#30a46cff", + "#2b9a66ff", + "#218358ff", + "#193b2dff", + ], + light_alpha: [ + "#00c04004", + "#00a32f0b", + "#00a43319", + "#00a83829", + "#019c393b", + "#00963c52", + "#00914071", + "#00924ba4", + "#008f4acf", + "#008647d4", + "#00713fde", + "#002616e6", + ], + dark: [ + "#0e1512ff", + "#121b17ff", + "#132d21ff", + "#113b29ff", + "#174933ff", + "#20573eff", + "#28684aff", + "#2f7c57ff", + "#30a46cff", + "#33b074ff", + "#3dd68cff", + "#b1f1cbff", + ], + dark_alpha: [ + "#00de4505", + "#29f99d0b", + "#22ff991e", + "#11ff992d", + "#2bffa23c", + "#44ffaa4b", + "#50fdac5e", + "#54ffad73", + "#44ffa49e", + "#43fea4ab", + "#46fea5d4", + "#bbffd7f0", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn grass() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Grass", + light: [ + "#fbfefbff", + "#f5fbf5ff", + "#e9f6e9ff", + "#daf1dbff", + "#c9e8caff", + "#b2ddb5ff", + "#94ce9aff", + "#65ba74ff", + "#46a758ff", + "#3e9b4fff", + "#2a7e3bff", + "#203c25ff", + ], + light_alpha: [ + "#00c00004", + "#0099000a", + "#00970016", + "#009f0725", + "#00930536", + "#008f0a4d", + "#018b0f6b", + "#008d199a", + "#008619b9", + "#007b17c1", + "#006514d5", + "#002006df", + ], + dark: [ + "#0e1511ff", + "#141a15ff", + "#1b2a1eff", + "#1d3a24ff", + "#25482dff", + "#2d5736ff", + "#366740ff", + "#3e7949ff", + "#46a758ff", + "#53b365ff", + "#71d083ff", + "#c2f0c2ff", + ], + dark_alpha: [ + "#00de1205", + "#5ef7780a", + "#70fe8c1b", + "#57ff802c", + "#68ff8b3b", + "#71ff8f4b", + "#77fd925d", + "#77fd9070", + "#65ff82a1", + "#72ff8dae", + "#89ff9fcd", + "#ceffceef", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn lime() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Lime", + light: [ + "#fcfdfaff", + "#f8faf3ff", + "#eef6d6ff", + "#e2f0bdff", + "#d3e7a6ff", + "#c2da91ff", + "#abc978ff", + "#8db654ff", + "#bdee63ff", + "#b0e64cff", + "#5c7c2fff", + "#37401cff", + ], + light_alpha: [ + "#66990005", + "#6b95000c", + "#96c80029", + "#8fc60042", + "#81bb0059", + "#72aa006e", + "#61990087", + "#559200ab", + "#93e4009c", + "#8fdc00b3", + "#375f00d0", + "#1e2900e3", + ], + dark: [ + "#11130cff", + "#151a10ff", + "#1f2917ff", + "#29371dff", + "#334423ff", + "#3d522aff", + "#496231ff", + "#577538ff", + "#bdee63ff", + "#d4ff70ff", + "#bde56cff", + "#e3f7baff", + ], + dark_alpha: [ + "#11bb0003", + "#78f7000a", + "#9bfd4c1a", + "#a7fe5c29", + "#affe6537", + "#b2fe6d46", + "#b6ff6f57", + "#b6fd6d6c", + "#caff69ed", + "#d4ff70ff", + "#d1fe77e4", + "#e9febff7", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn mint() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Mint", + light: [ + "#f9fefdff", + "#f2fbf9ff", + "#ddf9f2ff", + "#c8f4e9ff", + "#b3ecdeff", + "#9ce0d0ff", + "#7ecfbdff", + "#4cbba5ff", + "#86ead4ff", + "#7de0cbff", + "#027864ff", + "#16433cff", + ], + light_alpha: [ + "#00d5aa06", + "#00b18a0d", + "#00d29e22", + "#00cc9937", + "#00c0914c", + "#00b08663", + "#00a17d81", + "#009e7fb3", + "#00d3a579", + "#00c39982", + "#007763fd", + "#00312ae9", + ], + dark: [ + "#0e1515ff", + "#0f1b1bff", + "#092c2bff", + "#003a38ff", + "#004744ff", + "#105650ff", + "#1e685fff", + "#277f70ff", + "#86ead4ff", + "#a8f5e5ff", + "#58d5baff", + "#c4f5e1ff", + ], + dark_alpha: [ + "#00dede05", + "#00f9f90b", + "#00fff61d", + "#00fff42c", + "#00fff23a", + "#0effeb4a", + "#34fde55e", + "#41ffdf76", + "#92ffe7e9", + "#aefeedf5", + "#67ffded2", + "#cbfee9f5", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn sky() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Sky", + light: [ + "#f9feffff", + "#f1fafdff", + "#e1f6fdff", + "#d1f0faff", + "#bee7f5ff", + "#a9daedff", + "#8dcae3ff", + "#60b3d7ff", + "#7ce2feff", + "#74daf8ff", + "#00749eff", + "#1d3e56ff", + ], + light_alpha: [ + "#00d5ff06", + "#00a4db0e", + "#00b3ee1e", + "#00ace42e", + "#00a1d841", + "#0092ca56", + "#0089c172", + "#0085bf9f", + "#00c7fe83", + "#00bcf38b", + "#00749eff", + "#002540e2", + ], + dark: [ + "#0d141fff", + "#111a27ff", + "#112840ff", + "#113555ff", + "#154467ff", + "#1b537bff", + "#1f6692ff", + "#197caeff", + "#7ce2feff", + "#a8eeffff", + "#75c7f0ff", + "#c2f3ffff", + ], + dark_alpha: [ + "#0044ff0f", + "#1171fb18", + "#1184fc33", + "#128fff49", + "#1c9dfd5d", + "#28a5ff72", + "#2badfe8b", + "#1db2fea9", + "#7ce3fffe", + "#a8eeffff", + "#7cd3ffef", + "#c2f3ffff", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn black() -> ColorScaleSet { + StaticColorScaleSet { + scale: "Black", + light: [ + "#0000000d", + "#0000001a", + "#00000026", + "#00000033", + "#0000004d", + "#00000066", + "#00000080", + "#00000099", + "#000000b3", + "#000000cc", + "#000000e6", + "#000000f2", + ], + light_alpha: [ + "#0000000d", + "#0000001a", + "#00000026", + "#00000033", + "#0000004d", + "#00000066", + "#00000080", + "#00000099", + "#000000b3", + "#000000cc", + "#000000e6", + "#000000f2", + ], + dark: [ + "#0000000d", + "#0000001a", + "#00000026", + "#00000033", + "#0000004d", + "#00000066", + "#00000080", + "#00000099", + "#000000b3", + "#000000cc", + "#000000e6", + "#000000f2", + ], + dark_alpha: [ + "#0000000d", + "#0000001a", + "#00000026", + "#00000033", + "#0000004d", + "#00000066", + "#00000080", + "#00000099", + "#000000b3", + "#000000cc", + "#000000e6", + "#000000f2", + ], + } + .try_into() + .unwrap() +} + +pub(crate) fn white() -> ColorScaleSet { + StaticColorScaleSet { + scale: "White", + light: [ + "#ffffff0d", + "#ffffff1a", + "#ffffff26", + "#ffffff33", + "#ffffff4d", + "#ffffff66", + "#ffffff80", + "#ffffff99", + "#ffffffb3", + "#ffffffcc", + "#ffffffe6", + "#fffffff2", + ], + light_alpha: [ + "#ffffff0d", + "#ffffff1a", + "#ffffff26", + "#ffffff33", + "#ffffff4d", + "#ffffff66", + "#ffffff80", + "#ffffff99", + "#ffffffb3", + "#ffffffcc", + "#ffffffe6", + "#fffffff2", + ], + dark: [ + "#ffffff0d", + "#ffffff1a", + "#ffffff26", + "#ffffff33", + "#ffffff4d", + "#ffffff66", + "#ffffff80", + "#ffffff99", + "#ffffffb3", + "#ffffffcc", + "#ffffffe6", + "#fffffff2", + ], + dark_alpha: [ + "#ffffff0d", + "#ffffff1a", + "#ffffff26", + "#ffffff33", + "#ffffff4d", + "#ffffff66", + "#ffffff80", + "#ffffff99", + "#ffffffb3", + "#ffffffcc", + "#ffffffe6", + "#fffffff2", + ], + } + .try_into() + .unwrap() +} diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme/mod.rs similarity index 77% rename from crates/ui/src/theme.rs rename to crates/ui/src/theme/mod.rs index a21d8b4..5c4eee9 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme/mod.rs @@ -1,73 +1,186 @@ +use crate::scroll::ScrollbarShow; +use colors::hsl; use gpui::{ - hsla, point, AppContext, BoxShadow, Global, Hsla, ModelContext, Pixels, SharedString, + blue, hsla, transparent_black, AppContext, Global, Hsla, ModelContext, SharedString, ViewContext, WindowAppearance, WindowContext, }; use std::ops::{Deref, DerefMut}; -use crate::scroll::ScrollbarShow; +pub mod colors; +pub mod scale; -pub fn init(cx: &mut AppContext) { - Theme::sync_system_appearance(cx) +#[derive(Debug, Clone, Copy, Default)] +pub struct ThemeColors { + pub border: Hsla, + pub window_border: Hsla, + pub accent: Hsla, + pub accent_foreground: Hsla, + pub background: Hsla, + pub card: Hsla, + pub card_foreground: Hsla, + pub danger: Hsla, + pub danger_active: Hsla, + pub danger_foreground: Hsla, + pub danger_hover: Hsla, + pub drag_border: Hsla, + pub drop_target: Hsla, + pub foreground: Hsla, + pub input: Hsla, + pub link: Hsla, + pub link_active: Hsla, + pub link_hover: Hsla, + pub list: Hsla, + pub list_active: Hsla, + pub list_active_border: Hsla, + pub list_even: Hsla, + pub list_head: Hsla, + pub list_hover: Hsla, + pub muted: Hsla, + pub muted_foreground: Hsla, + pub popover: Hsla, + pub popover_foreground: Hsla, + pub primary: Hsla, + pub primary_active: Hsla, + pub primary_foreground: Hsla, + pub primary_hover: Hsla, + pub progress_bar: Hsla, + pub ring: Hsla, + pub scrollbar: Hsla, + pub scrollbar_thumb: Hsla, + pub scrollbar_thumb_hover: Hsla, + pub secondary: Hsla, + pub secondary_active: Hsla, + pub secondary_foreground: Hsla, + pub secondary_hover: Hsla, + pub selection: Hsla, + pub skeleton: Hsla, + pub slider_bar: Hsla, + pub slider_thumb: Hsla, + pub tab: Hsla, + pub tab_active: Hsla, + pub tab_active_foreground: Hsla, + pub tab_bar: Hsla, + pub tab_foreground: Hsla, + pub title_bar: Hsla, + pub title_bar_border: Hsla, } -pub trait ActiveTheme { - fn theme(&self) -> &Theme; -} +impl ThemeColors { + pub fn light() -> Self { + Self { + accent: hsl(240.0, 5.0, 96.0), + accent_foreground: hsl(240.0, 5.9, 10.0), + background: hsl(0.0, 0.0, 100.), + border: hsl(240.0, 5.9, 90.0), + window_border: hsl(240.0, 5.9, 78.0), + card: hsl(0.0, 0.0, 100.0), + card_foreground: hsl(240.0, 10.0, 3.9), + danger: hsl(0.0, 84.2, 60.2), + danger_active: hsl(0.0, 84.2, 47.0), + danger_foreground: hsl(0.0, 0.0, 98.0), + danger_hover: hsl(0.0, 84.2, 65.0), + drag_border: blue(), + drop_target: hsl(235.0, 30., 44.0).opacity(0.25), + foreground: hsl(240.0, 10., 3.9), + input: hsl(240.0, 5.9, 90.0), + link: hsl(221.0, 83.0, 53.0), + link_active: hsl(221.0, 83.0, 53.0).darken(0.2), + link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2), + list: hsl(0.0, 0.0, 100.), + list_active: hsl(211.0, 97.0, 85.0).opacity(0.2), + list_active_border: hsl(211.0, 97.0, 85.0), + list_even: hsl(240.0, 5.0, 96.0), + list_head: hsl(0.0, 0.0, 100.), + list_hover: hsl(240.0, 4.8, 95.0), + muted: hsl(240.0, 4.8, 95.9), + muted_foreground: hsl(240.0, 3.8, 46.1), + popover: hsl(0.0, 0.0, 100.0), + popover_foreground: hsl(240.0, 10.0, 3.9), + primary: hsl(223.0, 5.9, 10.0), + primary_active: hsl(223.0, 1.9, 25.0), + primary_foreground: hsl(223.0, 0.0, 98.0), + primary_hover: hsl(223.0, 5.9, 15.0), + progress_bar: hsl(223.0, 5.9, 10.0), + ring: hsl(240.0, 5.9, 65.0), + scrollbar: hsl(0., 0., 97.).opacity(0.75), + scrollbar_thumb: hsl(0., 0., 69.).opacity(0.9), + scrollbar_thumb_hover: hsl(0., 0., 59.), + secondary: hsl(240.0, 5.9, 96.9), + secondary_active: hsl(240.0, 5.9, 90.), + secondary_foreground: hsl(240.0, 59.0, 10.), + secondary_hover: hsl(240.0, 5.9, 98.), + selection: hsl(211.0, 97.0, 85.0), + skeleton: hsl(223.0, 5.9, 10.0).opacity(0.1), + slider_bar: hsl(223.0, 5.9, 10.0), + slider_thumb: hsl(0.0, 0.0, 100.0), + tab: transparent_black(), + tab_active: hsl(0.0, 0.0, 100.0), + tab_active_foreground: hsl(240.0, 10., 3.9), + tab_bar: hsl(240.0, 4.8, 95.9), + tab_foreground: hsl(240.0, 10., 3.9), + title_bar: hsl(0.0, 0.0, 98.0), + title_bar_border: hsl(220.0, 13.0, 91.0), + } + } -impl ActiveTheme for AppContext { - fn theme(&self) -> &Theme { - Theme::global(self) + pub fn dark() -> Self { + Self { + accent: hsl(240.0, 3.7, 15.9), + accent_foreground: hsl(0.0, 0.0, 78.0), + background: hsl(0.0, 0.0, 8.0), + border: hsl(240.0, 3.7, 16.9), + window_border: hsl(240.0, 3.7, 28.0), + card: hsl(0.0, 0.0, 8.0), + card_foreground: hsl(0.0, 0.0, 78.0), + danger: hsl(0.0, 62.8, 30.6), + danger_active: hsl(0.0, 62.8, 20.6), + danger_foreground: hsl(0.0, 0.0, 78.0), + danger_hover: hsl(0.0, 62.8, 35.6), + drag_border: blue(), + drop_target: hsl(235.0, 30., 44.0).opacity(0.1), + foreground: hsl(0., 0., 78.), + input: hsl(240.0, 3.7, 15.9), + link: hsl(221.0, 83.0, 53.0), + link_active: hsl(221.0, 83.0, 53.0).darken(0.2), + link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2), + list: hsl(0.0, 0.0, 8.0), + list_active: hsl(240.0, 3.7, 15.0).opacity(0.2), + list_active_border: hsl(240.0, 5.9, 35.5), + list_even: hsl(240.0, 3.7, 10.0), + list_head: hsl(0.0, 0.0, 8.0), + list_hover: hsl(240.0, 3.7, 15.9), + muted: hsl(240.0, 3.7, 15.9), + muted_foreground: hsl(240.0, 5.0, 64.9), + popover: hsl(0.0, 0.0, 10.), + popover_foreground: hsl(0.0, 0.0, 78.0), + primary: hsl(223.0, 0.0, 98.0), + primary_active: hsl(223.0, 0.0, 80.0), + primary_foreground: hsl(223.0, 5.9, 10.0), + primary_hover: hsl(223.0, 0.0, 90.0), + progress_bar: hsl(223.0, 0.0, 98.0), + ring: hsl(240.0, 4.9, 83.9), + scrollbar: hsl(240., 1., 15.).opacity(0.75), + scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9), + scrollbar_thumb_hover: hsl(0., 0., 68.), + secondary: hsl(240.0, 0., 13.0), + secondary_active: hsl(240.0, 0., 10.), + secondary_foreground: hsl(0.0, 0.0, 78.0), + secondary_hover: hsl(240.0, 0., 15.), + selection: hsl(211.0, 97.0, 22.0), + skeleton: hsla(223.0, 0.0, 98.0, 0.1), + slider_bar: hsl(223.0, 0.0, 98.0), + slider_thumb: hsl(0.0, 0.0, 8.0), + tab: transparent_black(), + tab_active: hsl(0.0, 0.0, 8.0), + tab_active_foreground: hsl(0., 0., 78.), + tab_bar: hsl(299.0, 0., 5.5), + tab_foreground: hsl(0., 0., 78.), + title_bar: hsl(240.0, 0.0, 10.0), + title_bar_border: hsl(240.0, 3.7, 15.9), + } } } -impl ActiveTheme for ViewContext<'_, V> { - fn theme(&self) -> &Theme { - self.deref().theme() - } -} - -impl ActiveTheme for ModelContext<'_, V> { - fn theme(&self) -> &Theme { - self.deref().theme() - } -} - -impl ActiveTheme for WindowContext<'_> { - fn theme(&self) -> &Theme { - self.deref().theme() - } -} - -/// Make a [gpui::Hsla] color. -/// -/// - h: 0..360.0 -/// - s: 0.0..100.0 -/// - l: 0.0..100.0 -pub fn hsl(h: f32, s: f32, l: f32) -> Hsla { - hsla(h / 360., s / 100.0, l / 100.0, 1.0) -} - -/// Make a BoxShadow like CSS -/// -/// e.g: -/// -/// If CSS is `box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);` -/// -/// Then the equivalent in Rust is `box_shadow(0., 0., 10., 0., hsla(0., 0., 0., 0.1))` -pub fn box_shadow( - x: impl Into, - y: impl Into, - blur: impl Into, - spread: impl Into, - color: Hsla, -) -> BoxShadow { - BoxShadow { - offset: point(x.into(), y.into()), - blur_radius: blur.into(), - spread_radius: spread.into(), - color, - } -} pub trait Colorize { fn opacity(&self, opacity: f32) -> Hsla; fn divide(&self, divisor: f32) -> Hsla; @@ -145,206 +258,42 @@ impl Colorize for Hsla { } } } -#[derive(Debug, Clone, Copy, Default)] -pub struct ThemeColor { - pub accent: Hsla, - pub accent_foreground: Hsla, - pub background: Hsla, - pub border: Hsla, - pub window_border: Hsla, - pub card: Hsla, - pub card_foreground: Hsla, - pub destructive: Hsla, - pub destructive_active: Hsla, - pub destructive_foreground: Hsla, - pub destructive_hover: Hsla, - pub drag_border: Hsla, - pub drop_target: Hsla, - pub foreground: Hsla, - pub input: Hsla, - pub link: Hsla, - pub link_active: Hsla, - pub link_hover: Hsla, - pub list: Hsla, - pub list_active: Hsla, - pub list_active_border: Hsla, - pub list_even: Hsla, - pub list_head: Hsla, - pub list_hover: Hsla, - pub muted: Hsla, - pub muted_foreground: Hsla, - pub panel: Hsla, - pub popover: Hsla, - pub popover_foreground: Hsla, - pub primary: Hsla, - pub primary_active: Hsla, - pub primary_foreground: Hsla, - pub primary_hover: Hsla, - pub progress_bar: Hsla, - pub ring: Hsla, - pub scrollbar: Hsla, - pub scrollbar_thumb: Hsla, - pub scrollbar_thumb_hover: Hsla, - pub secondary: Hsla, - pub secondary_active: Hsla, - pub secondary_foreground: Hsla, - pub secondary_hover: Hsla, - pub selection: Hsla, - pub skeleton: Hsla, - pub slider_bar: Hsla, - pub slider_thumb: Hsla, - pub tab: Hsla, - pub tab_active: Hsla, - pub tab_active_foreground: Hsla, - pub tab_bar: Hsla, - pub tab_foreground: Hsla, - pub title_bar: Hsla, - pub title_bar_border: Hsla, - pub sidebar: Hsla, - pub sidebar_accent: Hsla, - pub sidebar_accent_foreground: Hsla, - pub sidebar_border: Hsla, - pub sidebar_foreground: Hsla, - pub sidebar_primary: Hsla, - pub sidebar_primary_foreground: Hsla, + +pub trait ActiveTheme { + fn theme(&self) -> &Theme; } -impl ThemeColor { - pub fn light() -> Self { - Self { - accent: hsl(240.0, 5.0, 96.0), - accent_foreground: hsl(240.0, 5.9, 10.0), - background: hsl(0.0, 0.0, 100.), - border: hsl(240.0, 5.9, 90.0), - window_border: hsl(240.0, 5.9, 78.0), - card: hsl(0.0, 0.0, 100.0), - card_foreground: hsl(240.0, 10.0, 3.9), - destructive: hsl(0.0, 84.2, 60.2), - destructive_active: hsl(0.0, 84.2, 47.0), - destructive_foreground: hsl(0.0, 0.0, 98.0), - destructive_hover: hsl(0.0, 84.2, 65.0), - drag_border: crate::blue_500(), - drop_target: hsl(235.0, 30., 44.0).opacity(0.25), - foreground: hsl(240.0, 10., 3.9), - input: hsl(240.0, 5.9, 90.0), - link: hsl(221.0, 83.0, 53.0), - link_active: hsl(221.0, 83.0, 53.0).darken(0.2), - link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2), - list: hsl(0.0, 0.0, 100.), - list_active: hsl(211.0, 97.0, 85.0).opacity(0.2), - list_active_border: hsl(211.0, 97.0, 85.0), - list_even: hsl(240.0, 5.0, 96.0), - list_head: hsl(0.0, 0.0, 100.), - list_hover: hsl(240.0, 4.8, 95.0), - muted: hsl(240.0, 4.8, 95.9), - muted_foreground: hsl(240.0, 3.8, 46.1), - panel: hsl(0.0, 0.0, 100.0), - popover: hsl(0.0, 0.0, 100.0), - popover_foreground: hsl(240.0, 10.0, 3.9), - primary: hsl(223.0, 5.9, 10.0), - primary_active: hsl(223.0, 1.9, 25.0), - primary_foreground: hsl(223.0, 0.0, 98.0), - primary_hover: hsl(223.0, 5.9, 15.0), - progress_bar: hsl(223.0, 5.9, 10.0), - ring: hsl(240.0, 5.9, 65.0), - scrollbar: hsl(0., 0., 97.).opacity(0.75), - scrollbar_thumb: hsl(0., 0., 69.).opacity(0.9), - scrollbar_thumb_hover: hsl(0., 0., 59.), - secondary: hsl(240.0, 5.9, 96.9), - secondary_active: hsl(240.0, 5.9, 90.), - secondary_foreground: hsl(240.0, 59.0, 10.), - secondary_hover: hsl(240.0, 5.9, 98.), - selection: hsl(211.0, 97.0, 85.0), - skeleton: hsl(223.0, 5.9, 10.0).opacity(0.1), - slider_bar: hsl(223.0, 5.9, 10.0), - slider_thumb: hsl(0.0, 0.0, 100.0), - tab: gpui::transparent_black(), - tab_active: hsl(0.0, 0.0, 100.0), - tab_active_foreground: hsl(240.0, 10., 3.9), - tab_bar: hsl(240.0, 4.8, 95.9), - tab_foreground: hsl(240.0, 10., 3.9), - title_bar: hsl(0.0, 0.0, 98.0), - title_bar_border: hsl(220.0, 13.0, 91.0), - sidebar: hsl(0.0, 0.0, 98.0), - sidebar_accent: hsl(240.0, 4.8, 92.), - sidebar_accent_foreground: hsl(240.0, 5.9, 10.0), - sidebar_border: hsl(220.0, 13.0, 91.0), - sidebar_foreground: hsl(240.0, 5.3, 26.1), - sidebar_primary: hsl(240.0, 5.9, 10.0), - sidebar_primary_foreground: hsl(0.0, 0.0, 98.0), - } +impl ActiveTheme for AppContext { + fn theme(&self) -> &Theme { + Theme::global(self) } +} - pub fn dark() -> Self { - Self { - accent: hsl(240.0, 3.7, 15.9), - accent_foreground: hsl(0.0, 0.0, 78.0), - background: hsl(0.0, 0.0, 8.0), - border: hsl(240.0, 3.7, 16.9), - window_border: hsl(240.0, 3.7, 28.0), - card: hsl(0.0, 0.0, 8.0), - card_foreground: hsl(0.0, 0.0, 78.0), - destructive: hsl(0.0, 62.8, 30.6), - destructive_active: hsl(0.0, 62.8, 20.6), - destructive_foreground: hsl(0.0, 0.0, 78.0), - destructive_hover: hsl(0.0, 62.8, 35.6), - drag_border: crate::blue_500(), - drop_target: hsl(235.0, 30., 44.0).opacity(0.1), - foreground: hsl(0., 0., 78.), - input: hsl(240.0, 3.7, 15.9), - link: hsl(221.0, 83.0, 53.0), - link_active: hsl(221.0, 83.0, 53.0).darken(0.2), - link_hover: hsl(221.0, 83.0, 53.0).lighten(0.2), - list: hsl(0.0, 0.0, 8.0), - list_active: hsl(240.0, 3.7, 15.0).opacity(0.2), - list_active_border: hsl(240.0, 5.9, 35.5), - list_even: hsl(240.0, 3.7, 10.0), - list_head: hsl(0.0, 0.0, 8.0), - list_hover: hsl(240.0, 3.7, 15.9), - muted: hsl(240.0, 3.7, 15.9), - muted_foreground: hsl(240.0, 5.0, 64.9), - panel: hsl(299.0, 2., 11.), - popover: hsl(0.0, 0.0, 10.), - popover_foreground: hsl(0.0, 0.0, 78.0), - primary: hsl(223.0, 0.0, 98.0), - primary_active: hsl(223.0, 0.0, 80.0), - primary_foreground: hsl(223.0, 5.9, 10.0), - primary_hover: hsl(223.0, 0.0, 90.0), - progress_bar: hsl(223.0, 0.0, 98.0), - ring: hsl(240.0, 4.9, 83.9), - scrollbar: hsl(240., 1., 15.).opacity(0.75), - scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9), - scrollbar_thumb_hover: hsl(0., 0., 68.), - secondary: hsl(240.0, 0., 13.0), - secondary_active: hsl(240.0, 0., 10.), - secondary_foreground: hsl(0.0, 0.0, 78.0), - secondary_hover: hsl(240.0, 0., 15.), - selection: hsl(211.0, 97.0, 22.0), - skeleton: hsla(223.0, 0.0, 98.0, 0.1), - slider_bar: hsl(223.0, 0.0, 98.0), - slider_thumb: hsl(0.0, 0.0, 8.0), - tab: gpui::transparent_black(), - tab_active: hsl(0.0, 0.0, 8.0), - tab_active_foreground: hsl(0., 0., 78.), - tab_bar: hsl(299.0, 0., 5.5), - tab_foreground: hsl(0., 0., 78.), - title_bar: hsl(240.0, 0.0, 10.0), - title_bar_border: hsl(240.0, 3.7, 15.9), - sidebar: hsl(240.0, 0.0, 10.0), - sidebar_accent: hsl(240.0, 3.7, 15.9), - sidebar_accent_foreground: hsl(240.0, 4.8, 95.9), - sidebar_border: hsl(240.0, 3.7, 15.9), - sidebar_foreground: hsl(240.0, 4.8, 95.9), - sidebar_primary: hsl(0.0, 0.0, 98.0), - sidebar_primary_foreground: hsl(240.0, 5.9, 10.0), - } +impl ActiveTheme for ViewContext<'_, V> { + fn theme(&self) -> &Theme { + self.deref().theme() } } +impl ActiveTheme for ModelContext<'_, V> { + fn theme(&self) -> &Theme { + self.deref().theme() + } +} + +impl ActiveTheme for WindowContext<'_> { + fn theme(&self) -> &Theme { + self.deref().theme() + } +} + +pub fn init(cx: &mut AppContext) { + Theme::sync_system_appearance(cx) +} + #[derive(Debug, Clone)] pub struct Theme { - colors: ThemeColor, - + pub colors: ThemeColors, pub mode: ThemeMode, pub font_family: SharedString, pub font_size: f32, @@ -356,7 +305,7 @@ pub struct Theme { } impl Deref for Theme { - type Target = ThemeColor; + type Target = ThemeColors; fn deref(&self) -> &Self::Target { &self.colors @@ -410,7 +359,6 @@ impl Theme { self.scrollbar = self.scrollbar.apply(mask_color); self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color); self.scrollbar_thumb_hover = self.scrollbar_thumb_hover.apply(mask_color); - self.panel = self.panel.apply(mask_color); self.drag_border = self.drag_border.apply(mask_color); self.drop_target = self.drop_target.apply(mask_color); self.tab_bar = self.tab_bar.apply(mask_color); @@ -433,13 +381,6 @@ impl Theme { self.skeleton = self.skeleton.apply(mask_color); self.title_bar = self.title_bar.apply(mask_color); self.title_bar_border = self.title_bar_border.apply(mask_color); - self.sidebar = self.sidebar.apply(mask_color); - self.sidebar_accent = self.sidebar_accent.apply(mask_color); - self.sidebar_accent_foreground = self.sidebar_accent_foreground.apply(mask_color); - self.sidebar_border = self.sidebar_border.apply(mask_color); - self.sidebar_foreground = self.sidebar_foreground.apply(mask_color); - self.sidebar_primary = self.sidebar_primary.apply(mask_color); - self.sidebar_primary_foreground = self.sidebar_primary_foreground.apply(mask_color); } /// Sync the theme with the system appearance @@ -456,8 +397,8 @@ impl Theme { pub fn change(mode: ThemeMode, cx: &mut AppContext) { let colors = match mode { - ThemeMode::Light => ThemeColor::light(), - ThemeMode::Dark => ThemeColor::dark(), + ThemeMode::Light => ThemeColors::light(), + ThemeMode::Dark => ThemeColors::dark(), }; let mut theme = Theme::from(colors); @@ -468,8 +409,8 @@ impl Theme { } } -impl From for Theme { - fn from(colors: ThemeColor) -> Self { +impl From for Theme { + fn from(colors: ThemeColors) -> Self { Theme { mode: ThemeMode::default(), transparent: Hsla::transparent_black(), @@ -481,7 +422,7 @@ impl From for Theme { } else { "FreeMono".into() }, - radius: 5.0, + radius: 6.0, shadow: false, scrollbar_show: ScrollbarShow::default(), colors, @@ -491,8 +432,8 @@ impl From for Theme { #[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq)] pub enum ThemeMode { - Light, #[default] + Light, Dark, } @@ -501,28 +442,3 @@ impl ThemeMode { matches!(self, Self::Dark) } } - -#[cfg(test)] -mod tests { - use crate::theme::Colorize as _; - - #[test] - fn test_lighten() { - let color = super::hsl(240.0, 5.0, 30.0); - let color = color.lighten(0.5); - assert_eq!(color.l, 0.45000002); - let color = color.lighten(0.5); - assert_eq!(color.l, 0.675); - let color = color.lighten(0.1); - assert_eq!(color.l, 0.7425); - } - - #[test] - fn test_darken() { - let color = super::hsl(240.0, 5.0, 96.0); - let color = color.darken(0.5); - assert_eq!(color.l, 0.48); - let color = color.darken(0.5); - assert_eq!(color.l, 0.24); - } -} diff --git a/crates/ui/src/theme/scale.rs b/crates/ui/src/theme/scale.rs new file mode 100644 index 0000000..a0cab9a --- /dev/null +++ b/crates/ui/src/theme/scale.rs @@ -0,0 +1,295 @@ +use crate::theme::{ActiveTheme, ThemeMode}; +use gpui::{AppContext, Hsla, SharedString}; + +/// A collection of colors that are used to style the UI. +/// +/// Each step has a semantic meaning, and is used to style different parts of the UI. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct ColorScaleStep(usize); + +impl ColorScaleStep { + pub const ONE: Self = Self(1); + pub const TWO: Self = Self(2); + pub const THREE: Self = Self(3); + pub const FOUR: Self = Self(4); + pub const FIVE: Self = Self(5); + pub const SIX: Self = Self(6); + pub const SEVEN: Self = Self(7); + pub const EIGHT: Self = Self(8); + pub const NINE: Self = Self(9); + pub const TEN: Self = Self(10); + pub const ELEVEN: Self = Self(11); + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; +} + +/// A scale of colors for a given [`ColorScaleSet`]. +/// +/// Each [`ColorScale`] contains exactly 12 colors. Refer to +/// [`ColorScaleStep`] for a reference of what each step is used for. +pub struct ColorScale(Vec); + +impl FromIterator for ColorScale { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl ColorScale { + /// Returns the specified step in the [`ColorScale`]. + #[inline] + pub fn step(&self, step: ColorScaleStep) -> Hsla { + // Steps are one-based, so we need convert to the zero-based vec index. + self.0[step.0 - 1] + } + + /// `Step 1` - Used for main application backgrounds. + /// + /// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas. + /// + #[inline] + pub fn step_1(&self) -> Hsla { + self.step(ColorScaleStep::ONE) + } + + /// `Step 2` - Used for both main application backgrounds and subtle component backgrounds. + /// + /// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds. + #[inline] + pub fn step_2(&self) -> Hsla { + self.step(ColorScaleStep::TWO) + } + + /// `Step 3` - Used for UI component backgrounds in their normal states. + /// + /// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components. + #[inline] + pub fn step_3(&self) -> Hsla { + self.step(ColorScaleStep::THREE) + } + + /// `Step 4` - Used for UI component backgrounds in their hover states. + /// + /// Also suited for pressed or selected states of components with a transparent background. + #[inline] + pub fn step_4(&self) -> Hsla { + self.step(ColorScaleStep::FOUR) + } + + /// `Step 5` - Used for UI component backgrounds in their pressed or selected states. + #[inline] + pub fn step_5(&self) -> Hsla { + self.step(ColorScaleStep::FIVE) + } + + /// `Step 6` - Used for subtle borders on non-interactive components. + /// + /// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators. + #[inline] + pub fn step_6(&self) -> Hsla { + self.step(ColorScaleStep::SIX) + } + + /// `Step 7` - Used for subtle borders on interactive components. + /// + /// This step subtly delineates the boundary of elements users interact with. + #[inline] + pub fn step_7(&self) -> Hsla { + self.step(ColorScaleStep::SEVEN) + } + + /// `Step 8` - Used for stronger borders on interactive components and focus rings. + /// + /// It strengthens the visibility and accessibility of active elements and their focus states. + #[inline] + pub fn step_8(&self) -> Hsla { + self.step(ColorScaleStep::EIGHT) + } + + /// `Step 9` - Used for solid backgrounds. + /// + /// `Step 9` is the most saturated step, having the least mix of white or black. + /// + /// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as + /// error, warning, and success indicators. + #[inline] + pub fn step_9(&self) -> Hsla { + self.step(ColorScaleStep::NINE) + } + + /// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state. + /// + /// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read. + #[inline] + pub fn step_10(&self) -> Hsla { + self.step(ColorScaleStep::TEN) + } + + /// `Step 11` - Used for text and icons requiring low contrast or less emphasis. + #[inline] + pub fn step_11(&self) -> Hsla { + self.step(ColorScaleStep::ELEVEN) + } + + /// `Step 12` - Used for text and icons requiring high contrast or prominence. + #[inline] + pub fn step_12(&self) -> Hsla { + self.step(ColorScaleStep::TWELVE) + } +} + +pub struct ColorScales { + pub gray: ColorScaleSet, + pub mauve: ColorScaleSet, + pub slate: ColorScaleSet, + pub sage: ColorScaleSet, + pub olive: ColorScaleSet, + pub sand: ColorScaleSet, + pub gold: ColorScaleSet, + pub bronze: ColorScaleSet, + pub brown: ColorScaleSet, + pub yellow: ColorScaleSet, + pub amber: ColorScaleSet, + pub orange: ColorScaleSet, + pub tomato: ColorScaleSet, + pub red: ColorScaleSet, + pub ruby: ColorScaleSet, + pub crimson: ColorScaleSet, + pub pink: ColorScaleSet, + pub plum: ColorScaleSet, + pub purple: ColorScaleSet, + pub violet: ColorScaleSet, + pub iris: ColorScaleSet, + pub indigo: ColorScaleSet, + pub blue: ColorScaleSet, + pub cyan: ColorScaleSet, + pub teal: ColorScaleSet, + pub jade: ColorScaleSet, + pub green: ColorScaleSet, + pub grass: ColorScaleSet, + pub lime: ColorScaleSet, + pub mint: ColorScaleSet, + pub sky: ColorScaleSet, + pub black: ColorScaleSet, + pub white: ColorScaleSet, +} + +impl IntoIterator for ColorScales { + type Item = ColorScaleSet; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + self.gray, + self.mauve, + self.slate, + self.sage, + self.olive, + self.sand, + self.gold, + self.bronze, + self.brown, + self.yellow, + self.amber, + self.orange, + self.tomato, + self.red, + self.ruby, + self.crimson, + self.pink, + self.plum, + self.purple, + self.violet, + self.iris, + self.indigo, + self.blue, + self.cyan, + self.teal, + self.jade, + self.green, + self.grass, + self.lime, + self.mint, + self.sky, + self.black, + self.white, + ] + .into_iter() + } +} + +/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale. +pub struct ColorScaleSet { + name: SharedString, + light: ColorScale, + dark: ColorScale, + light_alpha: ColorScale, + dark_alpha: ColorScale, +} + +impl ColorScaleSet { + pub fn new( + name: impl Into, + light: ColorScale, + light_alpha: ColorScale, + dark: ColorScale, + dark_alpha: ColorScale, + ) -> Self { + Self { + name: name.into(), + light, + light_alpha, + dark, + dark_alpha, + } + } + + pub fn name(&self) -> &SharedString { + &self.name + } + + pub fn light(&self) -> &ColorScale { + &self.light + } + + pub fn light_alpha(&self) -> &ColorScale { + &self.light_alpha + } + + pub fn dark(&self) -> &ColorScale { + &self.dark + } + + pub fn dark_alpha(&self) -> &ColorScale { + &self.dark_alpha + } + + pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { + match cx.theme().mode { + ThemeMode::Light => self.light().step(step), + ThemeMode::Dark => self.dark().step(step), + } + } + + pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { + match cx.theme().mode { + ThemeMode::Light => self.light_alpha.step(step), + ThemeMode::Dark => self.dark_alpha.step(step), + } + } +} diff --git a/crates/ui/src/title_bar.rs b/crates/ui/src/title_bar.rs index e016f1f..1e98df8 100644 --- a/crates/ui/src/title_bar.rs +++ b/crates/ui/src/title_bar.rs @@ -1,12 +1,11 @@ +use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _}; use gpui::{ - div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla, - InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Stateful, - StatefulInteractiveElement as _, Style, Styled, WindowContext, + black, div, prelude::FluentBuilder as _, px, relative, white, AnyElement, ClickEvent, Div, + Element, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, + RenderOnce, Rgba, Stateful, StatefulInteractiveElement as _, Style, Styled, WindowContext, }; use std::rc::Rc; -use crate::{h_flex, theme::ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _}; - pub const HEIGHT: Pixels = px(34.); pub const TITLE_BAR_HEIGHT: Pixels = px(35.); @@ -108,31 +107,42 @@ impl ControlIcon { fn fg(&self, cx: &WindowContext) -> Hsla { if cx.theme().mode.is_dark() { - crate::white() + white() } else { - crate::black() + black() } } fn hover_fg(&self, cx: &WindowContext) -> Hsla { if self.is_close() || cx.theme().mode.is_dark() { - crate::white() + white() } else { - crate::black() + black() } } - fn hover_bg(&self, cx: &WindowContext) -> Hsla { + fn hover_bg(&self, cx: &WindowContext) -> Rgba { if self.is_close() { - if cx.theme().mode.is_dark() { - crate::red_800() - } else { - crate::red_600() + Rgba { + r: 232.0 / 255.0, + g: 17.0 / 255.0, + b: 32.0 / 255.0, + a: 1.0, } } else if cx.theme().mode.is_dark() { - crate::stone_700() + Rgba { + r: 0.9, + g: 0.9, + b: 0.9, + a: 0.1, + } } else { - crate::stone_200() + Rgba { + r: 0.1, + g: 0.1, + b: 0.1, + a: 0.2, + } } } } @@ -178,7 +188,7 @@ impl RenderOnce for ControlIcon { }) }) .hover(|style| style.bg(hover_bg).text_color(hover_fg)) - .active(|style| style.bg(hover_bg.opacity(0.7))) + .active(|style| style.bg(hover_bg)) .child(Icon::new(self.icon()).small()) } } diff --git a/crates/ui/src/tooltip.rs b/crates/ui/src/tooltip.rs index 2ae6a65..61b7f85 100644 --- a/crates/ui/src/tooltip.rs +++ b/crates/ui/src/tooltip.rs @@ -25,7 +25,6 @@ impl Render for Tooltip { .m_3() .bg(cx.theme().popover) .text_color(cx.theme().popover_foreground) - .bg(cx.theme().popover) .border_1() .border_color(cx.theme().border) .shadow_md()