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 people_panel: PeoplePanel,
24 pub project_panel: ProjectPanel,
25 pub selector: Selector,
26 pub editor: EditorStyle,
27}
28
29#[derive(Deserialize, Default)]
30pub struct Workspace {
31 pub background: Color,
32 pub titlebar: Titlebar,
33 pub tab: Tab,
34 pub active_tab: Tab,
35 pub pane_divider: Border,
36 pub left_sidebar: Sidebar,
37 pub right_sidebar: Sidebar,
38}
39
40#[derive(Clone, Deserialize, Default)]
41pub struct Titlebar {
42 #[serde(flatten)]
43 pub container: ContainerStyle,
44 pub title: TextStyle,
45 pub avatar_width: f32,
46 pub offline_icon: OfflineIcon,
47 pub icon_color: Color,
48 pub avatar: ImageStyle,
49 pub outdated_warning: ContainedText,
50}
51
52#[derive(Clone, Deserialize, Default)]
53pub struct OfflineIcon {
54 #[serde(flatten)]
55 pub container: ContainerStyle,
56 pub width: f32,
57}
58
59#[derive(Clone, Deserialize, Default)]
60pub struct Tab {
61 pub height: f32,
62 #[serde(flatten)]
63 pub container: ContainerStyle,
64 #[serde(flatten)]
65 pub label: LabelStyle,
66 pub spacing: f32,
67 pub icon_width: f32,
68 pub icon_close: Color,
69 pub icon_close_active: Color,
70 pub icon_dirty: Color,
71 pub icon_conflict: Color,
72}
73
74#[derive(Deserialize, Default)]
75pub struct Sidebar {
76 #[serde(flatten)]
77 pub container: ContainerStyle,
78 pub width: f32,
79 pub item: SidebarItem,
80 pub active_item: SidebarItem,
81 pub resize_handle: ContainerStyle,
82}
83
84#[derive(Deserialize, Default)]
85pub struct SidebarItem {
86 pub icon_color: Color,
87 pub icon_size: f32,
88 pub height: f32,
89}
90
91#[derive(Deserialize, Default)]
92pub struct ChatPanel {
93 #[serde(flatten)]
94 pub container: ContainerStyle,
95 pub message: ChatMessage,
96 pub pending_message: ChatMessage,
97 pub channel_select: ChannelSelect,
98 pub input_editor: InputEditorStyle,
99 pub sign_in_prompt: TextStyle,
100 pub hovered_sign_in_prompt: TextStyle,
101}
102
103#[derive(Debug, Deserialize, Default)]
104pub struct ProjectPanel {
105 #[serde(flatten)]
106 pub container: ContainerStyle,
107 pub entry: ProjectPanelEntry,
108 pub hovered_entry: ProjectPanelEntry,
109 pub selected_entry: ProjectPanelEntry,
110 pub hovered_selected_entry: ProjectPanelEntry,
111}
112
113#[derive(Debug, Deserialize, Default)]
114pub struct ProjectPanelEntry {
115 pub height: f32,
116 #[serde(flatten)]
117 pub container: ContainerStyle,
118 pub text: TextStyle,
119 pub icon_color: Color,
120 pub icon_size: f32,
121 pub icon_spacing: f32,
122}
123
124#[derive(Deserialize, Default)]
125pub struct PeoplePanel {
126 #[serde(flatten)]
127 pub container: ContainerStyle,
128 pub host_row_height: f32,
129 pub host_avatar: ImageStyle,
130 pub host_username: ContainedText,
131 pub tree_branch_width: f32,
132 pub tree_branch_color: Color,
133 pub shared_worktree: WorktreeRow,
134 pub hovered_shared_worktree: WorktreeRow,
135 pub unshared_worktree: WorktreeRow,
136 pub hovered_unshared_worktree: WorktreeRow,
137}
138
139#[derive(Deserialize, Default)]
140pub struct WorktreeRow {
141 #[serde(flatten)]
142 pub container: ContainerStyle,
143 pub height: f32,
144 pub name: ContainedText,
145 pub guest_avatar: ImageStyle,
146 pub guest_avatar_spacing: f32,
147}
148
149#[derive(Deserialize, Default)]
150pub struct ChatMessage {
151 #[serde(flatten)]
152 pub container: ContainerStyle,
153 pub body: TextStyle,
154 pub sender: ContainedText,
155 pub timestamp: ContainedText,
156}
157
158#[derive(Deserialize, Default)]
159pub struct ChannelSelect {
160 #[serde(flatten)]
161 pub container: ContainerStyle,
162 pub header: ChannelName,
163 pub item: ChannelName,
164 pub active_item: ChannelName,
165 pub hovered_item: ChannelName,
166 pub hovered_active_item: ChannelName,
167 pub menu: ContainerStyle,
168}
169
170#[derive(Deserialize, Default)]
171pub struct ChannelName {
172 #[serde(flatten)]
173 pub container: ContainerStyle,
174 pub hash: ContainedText,
175 pub name: TextStyle,
176}
177
178#[derive(Deserialize, Default)]
179pub struct Selector {
180 #[serde(flatten)]
181 pub container: ContainerStyle,
182 pub empty: ContainedLabel,
183 pub input_editor: InputEditorStyle,
184 pub item: ContainedLabel,
185 pub active_item: ContainedLabel,
186}
187
188#[derive(Clone, Debug, Deserialize, Default)]
189pub struct ContainedText {
190 #[serde(flatten)]
191 pub container: ContainerStyle,
192 #[serde(flatten)]
193 pub text: TextStyle,
194}
195
196#[derive(Deserialize, Default)]
197pub struct ContainedLabel {
198 #[serde(flatten)]
199 pub container: ContainerStyle,
200 #[serde(flatten)]
201 pub label: LabelStyle,
202}
203
204#[derive(Clone, Deserialize, Default)]
205pub struct EditorStyle {
206 pub text: TextStyle,
207 #[serde(default)]
208 pub placeholder_text: Option<TextStyle>,
209 pub background: Color,
210 pub selection: SelectionStyle,
211 pub gutter_background: Color,
212 pub active_line_background: Color,
213 pub line_number: Color,
214 pub line_number_active: Color,
215 pub guest_selections: Vec<SelectionStyle>,
216 pub syntax: Arc<SyntaxTheme>,
217 pub error_underline: Color,
218 pub warning_underline: Color,
219 #[serde(default)]
220 pub information_underline: Color,
221 #[serde(default)]
222 pub hint_underline: Color,
223}
224
225#[derive(Clone, Copy, Default, Deserialize)]
226pub struct SelectionStyle {
227 pub cursor: Color,
228 pub selection: Color,
229}
230
231#[derive(Clone, Deserialize, Default)]
232pub struct InputEditorStyle {
233 #[serde(flatten)]
234 pub container: ContainerStyle,
235 pub text: TextStyle,
236 #[serde(default)]
237 pub placeholder_text: Option<TextStyle>,
238 pub selection: SelectionStyle,
239}
240
241impl EditorStyle {
242 pub fn placeholder_text(&self) -> &TextStyle {
243 self.placeholder_text.as_ref().unwrap_or(&self.text)
244 }
245}
246
247impl InputEditorStyle {
248 pub fn as_editor(&self) -> EditorStyle {
249 EditorStyle {
250 text: self.text.clone(),
251 placeholder_text: self.placeholder_text.clone(),
252 background: self
253 .container
254 .background_color
255 .unwrap_or(Color::transparent_black()),
256 selection: self.selection,
257 gutter_background: Default::default(),
258 active_line_background: Default::default(),
259 line_number: Default::default(),
260 line_number_active: Default::default(),
261 guest_selections: Default::default(),
262 syntax: Default::default(),
263 error_underline: Default::default(),
264 warning_underline: Default::default(),
265 information_underline: Default::default(),
266 hint_underline: Default::default(),
267 }
268 }
269}
270
271#[derive(Default)]
272pub struct SyntaxTheme {
273 pub highlights: Vec<(String, HighlightStyle)>,
274}
275
276impl SyntaxTheme {
277 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
278 Self { highlights }
279 }
280}
281
282impl<'de> Deserialize<'de> for SyntaxTheme {
283 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284 where
285 D: serde::Deserializer<'de>,
286 {
287 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
288
289 let mut result = Self::new(Vec::new());
290 for (key, style) in syntax_data {
291 match result
292 .highlights
293 .binary_search_by(|(needle, _)| needle.cmp(&key))
294 {
295 Ok(i) | Err(i) => {
296 result.highlights.insert(i, (key, style));
297 }
298 }
299 }
300
301 Ok(result)
302 }
303}