From 01f8d27f22ff77a49f63eaa3f9d8e1884125da78 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Aug 2024 16:50:03 -0400 Subject: [PATCH] Add a simple set of default colors to `gpui` (#17110) This PR adds an initial set of default colors to `gpui`. These will power default-styled gpui components (things like checkboxes, buttons, inputs, etc.), storybook, and give a very simple, appearance-aware set of colors out of the box for folks to build with. These colors will evolve and be updated in the near future, they are literally pulled from Finder for now :) The API might not be perfect, I focused on getting something in quickly that we can iterate on! ### Usage ```rs use gpui::{colors, DefaultColor} fn auto(cx: &WindowContext) -> { // Init the full set of DefaultColors let colors = colors(cx.appearance()); // Use a color // It will automatically give you the correct color for the system's // current appearance. let background = DefaultColor::Background.hsla(&colors) } fn manual() -> { // Init the full sets of DefaultColors let light_colors = DefaultColors::light(); let dark_colors = DefaultColors::dark(); // Use a color // Maybe for some fancy inverted element let background = DefaultColor::Background.hsla(&light_colors) let inverted_background = DefaultColor::Background.hsla(&dark_colors) let inverted_text = DefaultColor::Text.hsla(&dark_colors) } ``` Note: We need `cx` for the auto way as we need to get the system appearance from the App/Window/ViewContext via `cx.appearance()`. ### Example You can run `script/storybook default_colors` to open the Default Colors story: | Light | Dark | |-------|------| | ![CleanShot 2024-08-29 at 16 19 20@2x](https://github.com/user-attachments/assets/80369de4-8926-4b30-80f5-f81a8a7b9531) | ![CleanShot 2024-08-29 at 16 19 33@2x](https://github.com/user-attachments/assets/fd7a2aae-27e6-460f-a054-8f37623dc96d) | Release Notes: - N/A --- crates/gpui/src/elements/common.rs | 114 ++++++++++++++++++ crates/gpui/src/elements/mod.rs | 2 + crates/storybook/src/stories.rs | 2 + .../storybook/src/stories/default_colors.rs | 89 ++++++++++++++ crates/storybook/src/story_selector.rs | 6 +- 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 crates/gpui/src/elements/common.rs create mode 100644 crates/storybook/src/stories/default_colors.rs diff --git a/crates/gpui/src/elements/common.rs b/crates/gpui/src/elements/common.rs new file mode 100644 index 0000000000000000000000000000000000000000..0204be1c377d3e6f8cafbe61ab54abde5ef9641e --- /dev/null +++ b/crates/gpui/src/elements/common.rs @@ -0,0 +1,114 @@ +use crate::{rgb, Hsla, Rgba, WindowAppearance}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +/// The appearance of the base gpui colors, used to style gpui elements +/// +/// Varries based on the system's current [WindowAppearance]. +pub enum DefaultThemeAppearance { + #[default] + /// Use the set of colors for light appearances + Light, + /// Use the set of colors for dark appearances + Dark, +} + +impl From for DefaultThemeAppearance { + fn from(appearance: WindowAppearance) -> Self { + match appearance { + WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light, + WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark, + } + } +} + +/// Get the default colors for the given appearance +pub fn colors(appearance: DefaultThemeAppearance) -> DefaultColors { + match appearance { + DefaultThemeAppearance::Light => DefaultColors::light(), + DefaultThemeAppearance::Dark => DefaultColors::dark(), + } +} + +/// A collection of colors +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct DefaultColors { + text: Rgba, + selected_text: Rgba, + background: Rgba, + disabled: Rgba, + selected: Rgba, + border: Rgba, + separator: Rgba, + container: Rgba, +} + +impl DefaultColors { + /// Get the default light colors + pub fn dark() -> Self { + Self { + text: rgb(0xFFFFFF), + selected_text: rgb(0xFFFFFF), + disabled: rgb(0x565656), + selected: rgb(0x2457CA), + background: rgb(0x222222), + border: rgb(0x000000), + separator: rgb(0xD9D9D9), + container: rgb(0x262626), + } + } + + /// Get the default dark colors + pub fn light() -> Self { + Self { + text: rgb(0x252525), + selected_text: rgb(0xFFFFFF), + background: rgb(0xFFFFFF), + disabled: rgb(0xB0B0B0), + selected: rgb(0x2A63D9), + border: rgb(0xD9D9D9), + separator: rgb(0xE6E6E6), + container: rgb(0xF4F5F5), + } + } +} + +/// A default gpui color +#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)] +pub enum DefaultColor { + /// Text color + Text, + /// Selected text color + SelectedText, + /// Background color + Background, + /// Disabled color + Disabled, + /// Selected color + Selected, + /// Border color + Border, + /// Separator color + Separator, + /// Container color + Container, +} +impl DefaultColor { + /// Get the Rgb color for the given color type + pub fn color(&self, colors: &DefaultColors) -> Rgba { + match self { + DefaultColor::Text => colors.text, + DefaultColor::SelectedText => colors.selected_text, + DefaultColor::Background => colors.background, + DefaultColor::Disabled => colors.disabled, + DefaultColor::Selected => colors.selected, + DefaultColor::Border => colors.border, + DefaultColor::Separator => colors.separator, + DefaultColor::Container => colors.container, + } + } + + /// Get the Hsla color for the given color type + pub fn hsla(&self, colors: &DefaultColors) -> Hsla { + self.color(&colors).into() + } +} diff --git a/crates/gpui/src/elements/mod.rs b/crates/gpui/src/elements/mod.rs index 33870341804c1eb05df62125113901de40373615..0a680e4399463fd62ba954f2cdcb4aa7f202d56b 100644 --- a/crates/gpui/src/elements/mod.rs +++ b/crates/gpui/src/elements/mod.rs @@ -1,6 +1,7 @@ mod anchored; mod animation; mod canvas; +mod common; mod deferred; mod div; mod img; @@ -13,6 +14,7 @@ mod uniform_list; pub use anchored::*; pub use animation::*; pub use canvas::*; +pub use common::*; pub use deferred::*; pub use div::*; pub use img::*; diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs index b824235b00b5d49502734515ca14b853ca3be435..66881f741f8cbcd7a8231b889f53ecab94ed71db 100644 --- a/crates/storybook/src/stories.rs +++ b/crates/storybook/src/stories.rs @@ -1,5 +1,6 @@ mod auto_height_editor; mod cursor; +mod default_colors; mod focus; mod kitchen_sink; mod overflow_scroll; @@ -11,6 +12,7 @@ mod with_rem_size; pub use auto_height_editor::*; pub use cursor::*; +pub use default_colors::*; pub use focus::*; pub use kitchen_sink::*; pub use overflow_scroll::*; diff --git a/crates/storybook/src/stories/default_colors.rs b/crates/storybook/src/stories/default_colors.rs new file mode 100644 index 0000000000000000000000000000000000000000..5e262726081daf4382586172a5b14f9b1d5f0ee7 --- /dev/null +++ b/crates/storybook/src/stories/default_colors.rs @@ -0,0 +1,89 @@ +use gpui::{ + colors, div, prelude::*, DefaultColor, DefaultThemeAppearance, Hsla, Render, View, ViewContext, + WindowContext, +}; +use story::Story; +use strum::IntoEnumIterator; +use ui::{h_flex, ActiveTheme}; + +pub struct DefaultColorsStory; + +impl DefaultColorsStory { + pub fn view(cx: &mut WindowContext) -> View { + cx.new_view(|_cx| Self) + } +} + +impl Render for DefaultColorsStory { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let appearances = [DefaultThemeAppearance::Light, DefaultThemeAppearance::Dark]; + + Story::container() + .child(Story::title("Default Colors")) + .children(appearances.iter().map(|&appearance| { + let colors = colors(appearance); + let color_types = DefaultColor::iter() + .map(|color| { + let name = format!("{:?}", color); + let rgba = color.hsla(&colors); + (name, rgba) + }) + .collect::>(); + + div() + .flex() + .flex_col() + .gap_4() + .p_4() + .child(Story::label(format!("{:?} Appearance", appearance))) + .children(color_types.iter().map(|(name, color)| { + let color: Hsla = *color; + + div() + .flex() + .items_center() + .gap_2() + .child( + div() + .w_12() + .h_12() + .bg(color) + .border_1() + .border_color(cx.theme().colors().border), + ) + .child(Story::label(format!("{}: {:?}", name, color.clone()))) + })) + .child( + h_flex() + .gap_1() + .child( + h_flex() + .bg(DefaultColor::Background.hsla(&colors)) + .h_8() + .p_2() + .text_sm() + .text_color(DefaultColor::Text.hsla(&colors)) + .child("Default Text"), + ) + .child( + h_flex() + .bg(DefaultColor::Container.hsla(&colors)) + .h_8() + .p_2() + .text_sm() + .text_color(DefaultColor::Text.hsla(&colors)) + .child("Text on Container"), + ) + .child( + h_flex() + .bg(DefaultColor::Selected.hsla(&colors)) + .h_8() + .p_2() + .text_sm() + .text_color(DefaultColor::SelectedText.hsla(&colors)) + .child("Selected Text"), + ), + ) + })) + } +} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 7c875c4d9010a3a9a3992c07069989e17e00f09e..5df02b1df2f4888775fd9d93fb62c6a8d90eab31 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -20,6 +20,7 @@ pub enum ComponentStory { CollabNotification, ContextMenu, Cursor, + DefaultColors, Disclosure, Focus, Icon, @@ -54,6 +55,7 @@ impl ComponentStory { .into(), Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(), Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(), + Self::DefaultColors => DefaultColorsStory::view(cx).into(), Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(), Self::Focus => FocusStory::view(cx).into(), Self::Icon => cx.new_view(|_| ui::IconStory).into(), @@ -64,15 +66,15 @@ impl ComponentStory { Self::ListHeader => cx.new_view(|_| ui::ListHeaderStory).into(), Self::ListItem => cx.new_view(|_| ui::ListItemStory).into(), Self::OverflowScroll => cx.new_view(|_| crate::stories::OverflowScrollStory).into(), + Self::Picker => PickerStory::new(cx).into(), Self::Scroll => ScrollStory::view(cx).into(), - Self::Text => TextStory::view(cx).into(), Self::Tab => cx.new_view(|_| ui::TabStory).into(), Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(), + Self::Text => TextStory::view(cx).into(), Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(), Self::ToolStrip => cx.new_view(|_| ui::ToolStripStory).into(), Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(), Self::WithRemSize => cx.new_view(|_| crate::stories::WithRemSizeStory).into(), - Self::Picker => PickerStory::new(cx).into(), } } }