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