1#![allow(clippy::unusual_byte_groupings)]
3
4use crate::theme::{paint, rgb, Theme, ThemeKind};
5use gpui::{point, px, BoxShadow, Hsla};
6
7use crate::button::ButtonVariant;
8
9pub(crate) fn box_shadow(x: f32, y: f32, color: Hsla, blur: f32, spread: f32) -> BoxShadow {
13 BoxShadow {
14 color,
15 offset: point(px(x), px(y)),
16 blur_radius: px(blur),
17 spread_radius: px(spread),
18 }
19}
20
21pub(crate) fn focus_ring(theme: Theme) -> BoxShadow {
23 box_shadow(
24 0.,
25 0.,
26 if theme.is_dark() {
27 paint(0xFFFFFF24)
28 } else {
29 paint(0x18181B24)
30 },
31 0.,
32 3.,
33 )
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub enum FieldState {
39 #[default]
40 Rest,
41 Focus,
42 Disabled,
43 Invalid,
44}
45
46#[derive(Clone, Copy, Debug)]
48pub struct FieldChrome {
49 pub bg: Hsla,
50 pub border: Hsla,
51 pub fg: Hsla,
52 pub placeholder: Hsla,
53 pub caret: Hsla,
54 pub inset: Hsla,
55 pub shadow: Hsla,
56 pub shadow_y: f32,
57 pub shadow_blur: f32,
58 pub ring: Option<Hsla>,
59}
60
61#[derive(Clone, Copy, Debug)]
63pub struct ButtonChrome {
64 pub bg: Hsla,
65 pub hover_bg: Hsla,
66 pub border: Hsla,
67 pub fg: Hsla,
68 pub inset: Hsla,
69 pub shadow: Hsla,
70 pub shadow_y: f32,
71 pub shadow_blur: f32,
72}
73
74pub fn button_chrome(theme: Theme, variant: ButtonVariant) -> ButtonChrome {
75 match (theme.kind, variant) {
76 (ThemeKind::Light, ButtonVariant::Primary) => ButtonChrome {
77 bg: paint(0x18181B_B8),
78 hover_bg: paint(0x18181B_CC),
79 border: paint(0xFFFFFF_29),
80 fg: theme.on_solid,
81 inset: paint(0xFFFFFF_38),
82 shadow: paint(0x0F172A_1A),
83 shadow_y: 8.0,
84 shadow_blur: 20.0,
85 },
86 (ThemeKind::Light, ButtonVariant::Secondary) => ButtonChrome {
87 bg: paint(0xFFFFFF_85),
88 hover_bg: paint(0xFFFFFF_99),
89 border: paint(0xFFFFFF_B8),
90 fg: theme.ink,
91 inset: paint(0xFFFFFF_E6),
92 shadow: paint(0x0F172A_0F),
93 shadow_y: 6.0,
94 shadow_blur: 16.0,
95 },
96 (ThemeKind::Light, ButtonVariant::Destructive) => ButtonChrome {
97 bg: paint(0xDC2626_C7),
98 hover_bg: paint(0xDC2626_DB),
99 border: paint(0xFFFFFF_47),
100 fg: rgb(0xFFFFFF),
101 inset: paint(0xFFFFFF_47),
102 shadow: paint(0xDC2626_29),
103 shadow_y: 8.0,
104 shadow_blur: 20.0,
105 },
106 (ThemeKind::Light, ButtonVariant::Outline) => ButtonChrome {
107 bg: paint(0xFFFFFF_47),
108 hover_bg: paint(0xFFFFFF_5C),
109 border: paint(0xFFFFFF_9E),
110 fg: theme.ink,
111 inset: paint(0xFFFFFF_BF),
112 shadow: paint(0x0F172A_0A),
113 shadow_y: 4.0,
114 shadow_blur: 12.0,
115 },
116 (ThemeKind::Light, ButtonVariant::OutlineDestructive) => ButtonChrome {
117 bg: paint(0xFEE2E2_6B),
118 hover_bg: paint(0xFEE2E2_85),
119 border: paint(0xFECACA_B3),
120 fg: theme.destructive,
121 inset: paint(0xFFFFFF_B3),
122 shadow: paint(0xDC2626_0F),
123 shadow_y: 4.0,
124 shadow_blur: 12.0,
125 },
126 (ThemeKind::Light, ButtonVariant::Ghost) => ButtonChrome {
127 bg: paint(0xFFFFFF_29),
128 hover_bg: paint(0xFFFFFF_3D),
129 border: paint(0xFFFFFF_47),
130 fg: theme.ink,
131 inset: paint(0xFFFFFF_66),
132 shadow: gpui::transparent_black(),
133 shadow_y: 0.0,
134 shadow_blur: 0.0,
135 },
136 (ThemeKind::Dark, ButtonVariant::Primary) => ButtonChrome {
137 bg: paint(0xFFFFFF_29),
138 hover_bg: paint(0xFFFFFF_38),
139 border: paint(0xFFFFFF_38),
140 fg: theme.on_solid,
141 inset: paint(0xFFFFFF_47),
142 shadow: paint(0x000000_47),
143 shadow_y: 8.0,
144 shadow_blur: 24.0,
145 },
146 (ThemeKind::Dark, ButtonVariant::Secondary) => ButtonChrome {
147 bg: paint(0xFFFFFF_12),
148 hover_bg: paint(0xFFFFFF_1A),
149 border: paint(0xFFFFFF_1A),
150 fg: theme.on_solid,
151 inset: paint(0xFFFFFF_1F),
152 shadow: paint(0x000000_2E),
153 shadow_y: 6.0,
154 shadow_blur: 16.0,
155 },
156 (ThemeKind::Dark, ButtonVariant::Destructive) => ButtonChrome {
157 bg: paint(0xDC2626_9E),
158 hover_bg: paint(0xDC2626_B3),
159 border: paint(0xFFFFFF_29),
160 fg: theme.on_solid,
161 inset: paint(0xFFFFFF_2E),
162 shadow: paint(0xDC2626_2E),
163 shadow_y: 8.0,
164 shadow_blur: 20.0,
165 },
166 (ThemeKind::Dark, ButtonVariant::Outline) => ButtonChrome {
167 bg: paint(0xFFFFFF_0A),
168 hover_bg: paint(0xFFFFFF_14),
169 border: paint(0xFFFFFF_24),
170 fg: theme.on_solid,
171 inset: paint(0xFFFFFF_1A),
172 shadow: paint(0x000000_29),
173 shadow_y: 4.0,
174 shadow_blur: 12.0,
175 },
176 (ThemeKind::Dark, ButtonVariant::OutlineDestructive) => ButtonChrome {
177 bg: paint(0x7F1D1D_47),
178 hover_bg: paint(0x7F1D1D_5C),
179 border: paint(0xF87171_47),
180 fg: theme.destructive_soft,
181 inset: paint(0xFFFFFF_14),
182 shadow: gpui::transparent_black(),
183 shadow_y: 0.0,
184 shadow_blur: 0.0,
185 },
186 (ThemeKind::Dark, ButtonVariant::Ghost) => ButtonChrome {
187 bg: paint(0xFFFFFF_08),
188 hover_bg: paint(0xFFFFFF_12),
189 border: paint(0xFFFFFF_0F),
190 fg: theme.on_solid,
191 inset: paint(0xFFFFFF_0F),
192 shadow: gpui::transparent_black(),
193 shadow_y: 0.0,
194 shadow_blur: 0.0,
195 },
196 }
197}
198
199pub fn field_chrome(theme: Theme, state: FieldState) -> FieldChrome {
200 match (theme.kind, state) {
201 (ThemeKind::Light, FieldState::Rest) => FieldChrome {
202 bg: paint(0xFFFFFF_47),
203 border: paint(0xFFFFFF_9E),
204 fg: theme.ink,
205 placeholder: theme.placeholder,
206 caret: theme.ink,
207 inset: paint(0xFFFFFF_BF),
208 shadow: paint(0x0F172A_0A),
209 shadow_y: 4.0,
210 shadow_blur: 12.0,
211 ring: None,
212 },
213 (ThemeKind::Light, FieldState::Focus) => FieldChrome {
214 bg: paint(0xFFFFFF_5C),
215 border: paint(0x18181B_47),
216 fg: theme.ink,
217 placeholder: theme.placeholder,
218 caret: rgb(0x18181B),
219 inset: paint(0xFFFFFF_BF),
220 shadow: paint(0x0F172A_0A),
221 shadow_y: 4.0,
222 shadow_blur: 12.0,
223 ring: Some(paint(0x18181B_24)),
224 },
225 (ThemeKind::Light, FieldState::Disabled) => FieldChrome {
226 bg: paint(0xFFFFFF_29),
227 border: paint(0xFFFFFF_47),
228 fg: theme.muted_fg(),
229 placeholder: theme.muted_fg(),
230 caret: theme.muted_fg(),
231 inset: paint(0xFFFFFF_66),
232 shadow: gpui::transparent_black(),
233 shadow_y: 0.0,
234 shadow_blur: 0.0,
235 ring: None,
236 },
237 (ThemeKind::Light, FieldState::Invalid) => FieldChrome {
238 bg: paint(0xFEE2E2_6B),
239 border: paint(0xFECACA_B3),
240 fg: theme.ink,
241 placeholder: theme.placeholder,
242 caret: theme.ink,
243 inset: paint(0xFFFFFF_B3),
244 shadow: paint(0xDC2626_0F),
245 shadow_y: 4.0,
246 shadow_blur: 12.0,
247 ring: None,
248 },
249 (ThemeKind::Dark, FieldState::Rest) => FieldChrome {
250 bg: paint(0xFFFFFF_0A),
251 border: paint(0xFFFFFF_24),
252 fg: theme.ink,
253 placeholder: theme.placeholder,
254 caret: theme.ink,
255 inset: paint(0xFFFFFF_1A),
256 shadow: paint(0x000000_29),
257 shadow_y: 4.0,
258 shadow_blur: 12.0,
259 ring: None,
260 },
261 (ThemeKind::Dark, FieldState::Focus) => FieldChrome {
262 bg: paint(0xFFFFFF_14),
263 border: paint(0xFFFFFF_38),
264 fg: theme.ink,
265 placeholder: theme.placeholder,
266 caret: rgb(0xFAFAFA),
267 inset: paint(0xFFFFFF_1A),
268 shadow: paint(0x000000_29),
269 shadow_y: 4.0,
270 shadow_blur: 12.0,
271 ring: Some(paint(0xFFFFFF_24)),
272 },
273 (ThemeKind::Dark, FieldState::Disabled) => FieldChrome {
274 bg: paint(0xFFFFFF_08),
275 border: paint(0xFFFFFF_0F),
276 fg: theme.muted_fg(),
277 placeholder: theme.muted_fg(),
278 caret: theme.muted_fg(),
279 inset: paint(0xFFFFFF_0F),
280 shadow: gpui::transparent_black(),
281 shadow_y: 0.0,
282 shadow_blur: 0.0,
283 ring: None,
284 },
285 (ThemeKind::Dark, FieldState::Invalid) => FieldChrome {
286 bg: paint(0x7F1D1D_47),
287 border: paint(0xF87171_47),
288 fg: theme.ink,
289 placeholder: theme.placeholder,
290 caret: theme.ink,
291 inset: paint(0xFFFFFF_14),
292 shadow: gpui::transparent_black(),
293 shadow_y: 0.0,
294 shadow_blur: 0.0,
295 ring: None,
296 },
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::button::ButtonVariant;
304
305 #[test]
306 fn light_primary_matches_paper() {
307 let chrome = button_chrome(Theme::light(), ButtonVariant::Primary);
308 assert_eq!(chrome.bg, paint(0x18181B_B8));
309 assert_eq!(chrome.border, paint(0xFFFFFF_29));
310 assert_eq!(chrome.shadow_y, 8.0);
311 assert_eq!(chrome.shadow_blur, 20.0);
312 }
313
314 #[test]
315 fn dark_primary_is_white_glass() {
316 let chrome = button_chrome(Theme::dark(), ButtonVariant::Primary);
317 assert_eq!(chrome.bg, paint(0xFFFFFF_29));
318 assert_eq!(chrome.border, paint(0xFFFFFF_38));
319 assert_eq!(chrome.shadow_blur, 24.0);
320 }
321
322 #[test]
323 fn light_focus_matches_paper() {
324 let chrome = field_chrome(Theme::light(), FieldState::Focus);
325 assert_eq!(chrome.bg, paint(0xFFFFFF_5C));
326 assert_eq!(chrome.border, paint(0x18181B_47));
327 assert_eq!(chrome.ring, Some(paint(0x18181B_24)));
328 }
329
330 #[test]
331 fn dark_invalid_matches_paper() {
332 let chrome = field_chrome(Theme::dark(), FieldState::Invalid);
333 assert_eq!(chrome.bg, paint(0x7F1D1D_47));
334 assert_eq!(chrome.border, paint(0xF87171_47));
335 }
336}