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