1use std::rc::Rc;
2
3use cranpose_ui_graphics::{DrawPrimitive, DrawScope as _, DrawScopeDefault, ShadowPrimitive};
4
5use super::{
6 Brush, Color, DrawCommand, LayerShape, Modifier, Point, Rect, Shadow, ShadowScope, Size,
7 inspector_metadata,
8};
9use crate::modifier_nodes::DrawCommandElement;
10
11impl Modifier {
12 pub fn drop_shadow(
19 self,
20 shape: LayerShape,
21 block: impl Fn(&mut ShadowScope) + 'static,
22 ) -> Self {
23 let block = Rc::new(block);
24 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
25 let mut shadow = ShadowScope::default();
26 block(&mut shadow);
27 let primitives = build_drop_shadow_primitives(scope.size(), shape, &shadow);
28 scope.push_recorded(primitives);
29 });
30 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Behind(draw)))
31 .with_inspector_metadata(inspector_metadata("dropShadow", move |info| {
32 info.add_property("shape", format!("{shape:?}"));
33 info.add_property("shadowKind", "block");
34 }));
35 self.then(modifier)
36 }
37
38 pub fn drop_shadow_value(self, shape: LayerShape, shadow: Shadow) -> Self {
40 let shadow_value = shadow.clone();
41 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
42 let shadow = shadow_value.to_scope(crate::render_state::current_density());
43 let primitives = build_drop_shadow_primitives(scope.size(), shape, &shadow);
44 scope.push_recorded(primitives);
45 });
46 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Behind(draw)))
47 .with_inspector_metadata(inspector_metadata("dropShadow", move |info| {
48 info.add_property("shape", format!("{shape:?}"));
49 info.add_property("shadowKind", "static");
50 }));
51 self.then(modifier)
52 }
53
54 pub fn inner_shadow(
61 self,
62 shape: LayerShape,
63 block: impl Fn(&mut ShadowScope) + 'static,
64 ) -> Self {
65 let block = Rc::new(block);
66 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
67 let mut shadow = ShadowScope::default();
68 block(&mut shadow);
69 let primitives = build_inner_shadow_primitives(scope.size(), shape, &shadow);
70 scope.push_recorded(primitives);
71 });
72 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Overlay(draw)))
73 .with_inspector_metadata(inspector_metadata("innerShadow", move |info| {
74 info.add_property("shape", format!("{shape:?}"));
75 info.add_property("shadowKind", "block");
76 }));
77 self.then(modifier)
78 }
79
80 pub fn inner_shadow_value(self, shape: LayerShape, shadow: Shadow) -> Self {
82 let shadow_value = shadow.clone();
83 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
84 let shadow = shadow_value.to_scope(crate::render_state::current_density());
85 let primitives = build_inner_shadow_primitives(scope.size(), shape, &shadow);
86 scope.push_recorded(primitives);
87 });
88 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Overlay(draw)))
89 .with_inspector_metadata(inspector_metadata("innerShadow", move |info| {
90 info.add_property("shape", format!("{shape:?}"));
91 info.add_property("shadowKind", "static");
92 }));
93 self.then(modifier)
94 }
95}
96
97fn normalized_scope(scope: &ShadowScope) -> Option<ShadowScope> {
98 if !scope.alpha.is_finite() || scope.alpha <= 0.0 {
99 return None;
100 }
101 let radius = if scope.radius.is_finite() {
102 scope.radius.max(0.0)
103 } else {
104 0.0
105 };
106 let spread = if scope.spread.is_finite() {
107 scope.spread
108 } else {
109 0.0
110 };
111 let offset = Point {
112 x: if scope.offset.x.is_finite() {
113 scope.offset.x
114 } else {
115 0.0
116 },
117 y: if scope.offset.y.is_finite() {
118 scope.offset.y
119 } else {
120 0.0
121 },
122 };
123 Some(ShadowScope {
124 radius,
125 spread,
126 offset,
127 color: scope.color,
128 brush: scope.brush.clone(),
129 alpha: scope.alpha.clamp(0.0, 1.0),
130 blend_mode: scope.blend_mode,
131 cutout: scope.cutout,
132 })
133}
134
135fn build_drop_shadow_primitives(
136 size: Size,
137 shape: LayerShape,
138 scope: &ShadowScope,
139) -> Vec<DrawPrimitive> {
140 let Some(scope) = normalized_scope(scope) else {
141 return Vec::new();
142 };
143 if size.width <= 0.0 || size.height <= 0.0 {
144 return Vec::new();
145 }
146
147 let brush = alpha_modulated_brush(
148 scope.brush.unwrap_or_else(|| Brush::solid(scope.color)),
149 scope.alpha,
150 );
151
152 let spread = scope.spread;
153 let rect = Rect {
154 x: scope.offset.x - spread,
155 y: scope.offset.y - spread,
156 width: size.width + spread * 2.0,
157 height: size.height + spread * 2.0,
158 };
159 if rect.width <= 0.0 || rect.height <= 0.0 {
160 return Vec::new();
161 }
162
163 let Some(shape_prim) = primitive_for_shape(shape, rect, brush) else {
164 return Vec::new();
165 };
166
167 let cutout = if scope.cutout {
168 let element_rect = Rect {
169 x: 0.0,
170 y: 0.0,
171 width: size.width,
172 height: size.height,
173 };
174 primitive_for_shape(shape, element_rect, Brush::solid(Color::BLACK)).map(Box::new)
175 } else {
176 None
177 };
178
179 vec![DrawPrimitive::Shadow(ShadowPrimitive::Drop {
180 shape: Box::new(shape_prim),
181 cutout,
182 blur_radius: scope.radius,
183 blend_mode: scope.blend_mode,
184 })]
185}
186
187fn build_inner_shadow_primitives(
188 size: Size,
189 shape: LayerShape,
190 scope: &ShadowScope,
191) -> Vec<DrawPrimitive> {
192 let Some(scope) = normalized_scope(scope) else {
193 return Vec::new();
194 };
195 if size.width <= 0.0 || size.height <= 0.0 {
196 return Vec::new();
197 }
198 if scope.radius <= f32::EPSILON
199 && scope.spread.abs() <= f32::EPSILON
200 && scope.offset.x.abs() <= f32::EPSILON
201 && scope.offset.y.abs() <= f32::EPSILON
202 {
203 return Vec::new();
204 }
205
206 let brush = alpha_modulated_brush(
207 scope.brush.unwrap_or_else(|| Brush::solid(scope.color)),
208 scope.alpha,
209 );
210
211 let outer = Rect {
212 x: 0.0,
213 y: 0.0,
214 width: size.width,
215 height: size.height,
216 };
217 let left = scope.offset.x + scope.spread;
218 let top = scope.offset.y + scope.spread;
219 let right = (scope.offset.x + size.width - scope.spread).max(left);
220 let bottom = (scope.offset.y + size.height - scope.spread).max(top);
221 let inner = Rect {
222 x: left,
223 y: top,
224 width: right - left,
225 height: bottom - top,
226 };
227 if inner.width <= 0.0 || inner.height <= 0.0 {
228 return Vec::new();
229 }
230
231 let Some(fill) = primitive_for_shape(shape, outer, brush) else {
232 return Vec::new();
233 };
234 let Some(cutout) = primitive_for_shape(shape, inner, Brush::solid(Color::WHITE)) else {
235 return Vec::new();
236 };
237
238 vec![DrawPrimitive::Shadow(ShadowPrimitive::Inner {
239 fill: Box::new(fill),
240 cutout: Box::new(cutout),
241 blur_radius: scope.radius,
242 blend_mode: scope.blend_mode,
243 clip_rect: outer,
244 })]
245}
246
247fn primitive_for_shape(shape: LayerShape, rect: Rect, brush: Brush) -> Option<DrawPrimitive> {
248 if rect.width <= 0.0 || rect.height <= 0.0 {
249 return None;
250 }
251
252 Some(match shape {
253 LayerShape::Rectangle => DrawPrimitive::Rect {
254 rect,
255 brush,
256 stroke: None,
257 },
258 LayerShape::Rounded(shape) => {
259 let radii = shape.resolve(rect.width, rect.height);
260 DrawPrimitive::RoundRect {
261 rect,
262 brush,
263 radii,
264 stroke: None,
265 }
266 }
267 })
268}
269
270fn alpha_modulated_brush(brush: Brush, alpha: f32) -> Brush {
271 let alpha = alpha.clamp(0.0, 1.0);
272 match brush {
273 Brush::Solid(color) => Brush::Solid(color.with_alpha(color.a() * alpha)),
274 Brush::LinearGradient {
275 colors,
276 stops,
277 start,
278 end,
279 tile_mode,
280 } => Brush::LinearGradient {
281 colors: colors
282 .into_iter()
283 .map(|color| color.with_alpha(color.a() * alpha))
284 .collect(),
285 stops,
286 start,
287 end,
288 tile_mode,
289 },
290 Brush::RadialGradient {
291 colors,
292 stops,
293 center,
294 radius,
295 tile_mode,
296 } => Brush::RadialGradient {
297 colors: colors
298 .into_iter()
299 .map(|color| color.with_alpha(color.a() * alpha))
300 .collect(),
301 stops,
302 center,
303 radius,
304 tile_mode,
305 },
306 Brush::SweepGradient {
307 colors,
308 stops,
309 center,
310 } => Brush::SweepGradient {
311 colors: colors
312 .into_iter()
313 .map(|color| color.with_alpha(color.a() * alpha))
314 .collect(),
315 stops,
316 center,
317 },
318 }
319}