Skip to main content

gpui_cn/theme/
config.rs

1use gpui_kit::{Hsla, SharedString};
2
3use super::color::hex;
4
5/// The inputs a theme is built from. Every token components read is derived
6/// from these, so this is the whole customization surface for colors.
7///
8/// A surface, an ink, a brand accent, and a contrast that says how far
9/// secondary surfaces and borders move from the surface toward the ink.
10#[derive(Clone, Debug, PartialEq)]
11#[non_exhaustive]
12pub struct ThemeConfig {
13    /// The window background.
14    pub surface: Hsla,
15    /// Text and strong strokes.
16    pub ink: Hsla,
17    /// The brand color: focus ring, links, toggles, sliders, selection.
18    pub accent: Hsla,
19    /// `0..=100`. 50 gives the shadcn neutral ratios.
20    pub contrast: u8,
21    /// Font families. `None` uses the platform default.
22    pub fonts: ThemeFonts,
23    /// Colors that carry a meaning of their own.
24    pub semantic: SemanticColors,
25}
26
27/// Font families for a theme. `None` means the platform default.
28#[derive(Clone, Debug, PartialEq)]
29#[non_exhaustive]
30pub struct ThemeFonts {
31    /// Interface text.
32    pub ui: Option<SharedString>,
33    /// Code, identifiers, and aligned numbers.
34    pub code: Option<SharedString>,
35}
36
37impl Default for ThemeFonts {
38    /// The platform interface font, and the bundled code font when one
39    /// ships (see [`super::fonts`]).
40    fn default() -> Self {
41        Self {
42            ui: None,
43            code: super::fonts::default_code_font(),
44        }
45    }
46}
47
48/// Colors with a fixed meaning, independent of surface and ink.
49#[derive(Clone, Debug, PartialEq)]
50#[non_exhaustive]
51pub struct SemanticColors {
52    /// Completed, added, positive.
53    pub success: Hsla,
54    /// Destructive actions, removed, errors.
55    pub destructive: Hsla,
56    /// Needs attention but not an error.
57    pub warning: Hsla,
58    /// Skills, plugins, and other extension surfaces.
59    pub skill: Hsla,
60}
61
62impl Default for ThemeConfig {
63    fn default() -> Self {
64        Self::light()
65    }
66}
67
68impl Default for SemanticColors {
69    fn default() -> Self {
70        Self::light()
71    }
72}
73
74impl ThemeConfig {
75    /// The built-in light theme.
76    pub fn light() -> Self {
77        Self {
78            surface: hex("#ffffff"),
79            ink: hex("#1a1c1f"),
80            accent: hex("#339cff"),
81            contrast: 45,
82            fonts: ThemeFonts::default(),
83            semantic: SemanticColors::light(),
84        }
85    }
86
87    /// The built-in dark theme.
88    pub fn dark() -> Self {
89        Self {
90            surface: hex("#181818"),
91            ink: hex("#ffffff"),
92            // The reference lifts its accent on a dark surface: its
93            // toggles and checkboxes sample as #539af8, not #339cff.
94            accent: hex("#539af8"),
95            contrast: 60,
96            fonts: ThemeFonts::default(),
97            semantic: SemanticColors::dark(),
98        }
99    }
100}
101
102impl SemanticColors {
103    /// The built-in light values.
104    pub fn light() -> Self {
105        Self {
106            success: hex("#00a240"),
107            destructive: hex("#ba2623"),
108            warning: hex("#b45309"),
109            skill: hex("#924ff7"),
110        }
111    }
112
113    /// The built-in dark values.
114    pub fn dark() -> Self {
115        Self {
116            success: hex("#40c977"),
117            destructive: hex("#fa423e"),
118            warning: hex("#f59e0b"),
119            skill: hex("#ad7bf9"),
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn light_and_dark_differ_in_surface_and_accent() {
130        let light = ThemeConfig::light();
131        let dark = ThemeConfig::dark();
132        assert_ne!(light.accent, dark.accent);
133        assert_ne!(light.surface, dark.surface);
134        assert_eq!(light.contrast, 45);
135        assert_eq!(dark.contrast, 60);
136        assert_eq!(ThemeConfig::default(), light);
137    }
138}