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 shadow_value = shadow.clone();
43 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
44 let shadow =
45 shadow_value.to_scope(Density::from_scale(crate::render_state::current_density()));
46 let primitive = build_drop_shadow_primitive(scope.size(), shape, &shadow);
47 scope.push_recorded(primitive);
48 });
49 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Behind(draw)))
50 .with_inspector_metadata(inspector_metadata("dropShadow", move |info| {
51 info.add_property("shape", format!("{shape:?}"));
52 info.add_property("shadowKind", "static");
53 }));
54 self.then(modifier)
55 }
56
57 pub fn inner_shadow(
64 self,
65 shape: LayerShape,
66 block: impl Fn(&mut ShadowScope) + 'static,
67 ) -> Self {
68 let block = Rc::new(block);
69 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
70 let mut shadow = ShadowScope::default();
71 block(&mut shadow);
72 let primitive = build_inner_shadow_primitive(scope.size(), shape, &shadow);
73 scope.push_recorded(primitive);
74 });
75 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Overlay(draw)))
76 .with_inspector_metadata(inspector_metadata("innerShadow", move |info| {
77 info.add_property("shape", format!("{shape:?}"));
78 info.add_property("shadowKind", "block");
79 }));
80 self.then(modifier)
81 }
82
83 pub fn inner_shadow_value(self, shape: LayerShape, shadow: Shadow) -> Self {
85 let shadow_value = shadow.clone();
86 let draw = Rc::new(move |scope: &mut DrawScopeDefault| {
87 let shadow =
88 shadow_value.to_scope(Density::from_scale(crate::render_state::current_density()));
89 let primitive = build_inner_shadow_primitive(scope.size(), shape, &shadow);
90 scope.push_recorded(primitive);
91 });
92 let modifier = Self::with_element(DrawCommandElement::new(DrawCommand::Overlay(draw)))
93 .with_inspector_metadata(inspector_metadata("innerShadow", move |info| {
94 info.add_property("shape", format!("{shape:?}"));
95 info.add_property("shadowKind", "static");
96 }));
97 self.then(modifier)
98 }
99}
100
101fn normalized_scope(scope: &ShadowScope) -> Option<ShadowScope> {
102 if !scope.alpha.is_finite() || scope.alpha <= 0.0 {
103 return None;
104 }
105 let radius = if scope.radius.is_finite() {
106 scope.radius.max(0.0)
107 } else {
108 0.0
109 };
110 let spread = if scope.spread.is_finite() {
111 scope.spread
112 } else {
113 0.0
114 };
115 let offset = Point {
116 x: if scope.offset.x.is_finite() {
117 scope.offset.x
118 } else {
119 0.0
120 },
121 y: if scope.offset.y.is_finite() {
122 scope.offset.y
123 } else {
124 0.0
125 },
126 };
127 Some(ShadowScope {
128 radius,
129 spread,
130 offset,
131 color: scope.color,
132 brush: scope.brush.clone(),
133 alpha: scope.alpha.clamp(0.0, 1.0),
134 blend_mode: scope.blend_mode,
135 cutout: scope.cutout,
136 })
137}
138
139fn build_drop_shadow_primitive(
140 size: Size,
141 shape: LayerShape,
142 scope: &ShadowScope,
143) -> Option<DrawPrimitive> {
144 let scope = normalized_scope(scope)?;
145 if size.width <= 0.0 || size.height <= 0.0 {
146 return None;
147 }
148
149 let brush = alpha_modulated_brush(
150 scope.brush.unwrap_or_else(|| Brush::solid(scope.color)),
151 scope.alpha,
152 );
153
154 let spread = scope.spread;
155 let rect = Rect {
156 x: scope.offset.x - spread,
157 y: scope.offset.y - spread,
158 width: size.width + spread * 2.0,
159 height: size.height + spread * 2.0,
160 };
161 if rect.width <= 0.0 || rect.height <= 0.0 {
162 return None;
163 }
164
165 let shape_prim = primitive_for_shape(shape, rect, brush)?;
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 Some(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_primitive(
188 size: Size,
189 shape: LayerShape,
190 scope: &ShadowScope,
191) -> Option<DrawPrimitive> {
192 let scope = normalized_scope(scope)?;
193 if size.width <= 0.0 || size.height <= 0.0 {
194 return None;
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 None;
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 None;
227 }
228
229 let fill = primitive_for_shape(shape, outer, brush)?;
230 let cutout = primitive_for_shape(shape, inner, Brush::solid(Color::WHITE))?;
231
232 Some(DrawPrimitive::Shadow(ShadowPrimitive::Inner {
233 fill: Box::new(fill),
234 cutout: Box::new(cutout),
235 blur_radius: scope.radius,
236 blend_mode: scope.blend_mode,
237 clip_rect: outer,
238 }))
239}
240
241fn primitive_for_shape(shape: LayerShape, rect: Rect, brush: Brush) -> Option<DrawPrimitive> {
242 if rect.width <= 0.0 || rect.height <= 0.0 {
243 return None;
244 }
245
246 Some(match shape {
247 LayerShape::Rectangle => DrawPrimitive::Rect {
248 rect,
249 brush,
250 stroke: None,
251 },
252 LayerShape::Rounded(shape) => {
253 let radii = shape.resolve(rect.width, rect.height);
254 DrawPrimitive::RoundRect {
255 rect,
256 brush,
257 radii,
258 stroke: None,
259 }
260 }
261 })
262}
263
264fn alpha_modulated_brush(brush: Brush, alpha: f32) -> Brush {
265 let alpha = alpha.clamp(0.0, 1.0);
266 match brush {
267 Brush::Solid(color) => Brush::Solid(color.with_alpha(color.a() * alpha)),
268 Brush::LinearGradient {
269 colors,
270 stops,
271 start,
272 end,
273 tile_mode,
274 } => Brush::LinearGradient {
275 colors: colors
276 .into_iter()
277 .map(|color| color.with_alpha(color.a() * alpha))
278 .collect(),
279 stops,
280 start,
281 end,
282 tile_mode,
283 },
284 Brush::RadialGradient {
285 colors,
286 stops,
287 center,
288 radius,
289 tile_mode,
290 } => Brush::RadialGradient {
291 colors: colors
292 .into_iter()
293 .map(|color| color.with_alpha(color.a() * alpha))
294 .collect(),
295 stops,
296 center,
297 radius,
298 tile_mode,
299 },
300 Brush::SweepGradient {
301 colors,
302 stops,
303 center,
304 } => Brush::SweepGradient {
305 colors: colors
306 .into_iter()
307 .map(|color| color.with_alpha(color.a() * alpha))
308 .collect(),
309 stops,
310 center,
311 },
312 }
313}