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