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 {
49 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(layer)))
50 .with_inspector_metadata(inspector_metadata("graphicsLayer", |info| {
51 info.add_property("lazy", "true");
52 }));
53 self.then(modifier)
54 }
55
56 pub fn graphics_layer_value(self, layer: GraphicsLayer) -> Self {
61 let inspector_values = layer.clone();
62 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
63 .with_inspector_metadata(inspector_metadata("graphicsLayer", move |info| {
64 info.add_property("alpha", inspector_values.alpha.to_string());
65 info.add_property("scale", inspector_values.scale.to_string());
66 info.add_property("scaleX", inspector_values.scale_x.to_string());
67 info.add_property("scaleY", inspector_values.scale_y.to_string());
68 info.add_property("rotationX", inspector_values.rotation_x.to_string());
69 info.add_property("rotationY", inspector_values.rotation_y.to_string());
70 info.add_property("rotationZ", inspector_values.rotation_z.to_string());
71 info.add_property(
72 "cameraDistance",
73 inspector_values.camera_distance.to_string(),
74 );
75 info.add_property(
76 "transformOrigin",
77 format!(
78 "{},{}",
79 inspector_values.transform_origin.pivot_fraction_x,
80 inspector_values.transform_origin.pivot_fraction_y
81 ),
82 );
83 info.add_property("translationX", inspector_values.translation_x.to_string());
84 info.add_property("translationY", inspector_values.translation_y.to_string());
85 info.add_property(
86 "shadowElevation",
87 inspector_values.shadow_elevation.to_string(),
88 );
89 info.add_property("shape", format!("{:?}", inspector_values.shape));
90 info.add_property("clip", inspector_values.clip.to_string());
91 info.add_property(
92 "ambientShadowColor",
93 format!("{:?}", inspector_values.ambient_shadow_color),
94 );
95 info.add_property(
96 "spotShadowColor",
97 format!("{:?}", inspector_values.spot_shadow_color),
98 );
99 info.add_property(
100 "compositingStrategy",
101 format!("{:?}", inspector_values.compositing_strategy),
102 );
103 info.add_property("blendMode", format!("{:?}", inspector_values.blend_mode));
104 if let Some(filter) = inspector_values.color_filter {
105 info.add_property("colorFilter", format!("{filter:?}"));
106 }
107 }));
108 self.then(modifier)
109 }
110
111 #[allow(clippy::too_many_arguments)]
116 pub fn graphics_layer_params(
117 self,
118 scale_x: f32,
119 scale_y: f32,
120 alpha: f32,
121 translation_x: f32,
122 translation_y: f32,
123 shadow_elevation: f32,
124 rotation_x: f32,
125 rotation_y: f32,
126 rotation_z: f32,
127 camera_distance: f32,
128 transform_origin: TransformOrigin,
129 shape: LayerShape,
130 clip: bool,
131 render_effect: Option<RenderEffect>,
132 ambient_shadow_color: Color,
133 spot_shadow_color: Color,
134 compositing_strategy: CompositingStrategy,
135 blend_mode: BlendMode,
136 color_filter: Option<ColorFilter>,
137 ) -> Self {
138 self.graphics_layer_value(GraphicsLayer {
139 alpha,
140 scale: 1.0,
141 scale_x,
142 scale_y,
143 rotation_x,
144 rotation_y,
145 rotation_z,
146 camera_distance,
147 transform_origin,
148 translation_x,
149 translation_y,
150 shadow_elevation,
151 ambient_shadow_color,
152 spot_shadow_color,
153 shape,
154 clip,
155 compositing_strategy,
156 blend_mode,
157 color_filter,
158 render_effect,
159 backdrop_effect: None,
160 })
161 }
162
163 pub fn graphics_layer_block(self, configure: impl Fn(&mut GraphicsLayer) + 'static) -> Self {
168 self.graphics_layer(move || {
169 let mut layer = GraphicsLayer::default();
170 configure(&mut layer);
171 layer
172 })
173 }
174
175 pub fn shadow(self, elevation: f32) -> Self {
181 self.shadow_with(
182 elevation,
183 LayerShape::Rectangle,
184 elevation > 0.0,
185 Color::BLACK,
186 Color::BLACK,
187 )
188 }
189
190 pub fn shadow_with(
192 self,
193 elevation: f32,
194 shape: LayerShape,
195 clip: bool,
196 ambient_color: Color,
197 spot_color: Color,
198 ) -> Self {
199 let clamped_elevation = elevation.max(0.0);
200 if clamped_elevation == 0.0 && !clip {
201 return self;
202 }
203
204 self.graphics_layer_value(GraphicsLayer {
205 shadow_elevation: clamped_elevation,
206 ambient_shadow_color: ambient_color,
207 spot_shadow_color: spot_color,
208 shape,
209 clip,
210 ..Default::default()
211 })
212 }
213
214 pub fn backdrop_effect(self, effect: RenderEffect) -> Self {
216 let layer = GraphicsLayer {
217 backdrop_effect: Some(effect),
218 ..Default::default()
219 };
220 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
221 .with_inspector_metadata(inspector_metadata("backdropEffect", |info| {
222 info.add_property("enabled", "true");
223 }));
224 self.then(modifier)
225 }
226
227 pub fn backdrop_blur(self, radius: Dp) -> Self {
232 if radius.0 <= 0.0 {
233 return self.clip_to_bounds();
234 }
235
236 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
237 backdrop_blur_layer(radius, LayerShape::Rectangle)
238 })))
239 .with_inspector_metadata(inspector_metadata("backdropBlur", move |info| {
240 info.add_property("radius", radius.0.to_string());
241 }));
242 self.then(modifier)
243 }
244
245 pub fn backdrop_gradient_blur(
250 self,
251 start_radius: Dp,
252 end_radius: Dp,
253 direction: GradientBlurDirection,
254 ) -> Self {
255 if start_radius.0 <= 0.0 && end_radius.0 <= 0.0 {
256 return self.clip_to_bounds();
257 }
258 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
259 backdrop_gradient_blur_layer(start_radius, end_radius, direction)
260 })))
261 .with_inspector_metadata(inspector_metadata(
262 "backdropGradientBlur",
263 move |info| {
264 info.add_property("startRadius", start_radius.0.to_string());
265 info.add_property("endRadius", end_radius.0.to_string());
266 info.add_property("direction", format!("{direction:?}"));
267 },
268 ));
269 self.then(modifier)
270 }
271
272 pub fn glass_material(self, material: GlassMaterial) -> Self {
274 let blur_radius = material.blur_radius;
275 let shape = material.shape;
276 let modifier = Self::with_element(LazyGraphicsLayerElement::new(Rc::new(move || {
277 backdrop_blur_layer(blur_radius, LayerShape::Rounded(shape))
278 })))
279 .with_inspector_metadata(inspector_metadata("glassMaterial", move |info| {
280 info.add_property("blurRadius", blur_radius.0.to_string());
281 info.add_property("shape", format!("{shape:?}"));
282 }));
283
284 self.then(modifier)
285 .rounded_corner_shape(material.shape)
286 .background(material.tint)
287 }
288
289 pub fn shader_background(self, shader: RuntimeShader) -> Self {
291 self.backdrop_effect(RenderEffect::runtime_shader(shader))
292 }
293
294 pub fn color_filter(self, filter: ColorFilter) -> Self {
298 let layer = GraphicsLayer {
299 color_filter: Some(filter),
300 ..Default::default()
301 };
302 let modifier = Self::with_element(GraphicsLayerElement::new(layer))
303 .with_inspector_metadata(inspector_metadata("colorFilter", |info| {
304 info.add_property("enabled", "true");
305 }));
306 self.then(modifier)
307 }
308
309 pub fn tint(self, tint: Color) -> Self {
311 self.color_filter(ColorFilter::tint(tint))
312 }
313
314 pub fn compositing_strategy(self, strategy: CompositingStrategy) -> Self {
316 self.graphics_layer_value(GraphicsLayer {
317 compositing_strategy: strategy,
318 ..Default::default()
319 })
320 }
321
322 pub fn layer_blend_mode(self, blend_mode: BlendMode) -> Self {
327 self.graphics_layer_value(GraphicsLayer {
328 blend_mode,
329 ..Default::default()
330 })
331 }
332
333 pub fn gradient_cut_mask(
337 self,
338 area_width: f32,
339 area_height: f32,
340 spec: GradientCutMaskSpec,
341 ) -> Self {
342 let layer = GraphicsLayer {
343 render_effect: Some(gradient_cut_mask_effect(&spec, area_width, area_height)),
344 ..Default::default()
345 };
346 self.graphics_layer_value(layer)
347 }
348
349 pub fn rounded_alpha_mask(
354 self,
355 area_width: f32,
356 area_height: f32,
357 corner_radius: f32,
358 edge_feather: f32,
359 ) -> Self {
360 let has_radius = corner_radius.is_finite() && corner_radius > 0.0;
361 let has_feather = edge_feather.is_finite() && edge_feather > 0.0;
362 if !has_radius && !has_feather {
363 return self;
364 }
365 let layer = GraphicsLayer {
366 render_effect: Some(rounded_alpha_mask_effect(
367 area_width,
368 area_height,
369 corner_radius,
370 edge_feather,
371 )),
372 ..Default::default()
373 };
374 self.graphics_layer_value(layer)
375 }
376
377 pub fn gradient_fade_dst_out(
382 self,
383 area_width: f32,
384 area_height: f32,
385 spec: GradientFadeMaskSpec,
386 ) -> Self {
387 let layer = GraphicsLayer {
388 render_effect: Some(gradient_fade_dst_out_effect(&spec, area_width, area_height)),
389 ..Default::default()
390 };
391 self.graphics_layer_value(layer)
392 }
393}
394
395#[derive(Clone, Copy, Debug, PartialEq)]
397pub struct GlassMaterial {
398 pub blur_radius: Dp,
399 pub tint: Color,
400 pub shape: RoundedCornerShape,
401}
402
403impl GlassMaterial {
404 pub fn new(blur_radius: Dp, tint: Color, shape: RoundedCornerShape) -> Self {
405 Self {
406 blur_radius,
407 tint,
408 shape,
409 }
410 }
411}