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 primitives = build_drop_shadow_primitives(scope.size(), shape, &shadow);
30 scope.push_recorded(primitives);
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 primitives = build_drop_shadow_primitives(scope.size(), shape, &shadow);
47 scope.push_recorded(primitives);
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 primitives = build_inner_shadow_primitives(scope.size(), shape, &shadow);
73 scope.push_recorded(primitives);
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 primitives = build_inner_shadow_primitives(scope.size(), shape, &shadow);
90 scope.push_recorded(primitives);
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_primitives(
140 size: Size,
141 shape: LayerShape,
142 scope: &ShadowScope,
143) -> Vec<DrawPrimitive> {
144 let Some(scope) = normalized_scope(scope) else {
145 return Vec::new();
146 };
147 if size.width <= 0.0 || size.height <= 0.0 {
148 return Vec::new();
149 }
150
151 let brush = alpha_modulated_brush(
152 scope.brush.unwrap_or_else(|| Brush::solid(scope.color)),
153 scope.alpha,
154 );
155
156 let spread = scope.spread;
157 let rect = Rect {
158 x: scope.offset.x - spread,
159 y: scope.offset.y - spread,
160 width: size.width + spread * 2.0,
161 height: size.height + spread * 2.0,
162 };
163 if rect.width <= 0.0 || rect.height <= 0.0 {
164 return Vec::new();
165 }
166
167 let Some(shape_prim) = primitive_for_shape(shape, rect, brush) else {
168 return Vec::new();
169 };
170
171 let cutout = if scope.cutout {
172 let element_rect = Rect {
173 x: 0.0,
174 y: 0.0,
175 width: size.width,
176 height: size.height,
177 };
178 primitive_for_shape(shape, element_rect, Brush::solid(Color::BLACK)).map(Box::new)
179 } else {
180 None
181 };
182
183 vec![DrawPrimitive::Shadow(ShadowPrimitive::Drop {
184 shape: Box::new(shape_prim),
185 cutout,
186 blur_radius: scope.radius,
187 blend_mode: scope.blend_mode,
188 })]
189}
190
191fn build_inner_shadow_primitives(
192 size: Size,
193 shape: LayerShape,
194 scope: &ShadowScope,
195) -> Vec<DrawPrimitive> {
196 let Some(scope) = normalized_scope(scope) else {
197 return Vec::new();
198 };
199 if size.width <= 0.0 || size.height <= 0.0 {
200 return Vec::new();
201 }
202 if scope.radius <= f32::EPSILON
203 && scope.spread.abs() <= f32::EPSILON
204 && scope.offset.x.abs() <= f32::EPSILON
205 && scope.offset.y.abs() <= f32::EPSILON
206 {
207 return Vec::new();
208 }
209
210 let brush = alpha_modulated_brush(
211 scope.brush.unwrap_or_else(|| Brush::solid(scope.color)),
212 scope.alpha,
213 );
214
215 let outer = Rect {
216 x: 0.0,
217 y: 0.0,
218 width: size.width,
219 height: size.height,
220 };
221 let left = scope.offset.x + scope.spread;
222 let top = scope.offset.y + scope.spread;
223 let right = (scope.offset.x + size.width - scope.spread).max(left);
224 let bottom = (scope.offset.y + size.height - scope.spread).max(top);
225 let inner = Rect {
226 x: left,
227 y: top,
228 width: right - left,
229 height: bottom - top,
230 };
231 if inner.width <= 0.0 || inner.height <= 0.0 {
232 return Vec::new();
233 }
234
235 let Some(fill) = primitive_for_shape(shape, outer, brush) else {
236 return Vec::new();
237 };
238 let Some(cutout) = primitive_for_shape(shape, inner, Brush::solid(Color::WHITE)) else {
239 return Vec::new();
240 };
241
242 vec![DrawPrimitive::Shadow(ShadowPrimitive::Inner {
243 fill: Box::new(fill),
244 cutout: Box::new(cutout),
245 blur_radius: scope.radius,
246 blend_mode: scope.blend_mode,
247 clip_rect: outer,
248 })]
249}
250
251fn primitive_for_shape(shape: LayerShape, rect: Rect, brush: Brush) -> Option<DrawPrimitive> {
252 if rect.width <= 0.0 || rect.height <= 0.0 {
253 return None;
254 }
255
256 Some(match shape {
257 LayerShape::Rectangle => DrawPrimitive::Rect {
258 rect,
259 brush,
260 stroke: None,
261 },
262 LayerShape::Rounded(shape) => {
263 let radii = shape.resolve(rect.width, rect.height);
264 DrawPrimitive::RoundRect {
265 rect,
266 brush,
267 radii,
268 stroke: None,
269 }
270 }
271 })
272}
273
274fn alpha_modulated_brush(brush: Brush, alpha: f32) -> Brush {
275 let alpha = alpha.clamp(0.0, 1.0);
276 match brush {
277 Brush::Solid(color) => Brush::Solid(color.with_alpha(color.a() * alpha)),
278 Brush::LinearGradient {
279 colors,
280 stops,
281 start,
282 end,
283 tile_mode,
284 } => Brush::LinearGradient {
285 colors: colors
286 .into_iter()
287 .map(|color| color.with_alpha(color.a() * alpha))
288 .collect(),
289 stops,
290 start,
291 end,
292 tile_mode,
293 },
294 Brush::RadialGradient {
295 colors,
296 stops,
297 center,
298 radius,
299 tile_mode,
300 } => Brush::RadialGradient {
301 colors: colors
302 .into_iter()
303 .map(|color| color.with_alpha(color.a() * alpha))
304 .collect(),
305 stops,
306 center,
307 radius,
308 tile_mode,
309 },
310 Brush::SweepGradient {
311 colors,
312 stops,
313 center,
314 } => Brush::SweepGradient {
315 colors: colors
316 .into_iter()
317 .map(|color| color.with_alpha(color.a() * alpha))
318 .collect(),
319 stops,
320 center,
321 },
322 }
323}