gpui_component/theme/
mod.rs1use crate::{highlighter::HighlightTheme, scroll::ScrollbarShow};
2use gpui::{px, App, Global, Hsla, Pixels, SharedString, Window, WindowAppearance};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::{
6 ops::{Deref, DerefMut},
7 rc::Rc,
8 sync::Arc,
9};
10
11mod color;
12mod registry;
13mod schema;
14mod theme_color;
15
16pub use color::*;
17pub use registry::*;
18pub use schema::*;
19pub use theme_color::*;
20
21pub fn init(cx: &mut App) {
22 registry::init(cx);
23
24 Theme::sync_system_appearance(None, cx);
25 Theme::sync_scrollbar_appearance(cx);
26}
27
28pub trait ActiveTheme {
29 fn theme(&self) -> &Theme;
30}
31
32impl ActiveTheme for App {
33 #[inline(always)]
34 fn theme(&self) -> &Theme {
35 Theme::global(self)
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40pub struct Theme {
41 pub colors: ThemeColor,
42 pub highlight_theme: Arc<HighlightTheme>,
43 pub light_theme: Rc<ThemeConfig>,
44 pub dark_theme: Rc<ThemeConfig>,
45
46 pub mode: ThemeMode,
47 pub font_family: SharedString,
48 pub font_size: Pixels,
49 pub radius: Pixels,
51 pub radius_lg: Pixels,
53 pub shadow: bool,
54 pub transparent: Hsla,
55 pub scrollbar_show: ScrollbarShow,
57 pub tile_grid_size: Pixels,
59 pub tile_shadow: bool,
61 pub tile_radius: Pixels,
63}
64
65impl Default for Theme {
66 fn default() -> Self {
67 Self::from(&ThemeColor::default())
68 }
69}
70
71impl Deref for Theme {
72 type Target = ThemeColor;
73
74 fn deref(&self) -> &Self::Target {
75 &self.colors
76 }
77}
78
79impl DerefMut for Theme {
80 fn deref_mut(&mut self) -> &mut Self::Target {
81 &mut self.colors
82 }
83}
84
85impl Global for Theme {}
86
87impl Theme {
88 #[inline(always)]
90 pub fn global(cx: &App) -> &Theme {
91 cx.global::<Theme>()
92 }
93
94 #[inline(always)]
96 pub fn global_mut(cx: &mut App) -> &mut Theme {
97 cx.global_mut::<Theme>()
98 }
99
100 #[inline(always)]
102 pub fn is_dark(&self) -> bool {
103 self.mode.is_dark()
104 }
105
106 pub fn theme_name(&self) -> &SharedString {
108 if self.is_dark() {
109 &self.dark_theme.name
110 } else {
111 &self.light_theme.name
112 }
113 }
114
115 pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
133 let appearance = window
136 .as_ref()
137 .map(|window| window.appearance())
138 .unwrap_or_else(|| cx.window_appearance());
139
140 Self::change(appearance, window, cx);
141 }
142
143 pub fn sync_scrollbar_appearance(cx: &mut App) {
145 Theme::global_mut(cx).scrollbar_show = if cx.should_auto_hide_scrollbars() {
146 ScrollbarShow::Scrolling
147 } else {
148 ScrollbarShow::Hover
149 };
150 }
151
152 pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {
153 let mode = mode.into();
154 if !cx.has_global::<Theme>() {
155 let mut theme = Theme::default();
156 theme.light_theme = ThemeRegistry::global(cx).default_light_theme().clone();
157 theme.dark_theme = ThemeRegistry::global(cx).default_dark_theme().clone();
158 cx.set_global(theme);
159 }
160
161 let theme = cx.global_mut::<Theme>();
162 theme.mode = mode;
163 if mode.is_dark() {
164 theme.apply_config(&theme.dark_theme.clone());
165 } else {
166 theme.apply_config(&theme.light_theme.clone());
167 }
168
169 if let Some(window) = window {
170 window.refresh();
171 }
172 }
173
174 #[inline]
176 pub(crate) fn editor_background(&self) -> Hsla {
177 self.highlight_theme
178 .style
179 .editor_background
180 .unwrap_or(self.background)
181 }
182}
183
184impl From<&ThemeColor> for Theme {
185 fn from(colors: &ThemeColor) -> Self {
186 Theme {
187 mode: ThemeMode::default(),
188 transparent: Hsla::transparent_black(),
189 font_size: px(16.),
190 font_family: if cfg!(target_os = "macos") {
191 ".SystemUIFont".into()
192 } else if cfg!(target_os = "windows") {
193 "Segoe UI".into()
194 } else {
195 "FreeMono".into()
196 },
197 radius: px(6.),
198 radius_lg: px(8.),
199 shadow: true,
200 scrollbar_show: ScrollbarShow::default(),
201 tile_grid_size: px(8.),
202 tile_shadow: true,
203 tile_radius: px(0.),
204 colors: *colors,
205 light_theme: Rc::new(ThemeConfig::default()),
206 dark_theme: Rc::new(ThemeConfig::default()),
207 highlight_theme: HighlightTheme::default_light(),
208 }
209 }
210}
211
212#[derive(
213 Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash, Serialize, Deserialize, JsonSchema,
214)]
215#[serde(rename_all = "snake_case")]
216pub enum ThemeMode {
217 #[default]
218 Light,
219 Dark,
220}
221
222impl ThemeMode {
223 #[inline(always)]
224 pub fn is_dark(&self) -> bool {
225 matches!(self, Self::Dark)
226 }
227
228 pub fn name(&self) -> &'static str {
230 match self {
231 ThemeMode::Light => "light",
232 ThemeMode::Dark => "dark",
233 }
234 }
235}
236
237impl From<WindowAppearance> for ThemeMode {
238 fn from(appearance: WindowAppearance) -> Self {
239 match appearance {
240 WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
241 WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
242 }
243 }
244}