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