mod color;
mod css;
mod palette;
mod tokens;
pub use color::Color;
pub use css::{css, hsl, hsla, rgb, rgba, CssColorError};
pub use palette::{mantine, ColorName, Palette, Shades};
pub use tokens::{Scale, Size};
use gpui::{App, Global, SharedString};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorScheme {
Light,
Dark,
}
impl ColorScheme {
pub fn is_dark(self) -> bool {
matches!(self, ColorScheme::Dark)
}
pub fn toggled(self) -> Self {
match self {
ColorScheme::Light => ColorScheme::Dark,
ColorScheme::Dark => ColorScheme::Light,
}
}
}
#[derive(Debug, Clone)]
pub struct Theme {
pub scheme: ColorScheme,
pub palette: Palette,
pub primary_color: ColorName,
pub primary_shade_light: usize,
pub primary_shade_dark: usize,
pub white: Color,
pub black: Color,
pub spacing: Scale,
pub radius: Scale,
pub font_size: Scale,
pub default_radius: Size,
pub line_height: f32,
pub font_family: SharedString,
pub overrides: Overrides,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Overrides {
pub primary: Option<Color>,
pub body: Option<Color>,
pub surface: Option<Color>,
pub surface_hover: Option<Color>,
pub text: Option<Color>,
pub dimmed: Option<Color>,
pub border: Option<Color>,
}
impl Global for Theme {}
impl Default for Theme {
fn default() -> Self {
Theme::light()
}
}
impl Theme {
pub fn light() -> Self {
Theme {
scheme: ColorScheme::Light,
palette: mantine(),
primary_color: ColorName::Blue,
primary_shade_light: 6,
primary_shade_dark: 8,
white: Color::hex("#ffffff"),
black: Color::hex("#000000"),
spacing: Scale::spacing(),
radius: Scale::radius(),
font_size: Scale::font_size(),
default_radius: Size::Sm,
line_height: 1.55,
font_family: SharedString::new_static("Helvetica"),
overrides: Overrides::default(),
}
}
pub fn dark() -> Self {
Theme {
scheme: ColorScheme::Dark,
..Theme::light()
}
}
pub fn init(self, cx: &mut App) {
cx.set_global(self);
}
pub fn with_primary(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.primary = Some(Color::from_hsla(color.into()));
self
}
pub fn with_body(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.body = Some(Color::from_hsla(color.into()));
self
}
pub fn with_surface(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.surface = Some(Color::from_hsla(color.into()));
self
}
pub fn with_surface_hover(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.surface_hover = Some(Color::from_hsla(color.into()));
self
}
pub fn with_text(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.text = Some(Color::from_hsla(color.into()));
self
}
pub fn with_dimmed(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.dimmed = Some(Color::from_hsla(color.into()));
self
}
pub fn with_border(mut self, color: impl Into<gpui::Hsla>) -> Self {
self.overrides.border = Some(Color::from_hsla(color.into()));
self
}
pub fn spacing(&self, size: Size) -> f32 {
self.spacing.get(size)
}
pub fn radius(&self, size: Size) -> f32 {
self.radius.get(size)
}
pub fn font_size(&self, size: Size) -> f32 {
self.font_size.get(size)
}
pub fn color(&self, name: ColorName, shade: usize) -> Color {
self.palette.get(name, shade)
}
pub fn primary_shade(&self) -> usize {
match self.scheme {
ColorScheme::Light => self.primary_shade_light,
ColorScheme::Dark => self.primary_shade_dark,
}
}
pub fn primary(&self) -> Color {
self.overrides
.primary
.unwrap_or_else(|| self.color(self.primary_color, self.primary_shade()))
}
pub fn body(&self) -> Color {
self.overrides.body.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.white,
ColorScheme::Dark => self.color(ColorName::Dark, 7),
})
}
pub fn surface(&self) -> Color {
self.overrides.surface.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.white,
ColorScheme::Dark => self.color(ColorName::Dark, 6),
})
}
pub fn surface_hover(&self) -> Color {
self.overrides
.surface_hover
.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.color(ColorName::Gray, 0),
ColorScheme::Dark => self.color(ColorName::Dark, 5),
})
}
pub fn text(&self) -> Color {
self.overrides.text.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.color(ColorName::Dark, 9),
ColorScheme::Dark => self.color(ColorName::Dark, 0),
})
}
pub fn dimmed(&self) -> Color {
self.overrides.dimmed.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.color(ColorName::Gray, 6),
ColorScheme::Dark => self.color(ColorName::Dark, 2),
})
}
pub fn border(&self) -> Color {
self.overrides.border.unwrap_or_else(|| match self.scheme {
ColorScheme::Light => self.color(ColorName::Gray, 3),
ColorScheme::Dark => self.color(ColorName::Dark, 4),
})
}
}
pub fn theme(cx: &App) -> &Theme {
cx.global::<Theme>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn semantic_override_replaces_scheme_default() {
let base = Theme::light();
let themed = Theme::light()
.with_primary(css::rgb(200, 10, 10))
.with_body(css::css("#0b0b0f").unwrap());
assert!(themed.overrides.primary.is_some());
assert_ne!(themed.primary(), base.primary());
assert_ne!(themed.body(), base.body());
assert_eq!(themed.text(), base.text());
}
}