theme.rs

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