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