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)]
41pub struct Theme {
42 pub colors: ThemeColor,
43 pub highlight_theme: Arc<HighlightTheme>,
44 pub light_theme: Rc<ThemeConfig>,
45 pub dark_theme: Rc<ThemeConfig>,
46
47 pub mode: ThemeMode,
48 pub font_family: SharedString,
50 pub font_size: Pixels,
52 pub mono_font_family: SharedString,
60 pub mono_font_size: Pixels,
62 pub radius: Pixels,
64 pub radius_lg: Pixels,
66 pub shadow: bool,
67 pub transparent: Hsla,
68 pub scrollbar_show: ScrollbarShow,
70 pub tile_grid_size: Pixels,
72 pub tile_shadow: bool,
74 pub tile_radius: Pixels,
76}
77
78impl Default for Theme {
79 fn default() -> Self {
80 Self::from(&ThemeColor::default())
81 }
82}
83
84impl Deref for Theme {
85 type Target = ThemeColor;
86
87 fn deref(&self) -> &Self::Target {
88 &self.colors
89 }
90}
91
92impl DerefMut for Theme {
93 fn deref_mut(&mut self) -> &mut Self::Target {
94 &mut self.colors
95 }
96}
97
98impl Global for Theme {}
99
100impl Theme {
101 #[inline(always)]
103 pub fn global(cx: &App) -> &Theme {
104 cx.global::<Theme>()
105 }
106
107 #[inline(always)]
109 pub fn global_mut(cx: &mut App) -> &mut Theme {
110 cx.global_mut::<Theme>()
111 }
112
113 #[inline(always)]
115 pub fn is_dark(&self) -> bool {
116 self.mode.is_dark()
117 }
118
119 pub fn theme_name(&self) -> &SharedString {
121 if self.is_dark() {
122 &self.dark_theme.name
123 } else {
124 &self.light_theme.name
125 }
126 }
127
128 pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
130 let appearance = window
133 .as_ref()
134 .map(|window| window.appearance())
135 .unwrap_or_else(|| cx.window_appearance());
136
137 Self::change(appearance, window, cx);
138 }
139
140 pub fn sync_scrollbar_appearance(cx: &mut App) {
142 Theme::global_mut(cx).scrollbar_show = if cx.should_auto_hide_scrollbars() {
143 ScrollbarShow::Scrolling
144 } else {
145 ScrollbarShow::Hover
146 };
147 }
148
149 pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {
151 let mode = mode.into();
152 if !cx.has_global::<Theme>() {
153 let mut theme = Theme::default();
154 theme.light_theme = ThemeRegistry::global(cx).default_light_theme().clone();
155 theme.dark_theme = ThemeRegistry::global(cx).default_dark_theme().clone();
156 cx.set_global(theme);
157 }
158
159 let theme = cx.global_mut::<Theme>();
160 theme.mode = mode;
161 if mode.is_dark() {
162 theme.apply_config(&theme.dark_theme.clone());
163 } else {
164 theme.apply_config(&theme.light_theme.clone());
165 }
166
167 if let Some(window) = window {
168 window.refresh();
169 }
170 }
171
172 #[inline]
174 pub(crate) fn editor_background(&self) -> Hsla {
175 self.highlight_theme
176 .style
177 .editor_background
178 .unwrap_or(self.background)
179 }
180}
181
182impl From<&ThemeColor> for Theme {
183 fn from(colors: &ThemeColor) -> Self {
184 Theme {
185 mode: ThemeMode::default(),
186 transparent: Hsla::transparent_black(),
187 font_family: ".SystemUIFont".into(),
188 font_size: px(16.),
189 mono_font_family: if cfg!(target_os = "macos") {
190 "Menlo".into()
192 } else if cfg!(target_os = "windows") {
193 "Consolas".into()
194 } else {
195 "DejaVu Sans Mono".into()
196 },
197 mono_font_size: px(13.),
198 radius: px(6.),
199 radius_lg: px(8.),
200 shadow: true,
201 scrollbar_show: ScrollbarShow::default(),
202 tile_grid_size: px(8.),
203 tile_shadow: true,
204 tile_radius: px(0.),
205 colors: *colors,
206 light_theme: Rc::new(ThemeConfig::default()),
207 dark_theme: Rc::new(ThemeConfig::default()),
208 highlight_theme: HighlightTheme::default_light(),
209 }
210 }
211}
212
213#[derive(
214 Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash, Serialize, Deserialize, JsonSchema,
215)]
216#[serde(rename_all = "snake_case")]
217pub enum ThemeMode {
218 #[default]
219 Light,
220 Dark,
221}
222
223impl ThemeMode {
224 #[inline(always)]
225 pub fn is_dark(&self) -> bool {
226 matches!(self, Self::Dark)
227 }
228
229 pub fn name(&self) -> &'static str {
231 match self {
232 ThemeMode::Light => "light",
233 ThemeMode::Dark => "dark",
234 }
235 }
236}
237
238impl From<WindowAppearance> for ThemeMode {
239 fn from(appearance: WindowAppearance) -> Self {
240 match appearance {
241 WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
242 WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
243 }
244 }
245}