1pub use crate::{theme, ButtonVariant, ElementExt, Theme};
2use gpui2::{hsla, rgb, Hsla, WindowContext};
3use strum::EnumIter;
4
5#[derive(Clone, Copy)]
6pub struct PlayerThemeColors {
7 pub cursor: Hsla,
8 pub selection: Hsla,
9}
10
11impl std::fmt::Debug for PlayerThemeColors {
12 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13 f.debug_struct("PlayerThemeColors")
14 .field("cursor", &self.cursor.to_rgb().to_hex())
15 .field("selection", &self.selection.to_rgb().to_hex())
16 .finish()
17 }
18}
19
20impl PlayerThemeColors {
21 pub fn new(cx: &WindowContext, ix: usize) -> Self {
22 let theme = theme(cx);
23
24 if ix < theme.players.len() {
25 Self {
26 cursor: theme.players[ix].cursor,
27 selection: theme.players[ix].selection,
28 }
29 } else {
30 Self {
31 cursor: rgb::<Hsla>(0xff00ff),
32 selection: rgb::<Hsla>(0xff00ff),
33 }
34 }
35 }
36}
37
38#[derive(Clone, Copy)]
39pub struct SyntaxColor {
40 pub comment: Hsla,
41 pub string: Hsla,
42 pub function: Hsla,
43 pub keyword: Hsla,
44}
45
46impl std::fmt::Debug for SyntaxColor {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("SyntaxColor")
49 .field("comment", &self.comment.to_rgb().to_hex())
50 .field("string", &self.string.to_rgb().to_hex())
51 .field("function", &self.function.to_rgb().to_hex())
52 .field("keyword", &self.keyword.to_rgb().to_hex())
53 .finish()
54 }
55}
56
57impl SyntaxColor {
58 pub fn new(cx: &WindowContext) -> Self {
59 let theme = theme(cx);
60
61 Self {
62 comment: theme
63 .syntax
64 .get("comment")
65 .cloned()
66 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
67 string: theme
68 .syntax
69 .get("string")
70 .cloned()
71 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
72 function: theme
73 .syntax
74 .get("function")
75 .cloned()
76 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
77 keyword: theme
78 .syntax
79 .get("keyword")
80 .cloned()
81 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
82 }
83 }
84}
85
86/// ThemeColor is the primary interface for coloring elements in the UI.
87///
88/// It is a mapping layer between semantic theme colors and colors from the reference library.
89///
90/// While we are between zed and zed2 we use this to map semantic colors to the old theme.
91#[derive(Clone, Copy)]
92pub struct ThemeColor {
93 pub transparent: Hsla,
94 pub mac_os_traffic_light_red: Hsla,
95 pub mac_os_traffic_light_yellow: Hsla,
96 pub mac_os_traffic_light_green: Hsla,
97 pub border: Hsla,
98 pub border_variant: Hsla,
99 pub border_focused: Hsla,
100 pub border_transparent: Hsla,
101 /// The background color of an elevated surface, like a modal, tooltip or toast.
102 pub elevated_surface: Hsla,
103 pub surface: Hsla,
104 /// Window background color of the base app
105 pub background: Hsla,
106 /// Default background for elements like filled buttons,
107 /// text fields, checkboxes, radio buttons, etc.
108 /// - TODO: Map to step 3.
109 pub filled_element: Hsla,
110 /// The background color of a hovered element, like a button being hovered
111 /// with a mouse, or hovered on a touch screen.
112 /// - TODO: Map to step 4.
113 pub filled_element_hover: Hsla,
114 /// The background color of an active element, like a button being pressed,
115 /// or tapped on a touch screen.
116 /// - TODO: Map to step 5.
117 pub filled_element_active: Hsla,
118 /// The background color of a selected element, like a selected tab,
119 /// a button toggled on, or a checkbox that is checked.
120 pub filled_element_selected: Hsla,
121 pub filled_element_disabled: Hsla,
122 pub ghost_element: Hsla,
123 /// The background color of a hovered element with no default background,
124 /// like a ghost-style button or an interactable list item.
125 /// - TODO: Map to step 3.
126 pub ghost_element_hover: Hsla,
127 /// - TODO: Map to step 4.
128 pub ghost_element_active: Hsla,
129 pub ghost_element_selected: Hsla,
130 pub ghost_element_disabled: Hsla,
131 pub text: Hsla,
132 pub text_muted: Hsla,
133 pub text_placeholder: Hsla,
134 pub text_disabled: Hsla,
135 pub text_accent: Hsla,
136 pub icon_muted: Hsla,
137 pub syntax: SyntaxColor,
138
139 pub status_bar: Hsla,
140 pub title_bar: Hsla,
141 pub toolbar: Hsla,
142 pub tab_bar: Hsla,
143 /// The background of the editor
144 pub editor: Hsla,
145 pub editor_subheader: Hsla,
146 pub editor_active_line: Hsla,
147 pub terminal: Hsla,
148 pub image_fallback_background: Hsla,
149
150 pub git_created: Hsla,
151 pub git_modified: Hsla,
152 pub git_deleted: Hsla,
153 pub git_conflict: Hsla,
154 pub git_ignored: Hsla,
155 pub git_renamed: Hsla,
156
157 pub player: [PlayerThemeColors; 8],
158}
159
160impl std::fmt::Debug for ThemeColor {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 dbg!("ThemeColor debug");
163
164 f.debug_struct("ThemeColor")
165 .field("transparent", &self.transparent.to_rgb().to_hex())
166 .field(
167 "mac_os_traffic_light_red",
168 &self.mac_os_traffic_light_red.to_rgb().to_hex(),
169 )
170 .field(
171 "mac_os_traffic_light_yellow",
172 &self.mac_os_traffic_light_yellow.to_rgb().to_hex(),
173 )
174 .field(
175 "mac_os_traffic_light_green",
176 &self.mac_os_traffic_light_green.to_rgb().to_hex(),
177 )
178 .field("border", &self.border.to_rgb().to_hex())
179 .field("border_variant", &self.border_variant.to_rgb().to_hex())
180 .field("border_focused", &self.border_focused.to_rgb().to_hex())
181 .field(
182 "border_transparent",
183 &self.border_transparent.to_rgb().to_hex(),
184 )
185 .field("elevated_surface", &self.elevated_surface.to_rgb().to_hex())
186 .field("surface", &self.surface.to_rgb().to_hex())
187 .field("background", &self.background.to_rgb().to_hex())
188 .field("filled_element", &self.filled_element.to_rgb().to_hex())
189 .field(
190 "filled_element_hover",
191 &self.filled_element_hover.to_rgb().to_hex(),
192 )
193 .field(
194 "filled_element_active",
195 &self.filled_element_active.to_rgb().to_hex(),
196 )
197 .field(
198 "filled_element_selected",
199 &self.filled_element_selected.to_rgb().to_hex(),
200 )
201 .field(
202 "filled_element_disabled",
203 &self.filled_element_disabled.to_rgb().to_hex(),
204 )
205 .field("ghost_element", &self.ghost_element.to_rgb().to_hex())
206 .field(
207 "ghost_element_hover",
208 &self.ghost_element_hover.to_rgb().to_hex(),
209 )
210 .field(
211 "ghost_element_active",
212 &self.ghost_element_active.to_rgb().to_hex(),
213 )
214 .field(
215 "ghost_element_selected",
216 &self.ghost_element_selected.to_rgb().to_hex(),
217 )
218 .field(
219 "ghost_element_disabled",
220 &self.ghost_element_disabled.to_rgb().to_hex(),
221 )
222 .field("text", &self.text.to_rgb().to_hex())
223 .field("text_muted", &self.text_muted.to_rgb().to_hex())
224 .field("text_placeholder", &self.text_placeholder.to_rgb().to_hex())
225 .field("text_disabled", &self.text_disabled.to_rgb().to_hex())
226 .field("text_accent", &self.text_accent.to_rgb().to_hex())
227 .field("icon_muted", &self.icon_muted.to_rgb().to_hex())
228 .field("syntax", &self.syntax)
229 .field("status_bar", &self.status_bar.to_rgb().to_hex())
230 .field("title_bar", &self.title_bar.to_rgb().to_hex())
231 .field("toolbar", &self.toolbar.to_rgb().to_hex())
232 .field("tab_bar", &self.tab_bar.to_rgb().to_hex())
233 .field("editor", &self.editor.to_rgb().to_hex())
234 .field("editor_subheader", &self.editor_subheader.to_rgb().to_hex())
235 .field(
236 "editor_active_line",
237 &self.editor_active_line.to_rgb().to_hex(),
238 )
239 .field("terminal", &self.terminal.to_rgb().to_hex())
240 .field(
241 "image_fallback_background",
242 &self.image_fallback_background.to_rgb().to_hex(),
243 )
244 .field("git_created", &self.git_created.to_rgb().to_hex())
245 .field("git_modified", &self.git_modified.to_rgb().to_hex())
246 .field("git_deleted", &self.git_deleted.to_rgb().to_hex())
247 .field("git_conflict", &self.git_conflict.to_rgb().to_hex())
248 .field("git_ignored", &self.git_ignored.to_rgb().to_hex())
249 .field("git_renamed", &self.git_renamed.to_rgb().to_hex())
250 .field("player", &self.player)
251 .finish()
252 }
253}
254
255impl ThemeColor {
256 pub fn new(cx: &WindowContext) -> Self {
257 let theme = theme(cx);
258 let transparent = hsla(0.0, 0.0, 0.0, 0.0);
259
260 let players = [
261 PlayerThemeColors::new(cx, 0),
262 PlayerThemeColors::new(cx, 1),
263 PlayerThemeColors::new(cx, 2),
264 PlayerThemeColors::new(cx, 3),
265 PlayerThemeColors::new(cx, 4),
266 PlayerThemeColors::new(cx, 5),
267 PlayerThemeColors::new(cx, 6),
268 PlayerThemeColors::new(cx, 7),
269 ];
270
271 Self {
272 transparent,
273 mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
274 mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
275 mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
276 border: theme.lowest.base.default.border,
277 border_variant: theme.lowest.variant.default.border,
278 border_focused: theme.lowest.accent.default.border,
279 border_transparent: transparent,
280 elevated_surface: theme.lowest.base.default.background,
281 surface: theme.middle.base.default.background,
282 background: theme.lowest.base.default.background,
283 filled_element: theme.lowest.base.default.background,
284 filled_element_hover: hsla(0.0, 0.0, 100.0, 0.12),
285 filled_element_active: hsla(0.0, 0.0, 100.0, 0.16),
286 filled_element_selected: theme.lowest.accent.default.background,
287 filled_element_disabled: transparent,
288 ghost_element: transparent,
289 ghost_element_hover: hsla(0.0, 0.0, 100.0, 0.08),
290 ghost_element_active: hsla(0.0, 0.0, 100.0, 0.12),
291 ghost_element_selected: theme.lowest.accent.default.background,
292 ghost_element_disabled: transparent,
293 text: theme.lowest.base.default.foreground,
294 text_muted: theme.lowest.variant.default.foreground,
295 /// TODO: map this to a real value
296 text_placeholder: theme.lowest.negative.default.foreground,
297 text_disabled: theme.lowest.base.disabled.foreground,
298 text_accent: theme.lowest.accent.default.foreground,
299 icon_muted: theme.lowest.variant.default.foreground,
300 syntax: SyntaxColor::new(cx),
301
302 status_bar: theme.lowest.base.default.background,
303 title_bar: theme.lowest.base.default.background,
304 toolbar: theme.highest.base.default.background,
305 tab_bar: theme.middle.base.default.background,
306 editor: theme.highest.base.default.background,
307 editor_subheader: theme.middle.base.default.background,
308 terminal: theme.highest.base.default.background,
309 editor_active_line: theme.highest.on.default.background,
310 image_fallback_background: theme.lowest.base.default.background,
311
312 git_created: theme.lowest.positive.default.foreground,
313 git_modified: theme.lowest.accent.default.foreground,
314 git_deleted: theme.lowest.negative.default.foreground,
315 git_conflict: theme.lowest.warning.default.foreground,
316 git_ignored: theme.lowest.base.disabled.foreground,
317 git_renamed: theme.lowest.warning.default.foreground,
318
319 player: players,
320 }
321 }
322}
323
324/// Colors used exclusively for syntax highlighting.
325///
326/// For now we deserialize these from a theme.
327/// These will be defined statically in the new theme.
328#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
329pub enum HighlightColor {
330 #[default]
331 Default,
332 Comment,
333 String,
334 Function,
335 Keyword,
336}
337
338impl HighlightColor {
339 pub fn hsla(&self, theme: &Theme) -> Hsla {
340 match self {
341 Self::Default => theme
342 .syntax
343 .get("primary")
344 .cloned()
345 .expect("Couldn't find `primary` in theme.syntax"),
346 Self::Comment => theme
347 .syntax
348 .get("comment")
349 .cloned()
350 .expect("Couldn't find `comment` in theme.syntax"),
351 Self::String => theme
352 .syntax
353 .get("string")
354 .cloned()
355 .expect("Couldn't find `string` in theme.syntax"),
356 Self::Function => theme
357 .syntax
358 .get("function")
359 .cloned()
360 .expect("Couldn't find `function` in theme.syntax"),
361 Self::Keyword => theme
362 .syntax
363 .get("keyword")
364 .cloned()
365 .expect("Couldn't find `keyword` in theme.syntax"),
366 }
367 }
368}