glassy_ui/theme/
tokens.rs1#![allow(clippy::unusual_byte_groupings)]
3
4use gpui::{rgba, Hsla};
5
6pub const FONT_FAMILY: &str = "Inter Variable";
8
9#[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#[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
126pub fn rgb(value: u32) -> Hsla {
128 gpui::rgb(value).into()
129}
130
131pub 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}