1use gpui::{
27 AnyElement, App, Bounds, Corners, Element, GlobalElementId, InspectorElementId, IntoElement,
28 LayoutId, ParentElement, Pixels, RenderOnce, Styled, Window, div, px,
29};
30use gpui_kit_semantics::{NodeSpec, Role, Semantic};
31use gpui_kit_theme::{ActiveTheme, Radius, Surface};
32
33use crate::foundation::Ident;
34
35#[derive(IntoElement)]
37pub struct Frost {
38 ident: Ident,
39 surface: Surface,
40 radius: Radius,
41 blur: Option<f32>,
42 child: Option<AnyElement>,
43}
44
45impl std::fmt::Debug for Frost {
46 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 formatter
48 .debug_struct("Frost")
49 .field("ident", &self.ident)
50 .field("surface", &self.surface)
51 .field("radius", &self.radius)
52 .field("blur", &self.blur)
53 .field("has_child", &self.child.is_some())
54 .finish()
55 }
56}
57
58impl Frost {
59 pub fn new(ident: impl Into<Ident>) -> Self {
60 Self {
61 ident: ident.into(),
62 surface: Surface::Overlay,
63 radius: Radius::Card,
64 blur: None,
65 child: None,
66 }
67 }
68
69 pub fn surface(mut self, surface: Surface) -> Self {
72 self.surface = surface;
73 self
74 }
75
76 pub fn radius(mut self, radius: Radius) -> Self {
80 self.radius = radius;
81 self
82 }
83
84 pub fn blur(mut self, blur: f32) -> Self {
87 self.blur = Some(blur.max(0.0));
88 self
89 }
90
91 pub fn child(mut self, child: impl IntoElement) -> Self {
92 self.child = Some(child.into_any_element());
93 self
94 }
95}
96
97impl RenderOnce for Frost {
98 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
99 let theme = cx.theme();
100 let radius = theme.radius(self.radius);
101 let alpha = theme.effects.glass_alpha.clamp(0.0, 1.0);
102 let fill = theme.surface(self.surface).opacity(alpha);
103 let blur = self.blur.unwrap_or(theme.effects.glass_blur);
104 let translucent = blurs(alpha, blur);
105
106 let glass = div()
107 .rounded(px(radius))
108 .bg(fill)
109 .children(self.child)
110 .semantic_in(cx, NodeSpec::new(self.ident.semantic_id(), Role::Region));
111
112 Glass {
113 radius: px(radius),
114 blur: px(blur),
115 translucent,
116 child: glass.into_any_element(),
117 }
118 }
119}
120
121fn blurs(alpha: f32, blur: f32) -> bool {
125 alpha < 1.0 && blur > 0.0
126}
127
128struct Glass {
130 radius: Pixels,
131 blur: Pixels,
132 translucent: bool,
133 child: AnyElement,
134}
135
136impl Element for Glass {
137 type RequestLayoutState = ();
138 type PrepaintState = ();
139
140 fn id(&self) -> Option<gpui::ElementId> {
141 None
142 }
143
144 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
145 None
146 }
147
148 fn request_layout(
149 &mut self,
150 _id: Option<&GlobalElementId>,
151 _inspector_id: Option<&InspectorElementId>,
152 window: &mut Window,
153 cx: &mut App,
154 ) -> (LayoutId, ()) {
155 (self.child.request_layout(window, cx), ())
156 }
157
158 fn prepaint(
159 &mut self,
160 _id: Option<&GlobalElementId>,
161 _inspector_id: Option<&InspectorElementId>,
162 _bounds: Bounds<Pixels>,
163 _request_layout: &mut Self::RequestLayoutState,
164 window: &mut Window,
165 cx: &mut App,
166 ) {
167 self.child.prepaint(window, cx);
168 }
169
170 fn paint(
171 &mut self,
172 _id: Option<&GlobalElementId>,
173 _inspector_id: Option<&InspectorElementId>,
174 bounds: Bounds<Pixels>,
175 _request_layout: &mut Self::RequestLayoutState,
176 _prepaint: &mut Self::PrepaintState,
177 window: &mut Window,
178 cx: &mut App,
179 ) {
180 if !self.translucent {
181 self.child.paint(window, cx);
182 return;
183 }
184 window.paint_layer(bounds, |window| {
185 window.paint_backdrop_blur(bounds, Corners::all(self.radius), self.blur);
186 self.child.paint(window, cx);
187 });
188 }
189}
190
191impl IntoElement for Glass {
192 type Element = Self;
193
194 fn into_element(self) -> Self::Element {
195 self
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn an_opaque_theme_blurs_nothing() {
205 assert!(blurs(0.72, 24.0));
206 assert!(!blurs(1.0, 24.0), "an opaque fill hides what it blurred");
207 assert!(!blurs(0.72, 0.0), "no radius is no blur");
208 }
209}