Skip to main content

glassy_ui/theme/
tokens.rs

1//! Paper hex+alpha is grouped as `RRGGBB_AA`.
2#![allow(clippy::unusual_byte_groupings)]
3
4use gpui::{rgba, Hsla};
5
6/// Typeface used on the Paper kit pages.
7pub const FONT_FAMILY: &str = "Inter Variable";
8
9/// Light or dark kit surface.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum ThemeKind {
12    #[default]
13    Light,
14    Dark,
15}
16
17impl ThemeKind {
18    pub fn as_str(self) -> &'static str {
19        match self {
20            Self::Light => "light",
21            Self::Dark => "dark",
22        }
23    }
24
25    pub fn parse(name: &str) -> Option<Self> {
26        match name.trim().to_ascii_lowercase().as_str() {
27            "light" => Some(Self::Light),
28            "dark" => Some(Self::Dark),
29            _ => None,
30        }
31    }
32
33    pub fn toggle(self) -> Self {
34        match self {
35            Self::Light => Self::Dark,
36            Self::Dark => Self::Light,
37        }
38    }
39}
40
41/// Semantic colors for the active scheme.
42///
43/// Build one with [`Theme::light`] / [`Theme::dark`], tweak public fields,
44/// then [`crate::ActiveTheme::set_theme`].
45#[derive(Clone, Copy, Debug, PartialEq)]
46pub struct Theme {
47    pub kind: ThemeKind,
48    pub font_family: &'static str,
49    pub canvas: Hsla,
50    pub heading: Hsla,
51    pub body: Hsla,
52    pub label: Hsla,
53    pub on_solid: Hsla,
54    pub ink: Hsla,
55    pub placeholder: Hsla,
56    pub destructive: Hsla,
57    pub destructive_soft: Hsla,
58}
59
60impl Theme {
61    pub fn light() -> Self {
62        Self {
63            kind: ThemeKind::Light,
64            font_family: FONT_FAMILY,
65            canvas: rgb(0xE8EAEE),
66            heading: rgb(0x18181B),
67            body: rgb(0x71717A),
68            label: rgb(0xA1A1AA),
69            on_solid: rgb(0xFAFAFA),
70            ink: rgb(0x18181B),
71            placeholder: rgb(0xA1A1AA),
72            destructive: rgb(0xDC2626),
73            destructive_soft: rgb(0xDC2626),
74        }
75    }
76
77    pub fn dark() -> Self {
78        Self {
79            kind: ThemeKind::Dark,
80            font_family: FONT_FAMILY,
81            canvas: rgb(0x0B0C0F),
82            heading: rgb(0xFAFAFA),
83            body: rgb(0xA1A1AA),
84            label: rgb(0x71717A),
85            on_solid: rgb(0xFAFAFA),
86            ink: rgb(0xFAFAFA),
87            placeholder: rgb(0x71717A),
88            destructive: rgb(0xF87171),
89            destructive_soft: rgb(0xFCA5A5),
90        }
91    }
92
93    pub fn for_kind(kind: ThemeKind) -> Self {
94        match kind {
95            ThemeKind::Light => Self::light(),
96            ThemeKind::Dark => Self::dark(),
97        }
98    }
99
100    pub fn named(name: &str) -> Option<Self> {
101        ThemeKind::parse(name).map(Self::for_kind)
102    }
103
104    pub fn toggle(self) -> Self {
105        Self::for_kind(self.kind.toggle())
106    }
107
108    pub fn is_dark(self) -> bool {
109        self.kind == ThemeKind::Dark
110    }
111
112    pub fn muted_fg(self) -> Hsla {
113        match self.kind {
114            ThemeKind::Light => rgb(0xA1A1AA),
115            ThemeKind::Dark => rgb(0x71717A),
116        }
117    }
118}
119
120impl Default for Theme {
121    fn default() -> Self {
122        Self::light()
123    }
124}
125
126/// Opaque `0xRRGGBB`.
127pub fn rgb(value: u32) -> Hsla {
128    gpui::rgb(value).into()
129}
130
131/// `0xRRGGBBAA`.
132pub fn paint(value: u32) -> Hsla {
133    rgba(value).into()
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn light_and_dark_match_paper() {
142        let light = Theme::light();
143        assert_eq!(light.canvas, rgb(0xE8EAEE));
144        assert_eq!(light.heading, rgb(0x18181B));
145        assert_eq!(light.placeholder, rgb(0xA1A1AA));
146        assert!(!light.is_dark());
147
148        let dark = Theme::dark();
149        assert_eq!(dark.canvas, rgb(0x0B0C0F));
150        assert_eq!(dark.heading, rgb(0xFAFAFA));
151        assert_eq!(dark.placeholder, rgb(0x71717A));
152        assert!(dark.is_dark());
153    }
154
155    #[test]
156    fn named_and_toggle() {
157        assert_eq!(Theme::named("dark").unwrap().kind, ThemeKind::Dark);
158        assert_eq!(Theme::named("LIGHT").unwrap().kind, ThemeKind::Light);
159        assert!(Theme::named("midnight").is_none());
160        assert_eq!(ThemeKind::Light.toggle(), ThemeKind::Dark);
161        assert_eq!(Theme::light().toggle().kind, ThemeKind::Dark);
162    }
163}