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