1mod resolution;
2mod theme_registry;
3
4use gpui::{
5 color::Color,
6 elements::{ContainerStyle, ImageStyle, LabelStyle},
7 fonts::{HighlightStyle, TextStyle},
8 Border,
9};
10use serde::Deserialize;
11use std::{collections::HashMap, sync::Arc};
12
13pub use theme_registry::*;
14
15pub const DEFAULT_THEME_NAME: &'static str = "black";
16
17#[derive(Deserialize, Default)]
18pub struct Theme {
19 #[serde(default)]
20 pub name: String,
21 pub workspace: Workspace,
22 pub chat_panel: ChatPanel,
23 pub contacts_panel: ContactsPanel,
24 pub project_panel: ProjectPanel,
25 pub selector: Selector,
26 pub editor: EditorStyle,
27 pub find: Find,
28 pub project_diagnostics: ProjectDiagnostics,
29}
30
31#[derive(Deserialize, Default)]
32pub struct Workspace {
33 pub background: Color,
34 pub titlebar: Titlebar,
35 pub tab: Tab,
36 pub active_tab: Tab,
37 pub pane_divider: Border,
38 pub left_sidebar: Sidebar,
39 pub right_sidebar: Sidebar,
40 pub status_bar: StatusBar,
41}
42
43#[derive(Clone, Deserialize, Default)]
44pub struct Titlebar {
45 #[serde(flatten)]
46 pub container: ContainerStyle,
47 pub height: f32,
48 pub title: TextStyle,
49 pub avatar_width: f32,
50 pub avatar_ribbon: AvatarRibbon,
51 pub offline_icon: OfflineIcon,
52 pub share_icon_color: Color,
53 pub share_icon_active_color: Color,
54 pub avatar: ImageStyle,
55 pub sign_in_prompt: ContainedText,
56 pub hovered_sign_in_prompt: ContainedText,
57 pub outdated_warning: ContainedText,
58}
59
60#[derive(Clone, Deserialize, Default)]
61pub struct AvatarRibbon {
62 #[serde(flatten)]
63 pub container: ContainerStyle,
64 pub width: f32,
65 pub height: f32,
66}
67
68#[derive(Clone, Deserialize, Default)]
69pub struct OfflineIcon {
70 #[serde(flatten)]
71 pub container: ContainerStyle,
72 pub width: f32,
73 pub color: Color,
74}
75
76#[derive(Clone, Deserialize, Default)]
77pub struct Tab {
78 pub height: f32,
79 #[serde(flatten)]
80 pub container: ContainerStyle,
81 #[serde(flatten)]
82 pub label: LabelStyle,
83 pub spacing: f32,
84 pub icon_width: f32,
85 pub icon_close: Color,
86 pub icon_close_active: Color,
87 pub icon_dirty: Color,
88 pub icon_conflict: Color,
89}
90
91#[derive(Clone, Deserialize, Default)]
92pub struct Find {
93 #[serde(flatten)]
94 pub container: ContainerStyle,
95 pub editor: FindEditor,
96 pub invalid_editor: ContainerStyle,
97 pub mode_button_group: ContainerStyle,
98 pub mode_button: ContainedText,
99 pub active_mode_button: ContainedText,
100 pub hovered_mode_button: ContainedText,
101 pub active_hovered_mode_button: ContainedText,
102 pub match_background: Color,
103}
104
105#[derive(Clone, Deserialize, Default)]
106pub struct FindEditor {
107 #[serde(flatten)]
108 pub input: InputEditorStyle,
109 pub max_width: f32,
110}
111
112#[derive(Deserialize, Default)]
113pub struct Sidebar {
114 #[serde(flatten)]
115 pub container: ContainerStyle,
116 pub width: f32,
117 pub item: SidebarItem,
118 pub active_item: SidebarItem,
119 pub resize_handle: ContainerStyle,
120}
121
122#[derive(Deserialize, Default)]
123pub struct SidebarItem {
124 pub icon_color: Color,
125 pub icon_size: f32,
126 pub height: f32,
127}
128
129#[derive(Deserialize, Default)]
130pub struct StatusBar {
131 #[serde(flatten)]
132 pub container: ContainerStyle,
133 pub height: f32,
134 pub item_spacing: f32,
135 pub cursor_position: TextStyle,
136 pub diagnostic_message: TextStyle,
137}
138
139#[derive(Deserialize, Default)]
140pub struct ChatPanel {
141 #[serde(flatten)]
142 pub container: ContainerStyle,
143 pub message: ChatMessage,
144 pub pending_message: ChatMessage,
145 pub channel_select: ChannelSelect,
146 pub input_editor: InputEditorStyle,
147 pub sign_in_prompt: TextStyle,
148 pub hovered_sign_in_prompt: TextStyle,
149}
150
151#[derive(Debug, Deserialize, Default)]
152pub struct ProjectPanel {
153 #[serde(flatten)]
154 pub container: ContainerStyle,
155 pub entry: ProjectPanelEntry,
156 pub hovered_entry: ProjectPanelEntry,
157 pub selected_entry: ProjectPanelEntry,
158 pub hovered_selected_entry: ProjectPanelEntry,
159}
160
161#[derive(Debug, Deserialize, Default)]
162pub struct ProjectPanelEntry {
163 pub height: f32,
164 #[serde(flatten)]
165 pub container: ContainerStyle,
166 pub text: TextStyle,
167 pub icon_color: Color,
168 pub icon_size: f32,
169 pub icon_spacing: f32,
170}
171
172#[derive(Deserialize, Default)]
173pub struct ContactsPanel {
174 #[serde(flatten)]
175 pub container: ContainerStyle,
176 pub host_row_height: f32,
177 pub host_avatar: ImageStyle,
178 pub host_username: ContainedText,
179 pub tree_branch_width: f32,
180 pub tree_branch_color: Color,
181 pub shared_project: WorktreeRow,
182 pub hovered_shared_project: WorktreeRow,
183 pub unshared_project: WorktreeRow,
184 pub hovered_unshared_project: WorktreeRow,
185}
186
187#[derive(Deserialize, Default)]
188pub struct WorktreeRow {
189 #[serde(flatten)]
190 pub container: ContainerStyle,
191 pub height: f32,
192 pub name: ContainedText,
193 pub guest_avatar: ImageStyle,
194 pub guest_avatar_spacing: f32,
195}
196
197#[derive(Deserialize, Default)]
198pub struct ChatMessage {
199 #[serde(flatten)]
200 pub container: ContainerStyle,
201 pub body: TextStyle,
202 pub sender: ContainedText,
203 pub timestamp: ContainedText,
204}
205
206#[derive(Deserialize, Default)]
207pub struct ChannelSelect {
208 #[serde(flatten)]
209 pub container: ContainerStyle,
210 pub header: ChannelName,
211 pub item: ChannelName,
212 pub active_item: ChannelName,
213 pub hovered_item: ChannelName,
214 pub hovered_active_item: ChannelName,
215 pub menu: ContainerStyle,
216}
217
218#[derive(Deserialize, Default)]
219pub struct ChannelName {
220 #[serde(flatten)]
221 pub container: ContainerStyle,
222 pub hash: ContainedText,
223 pub name: TextStyle,
224}
225
226#[derive(Deserialize, Default)]
227pub struct Selector {
228 #[serde(flatten)]
229 pub container: ContainerStyle,
230 pub empty: ContainedLabel,
231 pub input_editor: InputEditorStyle,
232 pub item: ContainedLabel,
233 pub active_item: ContainedLabel,
234}
235
236#[derive(Clone, Debug, Deserialize, Default)]
237pub struct ContainedText {
238 #[serde(flatten)]
239 pub container: ContainerStyle,
240 #[serde(flatten)]
241 pub text: TextStyle,
242}
243
244#[derive(Clone, Deserialize, Default)]
245pub struct ContainedLabel {
246 #[serde(flatten)]
247 pub container: ContainerStyle,
248 #[serde(flatten)]
249 pub label: LabelStyle,
250}
251
252#[derive(Clone, Deserialize, Default)]
253pub struct ProjectDiagnostics {
254 #[serde(flatten)]
255 pub container: ContainerStyle,
256 pub empty_message: TextStyle,
257 pub status_bar_item: ContainedText,
258 pub tab_icon_width: f32,
259 pub tab_icon_spacing: f32,
260 pub tab_summary_spacing: f32,
261}
262
263#[derive(Clone, Deserialize, Default)]
264pub struct EditorStyle {
265 pub text: TextStyle,
266 #[serde(default)]
267 pub placeholder_text: Option<TextStyle>,
268 pub background: Color,
269 pub selection: SelectionStyle,
270 pub gutter_background: Color,
271 pub gutter_padding_factor: f32,
272 pub active_line_background: Color,
273 pub highlighted_line_background: Color,
274 pub line_number: Color,
275 pub line_number_active: Color,
276 pub guest_selections: Vec<SelectionStyle>,
277 pub syntax: Arc<SyntaxTheme>,
278 pub diagnostic_path_header: DiagnosticPathHeader,
279 pub diagnostic_header: DiagnosticHeader,
280 pub error_diagnostic: DiagnosticStyle,
281 pub invalid_error_diagnostic: DiagnosticStyle,
282 pub warning_diagnostic: DiagnosticStyle,
283 pub invalid_warning_diagnostic: DiagnosticStyle,
284 pub information_diagnostic: DiagnosticStyle,
285 pub invalid_information_diagnostic: DiagnosticStyle,
286 pub hint_diagnostic: DiagnosticStyle,
287 pub invalid_hint_diagnostic: DiagnosticStyle,
288}
289
290#[derive(Clone, Deserialize, Default)]
291pub struct DiagnosticPathHeader {
292 #[serde(flatten)]
293 pub container: ContainerStyle,
294 pub filename: ContainedText,
295 pub path: ContainedText,
296 pub text_scale_factor: f32,
297}
298
299#[derive(Clone, Deserialize, Default)]
300pub struct DiagnosticHeader {
301 #[serde(flatten)]
302 pub container: ContainerStyle,
303 pub message: ContainedLabel,
304 pub code: ContainedText,
305 pub text_scale_factor: f32,
306 pub icon_width_factor: f32,
307}
308
309#[derive(Clone, Deserialize, Default)]
310pub struct DiagnosticStyle {
311 pub message: LabelStyle,
312 #[serde(default)]
313 pub header: ContainerStyle,
314 pub text_scale_factor: f32,
315}
316
317#[derive(Clone, Copy, Default, Deserialize)]
318pub struct SelectionStyle {
319 pub cursor: Color,
320 pub selection: Color,
321}
322
323#[derive(Clone, Deserialize, Default)]
324pub struct InputEditorStyle {
325 #[serde(flatten)]
326 pub container: ContainerStyle,
327 pub text: TextStyle,
328 #[serde(default)]
329 pub placeholder_text: Option<TextStyle>,
330 pub selection: SelectionStyle,
331}
332
333impl EditorStyle {
334 pub fn placeholder_text(&self) -> &TextStyle {
335 self.placeholder_text.as_ref().unwrap_or(&self.text)
336 }
337
338 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
339 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
340 if style_ix == 0 {
341 &self.selection
342 } else {
343 &self.guest_selections[style_ix - 1]
344 }
345 }
346}
347
348impl InputEditorStyle {
349 pub fn as_editor(&self) -> EditorStyle {
350 let default_diagnostic_style = DiagnosticStyle {
351 message: self.text.clone().into(),
352 header: Default::default(),
353 text_scale_factor: 1.,
354 };
355 EditorStyle {
356 text: self.text.clone(),
357 placeholder_text: self.placeholder_text.clone(),
358 background: self
359 .container
360 .background_color
361 .unwrap_or(Color::transparent_black()),
362 selection: self.selection,
363 gutter_background: Default::default(),
364 gutter_padding_factor: Default::default(),
365 active_line_background: Default::default(),
366 highlighted_line_background: Default::default(),
367 line_number: Default::default(),
368 line_number_active: Default::default(),
369 guest_selections: Default::default(),
370 syntax: Default::default(),
371 diagnostic_path_header: DiagnosticPathHeader {
372 container: Default::default(),
373 filename: ContainedText {
374 container: Default::default(),
375 text: self.text.clone(),
376 },
377 path: ContainedText {
378 container: Default::default(),
379 text: self.text.clone(),
380 },
381 text_scale_factor: 1.,
382 },
383 diagnostic_header: DiagnosticHeader {
384 container: Default::default(),
385 message: ContainedLabel {
386 container: Default::default(),
387 label: self.text.clone().into(),
388 },
389 code: ContainedText {
390 container: Default::default(),
391 text: self.text.clone(),
392 },
393 icon_width_factor: Default::default(),
394 text_scale_factor: 1.,
395 },
396 error_diagnostic: default_diagnostic_style.clone(),
397 invalid_error_diagnostic: default_diagnostic_style.clone(),
398 warning_diagnostic: default_diagnostic_style.clone(),
399 invalid_warning_diagnostic: default_diagnostic_style.clone(),
400 information_diagnostic: default_diagnostic_style.clone(),
401 invalid_information_diagnostic: default_diagnostic_style.clone(),
402 hint_diagnostic: default_diagnostic_style.clone(),
403 invalid_hint_diagnostic: default_diagnostic_style.clone(),
404 }
405 }
406}
407
408#[derive(Default)]
409pub struct SyntaxTheme {
410 pub highlights: Vec<(String, HighlightStyle)>,
411}
412
413impl SyntaxTheme {
414 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
415 Self { highlights }
416 }
417}
418
419impl<'de> Deserialize<'de> for SyntaxTheme {
420 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
421 where
422 D: serde::Deserializer<'de>,
423 {
424 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
425
426 let mut result = Self::new(Vec::new());
427 for (key, style) in syntax_data {
428 match result
429 .highlights
430 .binary_search_by(|(needle, _)| needle.cmp(&key))
431 {
432 Ok(i) | Err(i) => {
433 result.highlights.insert(i, (key, style));
434 }
435 }
436 }
437
438 Ok(result)
439 }
440}