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