Skip to main content

cranpose_render_common/
layer_shadow.rs

1use cranpose_ui_graphics::{GraphicsLayer, Rect};
2
3use crate::layer_transform::layer_uniform_scale;
4
5const MIN_LAYER_SHADOW_SCALE: f32 = 0.1;
6const MIN_LAYER_SHADOW_SPREAD: f32 = 0.8;
7const MIN_AMBIENT_BLUR_RADIUS: f32 = 0.5;
8const MIN_SPOT_BLUR_RADIUS: f32 = 0.5;
9const AMBIENT_SPREAD_FACTOR: f32 = 0.24;
10const SPOT_OFFSET_X_FACTOR: f32 = 0.18;
11const SPOT_OFFSET_Y_FACTOR: f32 = 0.62;
12const AMBIENT_BLUR_FACTOR: f32 = 0.95;
13const SPOT_BLUR_FACTOR: f32 = 0.72;
14const SPOT_SPREAD_FACTOR: f32 = 0.72;
15const AMBIENT_ALPHA_FACTOR: f32 = 0.72;
16const SPOT_ALPHA_FACTOR: f32 = 0.96;
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct LayerShadowPass {
20    pub rect: Rect,
21    pub blur_radius: f32,
22    pub alpha: f32,
23}
24
25#[derive(Clone, Copy, Debug, Default, PartialEq)]
26pub struct LayerShadowGeometry {
27    pub ambient: Option<LayerShadowPass>,
28    pub spot: Option<LayerShadowPass>,
29}
30
31pub fn layer_shadow_geometry(
32    layer: &GraphicsLayer,
33    transformed_bounds: Rect,
34) -> LayerShadowGeometry {
35    if layer.shadow_elevation <= 0.0 {
36        return LayerShadowGeometry::default();
37    }
38
39    let scale = layer_uniform_scale(layer).max(MIN_LAYER_SHADOW_SCALE);
40    let elevation = layer.shadow_elevation * scale;
41    let spread = (elevation * AMBIENT_SPREAD_FACTOR).max(MIN_LAYER_SHADOW_SPREAD);
42    let ambient_alpha = (layer.ambient_shadow_color.a() * AMBIENT_ALPHA_FACTOR).clamp(0.0, 1.0);
43    let spot_alpha = (layer.spot_shadow_color.a() * SPOT_ALPHA_FACTOR).clamp(0.0, 1.0);
44
45    let ambient = (ambient_alpha > f32::EPSILON).then_some(LayerShadowPass {
46        rect: Rect {
47            x: transformed_bounds.x - spread,
48            y: transformed_bounds.y - spread,
49            width: transformed_bounds.width + spread * 2.0,
50            height: transformed_bounds.height + spread * 2.0,
51        },
52        blur_radius: (elevation * AMBIENT_BLUR_FACTOR).max(MIN_AMBIENT_BLUR_RADIUS),
53        alpha: ambient_alpha,
54    });
55
56    let spot_spread = spread * SPOT_SPREAD_FACTOR;
57    let spot = (spot_alpha > f32::EPSILON).then_some(LayerShadowPass {
58        rect: Rect {
59            x: transformed_bounds.x + elevation * SPOT_OFFSET_X_FACTOR - spot_spread,
60            y: transformed_bounds.y + elevation * SPOT_OFFSET_Y_FACTOR - spot_spread,
61            width: transformed_bounds.width + spot_spread * 2.0,
62            height: transformed_bounds.height + spot_spread * 2.0,
63        },
64        blur_radius: (elevation * SPOT_BLUR_FACTOR).max(MIN_SPOT_BLUR_RADIUS),
65        alpha: spot_alpha,
66    });
67
68    LayerShadowGeometry { ambient, spot }
69}
70
71#[cfg(test)]
72#[path = "tests/layer_shadow_tests.rs"]
73mod tests;