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
This commit is contained in:
Nate Butler 2024-08-29 16:50:03 -04:00 committed by GitHub
parent 82ceb4c091
commit 01f8d27f22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 211 additions and 2 deletions

View file

@ -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<WindowAppearance> 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()
}
}

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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<Self> {
cx.new_view(|_cx| Self)
}
}
impl Render for DefaultColorsStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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::<Vec<_>>();
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"),
),
)
}))
}
}

View file

@ -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(),
}
}
}