theme/theme/glass.rs
1//! Glass recipes: translucent chrome over the blurred window background, and
2//! the modal scrim.
3
4use gpui::{Hsla, WindowBackgroundAppearance, hsla};
5
6use crate::{Appearance, color, paint, theme::Theme};
7
8impl Theme {
9 /// The frost tint painted over the blurred window background (macOS glass),
10 /// at [`Brand::vibrancy_alpha`](crate::Brand::vibrancy_alpha). Dark: darker than
11 /// `surface`, matched to the reference vibrancy scrim `hsl(0 0% 3%)`.
12 /// Light: the material's own measured tone. Opaque, this IS the surface tone.
13 pub fn vibrancy_tint(&self) -> Hsla {
14 let alpha = self.vibrancy_alpha;
15 if alpha >= 1.0 {
16 return self.surface;
17 }
18 match self.appearance {
19 Appearance::Dark => color::grey(8).opacity(alpha),
20 Appearance::Light => self.material.tone.opacity(alpha),
21 }
22 }
23
24 /// The app's root fill: the frost where glass is on, the opaque panel
25 /// where it is not. What a root element paints instead of
26 /// [`Self::bg`](Self#structfield.bg), so a window that opens blurred is not
27 /// then covered over by the paint that made the blur pointless.
28 pub fn window_bg(&self) -> Hsla {
29 if self.vibrancy {
30 self.vibrancy_tint()
31 } else {
32 self.bg
33 }
34 }
35
36 /// The translucent tint floating cards paint over their backdrop blur
37 /// (see `bezel::material`). Dark: the reference `.glass-surface` menu tint
38 /// verbatim — `oklch(0.33 0 0 / 34%)`. Light: heavier, since white over a
39 /// bright backdrop separates a row from it by less.
40 ///
41 /// Some callers paint this *over* a [`Surface`](crate::SurfaceStyle) —
42 /// `control_bar` does — so coverage here hides the lens under it.
43 pub fn glass_overlay(&self) -> Hsla {
44 match self.appearance {
45 Appearance::Dark => color::oklch(0.33, 0.0, 0.0).opacity(0.34),
46 Appearance::Light => self.surface_overlay.opacity(0.55),
47 }
48 }
49
50 /// The composer pill / question panel fill. Light's `input_bg` is opaque
51 /// white (the elevation ladder on an opaque page) — over glass it read as
52 /// a solid slab in front of the frosted blur, so it thins to a
53 /// translucent tint there (0.6 and then 0.45 both still read too bright
54 /// over the 0.80 frost — lowered on user request). Dark's 3% white wash
55 /// is already glass-native.
56 pub fn input_glass_bg(&self) -> Hsla {
57 if self.glass && matches!(self.appearance, Appearance::Light) {
58 self.input_bg.opacity(0.30)
59 } else {
60 self.input_bg
61 }
62 }
63
64 /// Section-card fill — the group box, and the in-panel cards built like it.
65 ///
66 /// Each appearance plates in the direction it has room in: dark lifts on a
67 /// white wash (`../desktop`'s `--color-card`), light lands a white card
68 /// over whatever is beneath it. An opaque platform has no frost beneath the
69 /// card, so it takes the grey below its white page.
70 pub fn card_glass_bg(&self) -> Hsla {
71 if !self.glass {
72 return self.surface;
73 }
74 match self.appearance {
75 Appearance::Dark => hsla(0.0, 0.0, 1.0, 0.06),
76 Appearance::Light => self.surface_card.opacity(0.60),
77 }
78 }
79
80 /// The standard modal backdrop — see [`scrim`](crate::paint::scrim).
81 pub fn scrim(&self) -> Hsla {
82 paint::scrim_for(self.appearance, paint::SCRIM_ALPHA_DARK)
83 }
84
85 /// How the platform should composite the window behind our paint.
86 ///
87 /// Windows takes Mica, which is DWM's own backdrop; `Blurred` there is the
88 /// undocumented accent API's acrylic at a zero tint, which reads as plain
89 /// transparency. Mica tints from the desktop wallpaper rather than from
90 /// the windows behind, so what it carries is not what vibrancy carries.
91 ///
92 /// This is a method rather than a constant because it has to be *re-applied* after
93 /// every theme swap: gpui's macOS backend tears the `NSVisualEffectView`
94 /// out of the hierarchy whenever the value is anything but `Blurred`, and
95 /// the re-apply in `appearance::apply` is what restores vibrancy when the
96 /// user switches back to dark. See zed's `crates/zed/src/main.rs`, which
97 /// runs the same loop on every settings change.
98 pub fn window_background_appearance(&self) -> WindowBackgroundAppearance {
99 match (self.vibrancy, cfg!(target_os = "windows")) {
100 (false, _) => WindowBackgroundAppearance::Opaque,
101 (true, true) => WindowBackgroundAppearance::MicaBackdrop,
102 (true, false) => WindowBackgroundAppearance::Blurred,
103 }
104 }
105}