Skip to main content

glassy_ui/
chrome.rs

1//! Paper hex+alpha is grouped as `RRGGBB_AA`.
2#![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
9/// Construct a `BoxShadow` for the published gpui 0.2.2 struct API.
10/// (The old `BoxShadow::new(x, y, color).inset()/.blur_radius()/.spread_radius()`
11/// builder is gone; `inset` shadows are not supported by this version.)
12pub(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
21/// 3px zinc/white spread used by focused controls.
22pub(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/// Rest / focus / disabled / invalid paint for Input and Textarea.
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub enum FieldState {
39    #[default]
40    Rest,
41    Focus,
42    Disabled,
43    Invalid,
44}
45
46/// Per-state paint used by [`crate::Input`].
47#[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/// Per-variant paint used by [`crate::Button`].
62#[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}