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