use std::{cell::RefCell, rc::Rc};
use crate::element::style::Color;
#[derive(Clone, Debug, PartialEq)]
pub struct Theme {
pub colors: ColorTokens,
pub typography: TypographyTokens,
pub spacing: SpacingTokens,
pub radius: RadiusTokens,
}
impl Theme {
pub fn default_light() -> Self {
Self {
colors: ColorTokens {
background: Color::rgb8(248, 250, 252),
surface: Color::rgb8(255, 255, 255),
foreground: Color::rgb8(15, 23, 42),
foreground_secondary: Color::rgb8(71, 85, 105),
accent: Color::rgb8(59, 130, 246),
accent_hover: Color::rgb8(37, 99, 235),
error: Color::rgb8(220, 38, 38),
border: Color::rgb8(203, 213, 225),
},
typography: TypographyTokens {
font_size_sm: 12.0,
font_size_md: 14.0,
font_size_lg: 18.0,
font_size_xl: 24.0,
line_height: 1.5,
},
spacing: SpacingTokens {
xs: 4.0,
sm: 8.0,
md: 12.0,
lg: 16.0,
xl: 24.0,
},
radius: RadiusTokens {
sm: 4.0,
md: 8.0,
lg: 12.0,
},
}
}
pub fn default_dark() -> Self {
Self {
colors: ColorTokens {
background: Color::rgb8(17, 24, 39),
surface: Color::rgb8(35, 35, 52),
foreground: Color::rgb8(235, 235, 240),
foreground_secondary: Color::rgb8(156, 163, 175),
accent: Color::rgb8(59, 130, 246),
accent_hover: Color::rgb8(37, 99, 235),
error: Color::rgb8(248, 113, 113),
border: Color::rgb8(80, 80, 110),
},
typography: TypographyTokens {
font_size_sm: 12.0,
font_size_md: 14.0,
font_size_lg: 18.0,
font_size_xl: 24.0,
line_height: 1.5,
},
spacing: SpacingTokens {
xs: 4.0,
sm: 8.0,
md: 12.0,
lg: 16.0,
xl: 24.0,
},
radius: RadiusTokens {
sm: 4.0,
md: 8.0,
lg: 12.0,
},
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ColorTokens {
pub background: Color,
pub surface: Color,
pub foreground: Color,
pub foreground_secondary: Color,
pub accent: Color,
pub accent_hover: Color,
pub error: Color,
pub border: Color,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypographyTokens {
pub font_size_sm: f32,
pub font_size_md: f32,
pub font_size_lg: f32,
pub font_size_xl: f32,
pub line_height: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SpacingTokens {
pub xs: f32,
pub sm: f32,
pub md: f32,
pub lg: f32,
pub xl: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RadiusTokens {
pub sm: f32,
pub md: f32,
pub lg: f32,
}
thread_local! {
static ACTIVE_THEME: RefCell<Rc<Theme>> = RefCell::new(Rc::new(Theme::default_light()));
}
pub fn set_active_theme(theme: Theme) {
ACTIVE_THEME.with(|active| {
*active.borrow_mut() = Rc::new(theme);
});
}
pub fn current_theme() -> Theme {
ACTIVE_THEME.with(|active| active.borrow().as_ref().clone())
}
#[cfg(test)]
mod tests {
use super::{current_theme, set_active_theme, Theme};
#[test]
fn active_theme_defaults_to_light_on_new_thread() {
let from_thread = std::thread::spawn(current_theme)
.join()
.expect("theme thread should join");
assert_eq!(from_thread, Theme::default_light());
}
#[test]
fn active_theme_roundtrip_works() {
let previous = current_theme();
let dark = Theme::default_dark();
set_active_theme(dark.clone());
assert_eq!(current_theme(), dark);
set_active_theme(previous);
}
}