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