1use super::{inspector_metadata, GraphicsLayer, Modifier};
2use crate::modifier_nodes::{GraphicsLayerElement, LazyGraphicsLayerElement};
3use cranpose_ui_graphics::{
4 gradient_blur_effect, gradient_cut_mask_effect, gradient_fade_dst_out_effect,
5 rounded_alpha_mask_effect, BlendMode, Color, ColorFilter, CompositingStrategy, Dp,
6 GradientBlurDirection, GradientCutMaskSpec, GradientFadeMaskSpec, LayerShape, RenderEffect,
7 RoundedCornerShape, RuntimeShader, TransformOrigin,
8};
9use std::rc::Rc;
10
11fn backdrop_blur_layer(radius: Dp, shape: LayerShape) -> GraphicsLayer {
12 let radius_px = radius
13 .to_px(crate::render_state::current_density())
14 .max(0.0);
15 GraphicsLayer {
16 backdrop_effect: (radius_px > 0.0).then(|| RenderEffect::blur(radius_px)),
17 shape,
18 clip: true,
19 ..Default::default()
20 }
21}
22
23fn backdrop_gradient_blur_layer(
24 start_radius: Dp,
25 end_radius: Dp,
26 direction: GradientBlurDirection,
27) -> GraphicsLayer {
28 let density = crate::render_state::current_density();
29 let start_px = start_radius.to_px(density).max(0.0);
30 let end_px = end_radius.to_px(density).max(0.0);
31 GraphicsLayer {
32 backdrop_effect: (start_px > 0.0 || end_px > 0.0)
33 .then(|| gradient_blur_effect(start_px, end_px, direction)),
34 shape: LayerShape::Rectangle,
35 clip: true,
36 ..Default::default()
37 }
38}
39
40impl Modifier {
41 pub fn graphics_layer(self, layer: impl Fn() -> GraphicsLayer + 'static) -> Self {
77 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(layer)))
78 .with_inspector_metadata(inspector_metadata("graphicsLayer", |info| {
79 info.add_property("lazy", "true");
80 }));
81 self.then(modifier)
82 }
83
84 pub fn graphics_layer_value(self, layer: GraphicsLayer) -> Self {
89 let inspector_values = layer.clone();
90 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
91 .with_inspector_metadata(inspector_metadata("graphicsLayer", move |info| {
92 info.add_property("alpha", inspector_values.alpha.to_string());
93 info.add_property("scale", inspector_values.scale.to_string());
94 info.add_property("scaleX", inspector_values.scale_x.to_string());
95 info.add_property("scaleY", inspector_values.scale_y.to_string());
96 info.add_property("rotationX", inspector_values.rotation_x.to_string());
97 info.add_property("rotationY", inspector_values.rotation_y.to_string());
98 info.add_property("rotationZ", inspector_values.rotation_z.to_string());
99 info.add_property(
100 "cameraDistance",
101 inspector_values.camera_distance.to_string(),
102 );
103 info.add_property(
104 "transformOrigin",
105 format!(
106 "{},{}",
107 inspector_values.transform_origin.pivot_fraction_x,
108 inspector_values.transform_origin.pivot_fraction_y
109 ),
110 );
111 info.add_property("translationX", inspector_values.translation_x.to_string());
112 info.add_property("translationY", inspector_values.translation_y.to_string());
113 info.add_property(
114 "shadowElevation",
115 inspector_values.shadow_elevation.to_string(),
116 );
117 info.add_property("shape", format!("{:?}", inspector_values.shape));
118 info.add_property("clip", inspector_values.clip.to_string());
119 info.add_property(
120 "ambientShadowColor",
121 format!("{:?}", inspector_values.ambient_shadow_color),
122 );
123 info.add_property(
124 "spotShadowColor",
125 format!("{:?}", inspector_values.spot_shadow_color),
126 );
127 info.add_property(
128 "compositingStrategy",
129 format!("{:?}", inspector_values.compositing_strategy),
130 );
131 info.add_property("blendMode", format!("{:?}", inspector_values.blend_mode));
132 if let Some(filter) = inspector_values.color_filter {
133 info.add_property("colorFilter", format!("{filter:?}"));
134 }
135 }));
136 self.then(modifier)
137 }
138
139 #[allow(clippy::too_many_arguments)]
144 pub fn graphics_layer_params(
145 self,
146 scale_x: f32,
147 scale_y: f32,
148 alpha: f32,
149 translation_x: f32,
150 translation_y: f32,
151 shadow_elevation: f32,
152 rotation_x: f32,
153 rotation_y: f32,
154 rotation_z: f32,
155 camera_distance: f32,
156 transform_origin: TransformOrigin,
157 shape: LayerShape,
158 clip: bool,
159 render_effect: Option<RenderEffect>,
160 ambient_shadow_color: Color,
161 spot_shadow_color: Color,
162 compositing_strategy: CompositingStrategy,
163 blend_mode: BlendMode,
164 color_filter: Option<ColorFilter>,
165 ) -> Self {
166 self.graphics_layer_value(GraphicsLayer {
167 alpha,
168 scale: 1.0,
169 scale_x,
170 scale_y,
171 rotation_x,
172 rotation_y,
173 rotation_z,
174 camera_distance,
175 transform_origin,
176 translation_x,
177 translation_y,
178 shadow_elevation,
179 ambient_shadow_color,
180 spot_shadow_color,
181 shape,
182 clip,
183 compositing_strategy,
184 blend_mode,
185 color_filter,
186 render_effect,
187 backdrop_effect: None,
188 })
189 }
190
191 pub fn graphics_layer_block(self, configure: impl Fn(&mut GraphicsLayer) + 'static) -> Self {
196 self.graphics_layer(move || {
197 let mut layer = GraphicsLayer::default();
198 configure(&mut layer);
199 layer
200 })
201 }
202
203 pub fn shadow(self, elevation: f32) -> Self {
209 self.shadow_with(
210 elevation,
211 LayerShape::Rectangle,
212 elevation > 0.0,
213 Color::BLACK,
214 Color::BLACK,
215 )
216 }
217
218 pub fn shadow_with(
220 self,
221 elevation: f32,
222 shape: LayerShape,
223 clip: bool,
224 ambient_color: Color,
225 spot_color: Color,
226 ) -> Self {
227 let clamped_elevation = elevation.max(0.0);
228 if clamped_elevation == 0.0 && !clip {
229 return self;
230 }
231
232 self.graphics_layer_value(GraphicsLayer {
233 shadow_elevation: clamped_elevation,
234 ambient_shadow_color: ambient_color,
235 spot_shadow_color: spot_color,
236 shape,
237 clip,
238 ..Default::default()
239 })
240 }
241
242 pub fn backdrop_effect(self, effect: RenderEffect) -> Self {
244 let layer = GraphicsLayer {
245 backdrop_effect: Some(effect),
246 ..Default::default()
247 };
248 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
249 .with_inspector_metadata(inspector_metadata("backdropEffect", |info| {
250 info.add_property("enabled", "true");
251 }));
252 self.then(modifier)
253 }
254
255 pub fn backdrop_blur(self, radius: Dp) -> Self {
260 if radius.0 <= 0.0 {
261 return self.clip_to_bounds();
262 }
263
264 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
265 backdrop_blur_layer(radius, LayerShape::Rectangle)
266 })))
267 .with_inspector_metadata(inspector_metadata("backdropBlur", move |info| {
268 info.add_property("radius", radius.0.to_string());
269 }));
270 self.then(modifier)
271 }
272
273 pub fn backdrop_gradient_blur(
278 self,
279 start_radius: Dp,
280 end_radius: Dp,
281 direction: GradientBlurDirection,
282 ) -> Self {
283 if start_radius.0 <= 0.0 && end_radius.0 <= 0.0 {
284 return self.clip_to_bounds();
285 }
286 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
287 backdrop_gradient_blur_layer(start_radius, end_radius, direction)
288 })))
289 .with_inspector_metadata(inspector_metadata(
290 "backdropGradientBlur",
291 move |info| {
292 info.add_property("startRadius", start_radius.0.to_string());
293 info.add_property("endRadius", end_radius.0.to_string());
294 info.add_property("direction", format!("{direction:?}"));
295 },
296 ));
297 self.then(modifier)
298 }
299
300 pub fn glass_material(self, material: GlassMaterial) -> Self {
302 let blur_radius = material.blur_radius;
303 let shape = material.shape;
304 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
305 backdrop_blur_layer(blur_radius, LayerShape::Rounded(shape))
306 })))
307 .with_inspector_metadata(inspector_metadata("glassMaterial", move |info| {
308 info.add_property("blurRadius", blur_radius.0.to_string());
309 info.add_property("shape", format!("{shape:?}"));
310 }));
311
312 self.then(modifier)
313 .rounded_corner_shape(material.shape)
314 .background(material.tint)
315 }
316
317 pub fn shader_background(self, shader: RuntimeShader) -> Self {
319 self.backdrop_effect(RenderEffect::runtime_shader(shader))
320 }
321
322 pub fn color_filter(self, filter: ColorFilter) -> Self {
326 let layer = GraphicsLayer {
327 color_filter: Some(filter),
328 ..Default::default()
329 };
330 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
331 .with_inspector_metadata(inspector_metadata("colorFilter", |info| {
332 info.add_property("enabled", "true");
333 }));
334 self.then(modifier)
335 }
336
337 pub fn tint(self, tint: Color) -> Self {
339 self.color_filter(ColorFilter::tint(tint))
340 }
341
342 pub fn compositing_strategy(self, strategy: CompositingStrategy) -> Self {
344 self.graphics_layer_value(GraphicsLayer {
345 compositing_strategy: strategy,
346 ..Default::default()
347 })
348 }
349
350 pub fn layer_blend_mode(self, blend_mode: BlendMode) -> Self {
355 self.graphics_layer_value(GraphicsLayer {
356 blend_mode,
357 ..Default::default()
358 })
359 }
360
361 pub fn gradient_cut_mask(
365 self,
366 area_width: f32,
367 area_height: f32,
368 spec: GradientCutMaskSpec,
369 ) -> Self {
370 let layer = GraphicsLayer {
371 render_effect: Some(gradient_cut_mask_effect(&spec, area_width, area_height)),
372 ..Default::default()
373 };
374 self.graphics_layer_value(layer)
375 }
376
377 pub fn rounded_alpha_mask(
382 self,
383 area_width: f32,
384 area_height: f32,
385 corner_radius: f32,
386 edge_feather: f32,
387 ) -> Self {
388 let has_radius = corner_radius.is_finite() && corner_radius > 0.0;
389 let has_feather = edge_feather.is_finite() && edge_feather > 0.0;
390 if !has_radius && !has_feather {
391 return self;
392 }
393 let layer = GraphicsLayer {
394 render_effect: Some(rounded_alpha_mask_effect(
395 area_width,
396 area_height,
397 corner_radius,
398 edge_feather,
399 )),
400 ..Default::default()
401 };
402 self.graphics_layer_value(layer)
403 }
404
405 pub fn gradient_fade_dst_out(
410 self,
411 area_width: f32,
412 area_height: f32,
413 spec: GradientFadeMaskSpec,
414 ) -> Self {
415 let layer = GraphicsLayer {
416 render_effect: Some(gradient_fade_dst_out_effect(&spec, area_width, area_height)),
417 ..Default::default()
418 };
419 self.graphics_layer_value(layer)
420 }
421}
422
423#[derive(Clone, Copy, Debug, PartialEq)]
425pub struct GlassMaterial {
426 pub blur_radius: Dp,
427 pub tint: Color,
428 pub shape: RoundedCornerShape,
429}
430
431impl GlassMaterial {
432 pub fn new(blur_radius: Dp, tint: Color, shape: RoundedCornerShape) -> Self {
433 Self {
434 blur_radius,
435 tint,
436 shape,
437 }
438 }
439}