use std::cell::Cell;
use std::collections::HashMap;
use crate::geometry::Color;
const MISSING_COLOR: Color = Color::rgb(255, 0, 255);
const MISSING_RADIUS: f32 = 0.0;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ColorToken(
pub &'static str,
);
impl ColorToken {
pub const SURFACE: ColorToken = ColorToken("surface");
pub const SURFACE_HOVER: ColorToken = ColorToken("surface_hover");
pub const SURFACE_PRESSED: ColorToken = ColorToken("surface_pressed");
pub const TEXT: ColorToken = ColorToken("text");
pub const TEXT_MUTED: ColorToken = ColorToken("text_muted");
pub const ACCENT: ColorToken = ColorToken("accent");
pub const ACCENT_HOVER: ColorToken = ColorToken("accent_hover");
pub const ACCENT_PRESSED: ColorToken = ColorToken("accent_pressed");
pub const ACCENT_TEXT: ColorToken = ColorToken("accent_text");
pub const BORDER: ColorToken = ColorToken("border");
pub const FOCUS_RING: ColorToken = ColorToken("focus_ring");
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RadiusToken(
pub &'static str,
);
impl RadiusToken {
pub const SMALL: RadiusToken = RadiusToken("small");
pub const MEDIUM: RadiusToken = RadiusToken("medium");
pub const LARGE: RadiusToken = RadiusToken("large");
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ColorValue {
Literal(Color),
Token(ColorToken),
}
impl From<Color> for ColorValue {
fn from(color: Color) -> ColorValue {
ColorValue::Literal(color)
}
}
impl From<ColorToken> for ColorValue {
fn from(token: ColorToken) -> ColorValue {
ColorValue::Token(token)
}
}
impl ColorValue {
pub fn resolve(&self, theme: &Theme) -> Color {
match self {
ColorValue::Literal(color) => *color,
ColorValue::Token(token) => theme.color(*token),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum RadiusValue {
Literal(f32),
Token(RadiusToken),
}
impl From<f32> for RadiusValue {
fn from(radius: f32) -> RadiusValue {
RadiusValue::Literal(radius)
}
}
impl From<RadiusToken> for RadiusValue {
fn from(token: RadiusToken) -> RadiusValue {
RadiusValue::Token(token)
}
}
impl Default for RadiusValue {
fn default() -> RadiusValue {
RadiusValue::Literal(0.0)
}
}
impl RadiusValue {
pub fn resolve(&self, theme: &Theme) -> f32 {
match self {
RadiusValue::Literal(radius) => *radius,
RadiusValue::Token(token) => theme.radius(*token),
}
}
}
pub struct Theme {
pub colors: HashMap<&'static str, Color>,
pub radii: HashMap<&'static str, f32>,
consulted: Cell<bool>,
}
impl Clone for Theme {
fn clone(&self) -> Theme {
Theme {
colors: self.colors.clone(),
radii: self.radii.clone(),
consulted: Cell::new(false),
}
}
}
impl PartialEq for Theme {
fn eq(&self, other: &Theme) -> bool {
self.colors == other.colors && self.radii == other.radii
}
}
impl std::fmt::Debug for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Theme")
.field("colors", &self.colors)
.field("radii", &self.radii)
.finish()
}
}
impl Theme {
pub fn light() -> Theme {
Theme::new(
[
(ColorToken::SURFACE, Color::WHITE),
(ColorToken::SURFACE_HOVER, Color::rgb(240, 240, 240)),
(ColorToken::SURFACE_PRESSED, Color::rgb(224, 224, 224)),
(ColorToken::TEXT, Color::rgb(26, 26, 26)),
(ColorToken::TEXT_MUTED, Color::rgb(102, 102, 102)),
(ColorToken::ACCENT, Color::rgb(0, 95, 204)),
(ColorToken::ACCENT_HOVER, Color::rgb(0, 84, 181)),
(ColorToken::ACCENT_PRESSED, Color::rgb(0, 71, 153)),
(ColorToken::ACCENT_TEXT, Color::WHITE),
(ColorToken::BORDER, Color::rgb(204, 204, 204)),
(ColorToken::FOCUS_RING, Color::rgb(0, 120, 215)),
],
[
(RadiusToken::SMALL, 2.0),
(RadiusToken::MEDIUM, 4.0),
(RadiusToken::LARGE, 8.0),
],
)
}
pub fn dark() -> Theme {
Theme::new(
[
(ColorToken::SURFACE, Color::rgb(30, 30, 30)),
(ColorToken::SURFACE_HOVER, Color::rgb(42, 42, 42)),
(ColorToken::SURFACE_PRESSED, Color::rgb(56, 56, 56)),
(ColorToken::TEXT, Color::rgb(240, 240, 240)),
(ColorToken::TEXT_MUTED, Color::rgb(153, 153, 153)),
(ColorToken::ACCENT, Color::rgb(77, 163, 255)),
(ColorToken::ACCENT_HOVER, Color::rgb(110, 180, 255)),
(ColorToken::ACCENT_PRESSED, Color::rgb(140, 196, 255)),
(ColorToken::ACCENT_TEXT, Color::rgb(11, 11, 11)),
(ColorToken::BORDER, Color::rgb(68, 68, 68)),
(ColorToken::FOCUS_RING, Color::rgb(77, 163, 255)),
],
[
(RadiusToken::SMALL, 2.0),
(RadiusToken::MEDIUM, 4.0),
(RadiusToken::LARGE, 8.0),
],
)
}
fn new<const C: usize, const R: usize>(
colors: [(ColorToken, Color); C],
radii: [(RadiusToken, f32); R],
) -> Theme {
Theme {
colors: colors
.into_iter()
.map(|(token, color)| (token.0, color))
.collect(),
radii: radii
.into_iter()
.map(|(token, radius)| (token.0, radius))
.collect(),
consulted: Cell::new(false),
}
}
pub fn color(&self, token: ColorToken) -> Color {
self.consulted.set(true);
match self.colors.get(token.0) {
Some(color) => *color,
None => {
debug_assert!(
false,
"the theme defines no color token named {:?}",
token.0
);
MISSING_COLOR
}
}
}
pub fn radius(&self, token: RadiusToken) -> f32 {
self.consulted.set(true);
match self.radii.get(token.0) {
Some(radius) => *radius,
None => {
debug_assert!(
false,
"the theme defines no radius token named {:?}",
token.0
);
MISSING_RADIUS
}
}
}
pub(crate) fn reset_consulted(&self) {
self.consulted.set(false);
}
pub(crate) fn consulted(&self) -> bool {
self.consulted.get()
}
}
#[cfg(test)]
#[path = "theme_tests.rs"]
mod tests;