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