1use super::*;
2use crate::Size;
3
4fn rect_inflate(rect: Rect, delta: Px) -> Rect {
5 let d = delta.0.max(0.0);
6 Rect::new(
7 Point::new(Px(rect.origin.x.0 - d), Px(rect.origin.y.0 - d)),
8 Size::new(
9 Px(rect.size.width.0 + d * 2.0),
10 Px(rect.size.height.0 + d * 2.0),
11 ),
12 )
13}
14
15fn rect_deflate(rect: Rect, delta: Px) -> Rect {
16 let d = delta.0.max(0.0);
17 let w = (rect.size.width.0 - d * 2.0).max(0.0);
18 let h = (rect.size.height.0 - d * 2.0).max(0.0);
19 Rect::new(
20 Point::new(Px(rect.origin.x.0 + d), Px(rect.origin.y.0 + d)),
21 Size::new(Px(w), Px(h)),
22 )
23}
24
25fn rect_expand(rect: Rect, delta: Px) -> Rect {
26 if delta.0 >= 0.0 {
27 rect_inflate(rect, delta)
28 } else {
29 rect_deflate(rect, Px(-delta.0))
30 }
31}
32
33fn corners_inflate(mut corners: Corners, delta: Px) -> Corners {
34 let d = delta.0.max(0.0);
35 corners.top_left = Px((corners.top_left.0 + d).max(0.0));
36 corners.top_right = Px((corners.top_right.0 + d).max(0.0));
37 corners.bottom_left = Px((corners.bottom_left.0 + d).max(0.0));
38 corners.bottom_right = Px((corners.bottom_right.0 + d).max(0.0));
39 corners
40}
41
42fn corners_deflate(mut corners: Corners, delta: Px) -> Corners {
43 let d = delta.0.max(0.0);
44 corners.top_left = Px((corners.top_left.0 - d).max(0.0));
45 corners.top_right = Px((corners.top_right.0 - d).max(0.0));
46 corners.bottom_left = Px((corners.bottom_left.0 - d).max(0.0));
47 corners.bottom_right = Px((corners.bottom_right.0 - d).max(0.0));
48 corners
49}
50
51fn corners_expand(corners: Corners, delta: Px) -> Corners {
52 if delta.0 >= 0.0 {
53 corners_inflate(corners, delta)
54 } else {
55 corners_deflate(corners, Px(-delta.0))
56 }
57}
58
59fn color_with_alpha(mut color: Color, alpha: f32) -> Color {
60 color.a = alpha.clamp(0.0, 1.0);
61 color
62}
63
64fn shadow_alpha_weight(step_index: usize) -> f32 {
65 1.0 / (1.0 + step_index as f32)
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub struct ShadowRRectFallbackSpec {
70 pub order: DrawOrder,
71 pub rect: Rect,
72 pub corner_radii: Corners,
73 pub offset: Point,
74 pub spread: Px,
75 pub blur_radius: Px,
76 pub color: Color,
77}
78
79pub fn shadow_rrect_fallback_quads(spec: ShadowRRectFallbackSpec) -> Vec<SceneOp> {
84 let ShadowRRectFallbackSpec {
85 order,
86 rect,
87 corner_radii,
88 offset,
89 spread,
90 blur_radius,
91 color,
92 } = spec;
93 if rect.size.width.0 <= 0.0 || rect.size.height.0 <= 0.0 {
94 return Vec::new();
95 }
96 if !offset.x.0.is_finite()
97 || !offset.y.0.is_finite()
98 || !blur_radius.0.is_finite()
99 || !spread.0.is_finite()
100 || color.a <= 0.0
101 {
102 return Vec::new();
103 }
104
105 let blur = blur_radius.0.max(0.0);
106 let max_steps = 32_f32;
107 let steps = blur.ceil().clamp(0.0, max_steps) as usize;
108 let denom = (steps as f32).max(1.0);
109 let alpha_weight_sum = if steps == 0 {
110 1.0
111 } else {
112 (0..=steps)
113 .map(shadow_alpha_weight)
114 .sum::<f32>()
115 .max(f32::EPSILON)
116 };
117
118 let mut out = Vec::with_capacity(steps.saturating_add(1));
119 for i in (0..=steps).rev() {
120 let t = i as f32 / denom;
121 let layer_spread = spread.0 + blur * t;
122 let fallback_rect = {
123 let mut fallback_rect = rect_expand(rect, Px(layer_spread));
124 fallback_rect.origin.x = Px(fallback_rect.origin.x.0 + offset.x.0);
125 fallback_rect.origin.y = Px(fallback_rect.origin.y.0 + offset.y.0);
126 fallback_rect
127 };
128 if fallback_rect.size.width.0 <= 0.0 || fallback_rect.size.height.0 <= 0.0 {
129 continue;
130 }
131
132 let alpha = if steps == 0 {
133 color.a
134 } else {
135 color.a * (shadow_alpha_weight(i) / alpha_weight_sum)
136 };
137 out.push(SceneOp::Quad {
138 order,
139 rect: fallback_rect,
140 background: Paint::Solid(color_with_alpha(color, alpha)).into(),
141 border: Edges::all(Px(0.0)),
142 border_paint: Paint::Solid(Color::TRANSPARENT).into(),
143 corner_radii: corners_expand(corner_radii, Px(layer_spread)),
144 });
145 }
146
147 out
148}
149
150impl SceneRecording {
151 pub fn push_shadow_rrect_quad_fallback(&mut self, spec: ShadowRRectFallbackSpec) {
153 for op in shadow_rrect_fallback_quads(spec) {
154 self.push(op);
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn shadow_rrect_fallback_quads_keep_expected_profile() {
165 let ops = shadow_rrect_fallback_quads(ShadowRRectFallbackSpec {
166 order: DrawOrder(3),
167 rect: Rect::new(
168 Point::new(Px(20.0), Px(10.0)),
169 Size::new(Px(40.0), Px(24.0)),
170 ),
171 corner_radii: Corners::all(Px(6.0)),
172 offset: Point::new(Px(0.0), Px(2.0)),
173 spread: Px(1.0),
174 blur_radius: Px(4.0),
175 color: Color {
176 r: 0.0,
177 g: 0.0,
178 b: 0.0,
179 a: 0.18,
180 },
181 });
182
183 assert_eq!(ops.len(), 5);
184 let SceneOp::Quad {
185 rect: outer_rect,
186 background: outer_background,
187 corner_radii: outer_radii,
188 ..
189 } = ops[0]
190 else {
191 panic!("expected outer fallback quad");
192 };
193 let SceneOp::Quad {
194 rect: inner_rect,
195 background: inner_background,
196 corner_radii: inner_radii,
197 ..
198 } = ops[4]
199 else {
200 panic!("expected inner fallback quad");
201 };
202
203 let Paint::Solid(outer_color) = outer_background.paint else {
204 panic!("expected outer fallback color");
205 };
206 let Paint::Solid(inner_color) = inner_background.paint else {
207 panic!("expected inner fallback color");
208 };
209
210 assert!(outer_rect.size.width.0 > inner_rect.size.width.0);
211 assert!(outer_rect.size.height.0 > inner_rect.size.height.0);
212 assert!(outer_radii.top_left.0 > inner_radii.top_left.0);
213 assert!(outer_color.a < inner_color.a);
214 }
215
216 #[test]
217 fn push_shadow_rrect_quad_fallback_replays_ops_into_scene() {
218 let mut scene = Scene::default();
219 scene.push_shadow_rrect_quad_fallback(ShadowRRectFallbackSpec {
220 order: DrawOrder(0),
221 rect: Rect::new(Point::new(Px(0.0), Px(0.0)), Size::new(Px(12.0), Px(8.0))),
222 corner_radii: Corners::all(Px(4.0)),
223 offset: Point::new(Px(1.0), Px(2.0)),
224 spread: Px(0.0),
225 blur_radius: Px(2.0),
226 color: Color {
227 r: 0.0,
228 g: 0.0,
229 b: 0.0,
230 a: 0.12,
231 },
232 });
233
234 assert_eq!(scene.ops_len(), 3);
235 assert!(
236 scene
237 .ops()
238 .iter()
239 .all(|op| matches!(op, SceneOp::Quad { .. }))
240 );
241 }
242}