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}
218
219#[derive(Clone, Copy, Default, Deserialize)]
220pub struct SelectionStyle {
221 pub cursor: Color,
222 pub selection: Color,
223}
224
225#[derive(Clone, Deserialize, Default)]
226pub struct InputEditorStyle {
227 #[serde(flatten)]
228 pub container: ContainerStyle,
229 pub text: TextStyle,
230 #[serde(default)]
231 pub placeholder_text: Option<TextStyle>,
232 pub selection: SelectionStyle,
233}
234
235impl EditorStyle {
236 pub fn placeholder_text(&self) -> &TextStyle {
237 self.placeholder_text.as_ref().unwrap_or(&self.text)
238 }
239}
240
241impl InputEditorStyle {
242 pub fn as_editor(&self) -> EditorStyle {
243 EditorStyle {
244 text: self.text.clone(),
245 placeholder_text: self.placeholder_text.clone(),
246 background: self
247 .container
248 .background_color
249 .unwrap_or(Color::transparent_black()),
250 selection: self.selection,
251 gutter_background: Default::default(),
252 active_line_background: Default::default(),
253 line_number: Default::default(),
254 line_number_active: Default::default(),
255 guest_selections: Default::default(),
256 syntax: Default::default(),
257 }
258 }
259}
260
261#[derive(Default)]
262pub struct SyntaxTheme {
263 pub highlights: Vec<(String, HighlightStyle)>,
264}
265
266impl SyntaxTheme {
267 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
268 Self { highlights }
269 }
270}
271
272impl<'de> Deserialize<'de> for SyntaxTheme {
273 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
274 where
275 D: serde::Deserializer<'de>,
276 {
277 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
278
279 let mut result = Self::new(Vec::new());
280 for (key, style) in syntax_data {
281 match result
282 .highlights
283 .binary_search_by(|(needle, _)| needle.cmp(&key))
284 {
285 Ok(i) | Err(i) => {
286 result.highlights.insert(i, (key, style));
287 }
288 }
289 }
290
291 Ok(result)
292 }
293}