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 mode_button_group: ContainerStyle,
97 pub mode_button: ContainedText,
98 pub active_mode_button: ContainedText,
99 pub hovered_mode_button: ContainedText,
100 pub active_hovered_mode_button: ContainedText,
101 pub match_background: Color,
102}
103
104#[derive(Clone, Deserialize, Default)]
105pub struct FindEditor {
106 #[serde(flatten)]
107 pub input: InputEditorStyle,
108 pub max_width: f32,
109}
110
111#[derive(Deserialize, Default)]
112pub struct Sidebar {
113 #[serde(flatten)]
114 pub container: ContainerStyle,
115 pub width: f32,
116 pub item: SidebarItem,
117 pub active_item: SidebarItem,
118 pub resize_handle: ContainerStyle,
119}
120
121#[derive(Deserialize, Default)]
122pub struct SidebarItem {
123 pub icon_color: Color,
124 pub icon_size: f32,
125 pub height: f32,
126}
127
128#[derive(Deserialize, Default)]
129pub struct StatusBar {
130 #[serde(flatten)]
131 pub container: ContainerStyle,
132 pub height: f32,
133 pub item_spacing: f32,
134 pub cursor_position: TextStyle,
135 pub diagnostic_message: TextStyle,
136}
137
138#[derive(Deserialize, Default)]
139pub struct ChatPanel {
140 #[serde(flatten)]
141 pub container: ContainerStyle,
142 pub message: ChatMessage,
143 pub pending_message: ChatMessage,
144 pub channel_select: ChannelSelect,
145 pub input_editor: InputEditorStyle,
146 pub sign_in_prompt: TextStyle,
147 pub hovered_sign_in_prompt: TextStyle,
148}
149
150#[derive(Debug, Deserialize, Default)]
151pub struct ProjectPanel {
152 #[serde(flatten)]
153 pub container: ContainerStyle,
154 pub entry: ProjectPanelEntry,
155 pub hovered_entry: ProjectPanelEntry,
156 pub selected_entry: ProjectPanelEntry,
157 pub hovered_selected_entry: ProjectPanelEntry,
158}
159
160#[derive(Debug, Deserialize, Default)]
161pub struct ProjectPanelEntry {
162 pub height: f32,
163 #[serde(flatten)]
164 pub container: ContainerStyle,
165 pub text: TextStyle,
166 pub icon_color: Color,
167 pub icon_size: f32,
168 pub icon_spacing: f32,
169}
170
171#[derive(Deserialize, Default)]
172pub struct ContactsPanel {
173 #[serde(flatten)]
174 pub container: ContainerStyle,
175 pub host_row_height: f32,
176 pub host_avatar: ImageStyle,
177 pub host_username: ContainedText,
178 pub tree_branch_width: f32,
179 pub tree_branch_color: Color,
180 pub shared_project: WorktreeRow,
181 pub hovered_shared_project: WorktreeRow,
182 pub unshared_project: WorktreeRow,
183 pub hovered_unshared_project: WorktreeRow,
184}
185
186#[derive(Deserialize, Default)]
187pub struct WorktreeRow {
188 #[serde(flatten)]
189 pub container: ContainerStyle,
190 pub height: f32,
191 pub name: ContainedText,
192 pub guest_avatar: ImageStyle,
193 pub guest_avatar_spacing: f32,
194}
195
196#[derive(Deserialize, Default)]
197pub struct ChatMessage {
198 #[serde(flatten)]
199 pub container: ContainerStyle,
200 pub body: TextStyle,
201 pub sender: ContainedText,
202 pub timestamp: ContainedText,
203}
204
205#[derive(Deserialize, Default)]
206pub struct ChannelSelect {
207 #[serde(flatten)]
208 pub container: ContainerStyle,
209 pub header: ChannelName,
210 pub item: ChannelName,
211 pub active_item: ChannelName,
212 pub hovered_item: ChannelName,
213 pub hovered_active_item: ChannelName,
214 pub menu: ContainerStyle,
215}
216
217#[derive(Deserialize, Default)]
218pub struct ChannelName {
219 #[serde(flatten)]
220 pub container: ContainerStyle,
221 pub hash: ContainedText,
222 pub name: TextStyle,
223}
224
225#[derive(Deserialize, Default)]
226pub struct Selector {
227 #[serde(flatten)]
228 pub container: ContainerStyle,
229 pub empty: ContainedLabel,
230 pub input_editor: InputEditorStyle,
231 pub item: ContainedLabel,
232 pub active_item: ContainedLabel,
233}
234
235#[derive(Clone, Debug, Deserialize, Default)]
236pub struct ContainedText {
237 #[serde(flatten)]
238 pub container: ContainerStyle,
239 #[serde(flatten)]
240 pub text: TextStyle,
241}
242
243#[derive(Clone, Deserialize, Default)]
244pub struct ContainedLabel {
245 #[serde(flatten)]
246 pub container: ContainerStyle,
247 #[serde(flatten)]
248 pub label: LabelStyle,
249}
250
251#[derive(Clone, Deserialize, Default)]
252pub struct ProjectDiagnostics {
253 #[serde(flatten)]
254 pub container: ContainerStyle,
255 pub empty_message: TextStyle,
256 pub status_bar_item: ContainedText,
257 pub tab_icon_width: f32,
258 pub tab_icon_spacing: f32,
259 pub tab_summary_spacing: f32,
260}
261
262#[derive(Clone, Deserialize, Default)]
263pub struct EditorStyle {
264 pub text: TextStyle,
265 #[serde(default)]
266 pub placeholder_text: Option<TextStyle>,
267 pub background: Color,
268 pub selection: SelectionStyle,
269 pub gutter_background: Color,
270 pub gutter_padding_factor: f32,
271 pub active_line_background: Color,
272 pub highlighted_line_background: Color,
273 pub line_number: Color,
274 pub line_number_active: Color,
275 pub guest_selections: Vec<SelectionStyle>,
276 pub syntax: Arc<SyntaxTheme>,
277 pub diagnostic_path_header: DiagnosticPathHeader,
278 pub diagnostic_header: DiagnosticHeader,
279 pub error_diagnostic: DiagnosticStyle,
280 pub invalid_error_diagnostic: DiagnosticStyle,
281 pub warning_diagnostic: DiagnosticStyle,
282 pub invalid_warning_diagnostic: DiagnosticStyle,
283 pub information_diagnostic: DiagnosticStyle,
284 pub invalid_information_diagnostic: DiagnosticStyle,
285 pub hint_diagnostic: DiagnosticStyle,
286 pub invalid_hint_diagnostic: DiagnosticStyle,
287}
288
289#[derive(Clone, Deserialize, Default)]
290pub struct DiagnosticPathHeader {
291 #[serde(flatten)]
292 pub container: ContainerStyle,
293 pub filename: ContainedText,
294 pub path: ContainedText,
295 pub text_scale_factor: f32,
296}
297
298#[derive(Clone, Deserialize, Default)]
299pub struct DiagnosticHeader {
300 #[serde(flatten)]
301 pub container: ContainerStyle,
302 pub message: ContainedLabel,
303 pub code: ContainedText,
304 pub text_scale_factor: f32,
305 pub icon_width_factor: f32,
306}
307
308#[derive(Clone, Deserialize, Default)]
309pub struct DiagnosticStyle {
310 pub message: LabelStyle,
311 #[serde(default)]
312 pub header: ContainerStyle,
313 pub text_scale_factor: f32,
314}
315
316#[derive(Clone, Copy, Default, Deserialize)]
317pub struct SelectionStyle {
318 pub cursor: Color,
319 pub selection: Color,
320}
321
322#[derive(Clone, Deserialize, Default)]
323pub struct InputEditorStyle {
324 #[serde(flatten)]
325 pub container: ContainerStyle,
326 pub text: TextStyle,
327 #[serde(default)]
328 pub placeholder_text: Option<TextStyle>,
329 pub selection: SelectionStyle,
330}
331
332impl EditorStyle {
333 pub fn placeholder_text(&self) -> &TextStyle {
334 self.placeholder_text.as_ref().unwrap_or(&self.text)
335 }
336
337 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
338 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
339 if style_ix == 0 {
340 &self.selection
341 } else {
342 &self.guest_selections[style_ix - 1]
343 }
344 }
345}
346
347impl InputEditorStyle {
348 pub fn as_editor(&self) -> EditorStyle {
349 let default_diagnostic_style = DiagnosticStyle {
350 message: self.text.clone().into(),
351 header: Default::default(),
352 text_scale_factor: 1.,
353 };
354 EditorStyle {
355 text: self.text.clone(),
356 placeholder_text: self.placeholder_text.clone(),
357 background: self
358 .container
359 .background_color
360 .unwrap_or(Color::transparent_black()),
361 selection: self.selection,
362 gutter_background: Default::default(),
363 gutter_padding_factor: Default::default(),
364 active_line_background: Default::default(),
365 highlighted_line_background: Default::default(),
366 line_number: Default::default(),
367 line_number_active: Default::default(),
368 guest_selections: Default::default(),
369 syntax: Default::default(),
370 diagnostic_path_header: DiagnosticPathHeader {
371 container: Default::default(),
372 filename: ContainedText {
373 container: Default::default(),
374 text: self.text.clone(),
375 },
376 path: ContainedText {
377 container: Default::default(),
378 text: self.text.clone(),
379 },
380 text_scale_factor: 1.,
381 },
382 diagnostic_header: DiagnosticHeader {
383 container: Default::default(),
384 message: ContainedLabel {
385 container: Default::default(),
386 label: self.text.clone().into(),
387 },
388 code: ContainedText {
389 container: Default::default(),
390 text: self.text.clone(),
391 },
392 icon_width_factor: Default::default(),
393 text_scale_factor: 1.,
394 },
395 error_diagnostic: default_diagnostic_style.clone(),
396 invalid_error_diagnostic: default_diagnostic_style.clone(),
397 warning_diagnostic: default_diagnostic_style.clone(),
398 invalid_warning_diagnostic: default_diagnostic_style.clone(),
399 information_diagnostic: default_diagnostic_style.clone(),
400 invalid_information_diagnostic: default_diagnostic_style.clone(),
401 hint_diagnostic: default_diagnostic_style.clone(),
402 invalid_hint_diagnostic: default_diagnostic_style.clone(),
403 }
404 }
405}
406
407#[derive(Default)]
408pub struct SyntaxTheme {
409 pub highlights: Vec<(String, HighlightStyle)>,
410}
411
412impl SyntaxTheme {
413 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
414 Self { highlights }
415 }
416}
417
418impl<'de> Deserialize<'de> for SyntaxTheme {
419 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
420 where
421 D: serde::Deserializer<'de>,
422 {
423 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
424
425 let mut result = Self::new(Vec::new());
426 for (key, style) in syntax_data {
427 match result
428 .highlights
429 .binary_search_by(|(needle, _)| needle.cmp(&key))
430 {
431 Ok(i) | Err(i) => {
432 result.highlights.insert(i, (key, style));
433 }
434 }
435 }
436
437 Ok(result)
438 }
439}