Skip to main content

cranpose_ui_graphics/
gradient_blur.rs

1//! Spatially varying backdrop blur.
2//!
3//! Unlike an alpha gradient drawn over a uniformly blurred surface, this
4//! shader changes the sampling kernel radius at every fragment. It is intended
5//! for edge-to-edge system-bar treatments where content should move smoothly
6//! from sharp to fully frosted without a visible material boundary.
7
8use crate::{RenderEffect, RuntimeShader};
9
10/// Axis and direction used to interpolate the blur radius.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum GradientBlurDirection {
13    LeftToRight,
14    RightToLeft,
15    #[default]
16    TopToBottom,
17    BottomToTop,
18}
19
20impl GradientBlurDirection {
21    fn uniform_code(self) -> f32 {
22        match self {
23            Self::LeftToRight => 0.0,
24            Self::RightToLeft => 1.0,
25            Self::TopToBottom => 2.0,
26            Self::BottomToTop => 3.0,
27        }
28    }
29}
30
31/// WGSL implementation of a per-fragment Gaussian-style blur kernel.
32pub const GRADIENT_BLUR_WGSL: &str = include_str!("../shaders/gradient_blur.wgsl");
33
34/// Build a spatially varying blur effect.
35///
36/// Radii are physical pixels. The radius interpolates smoothly across the
37/// complete effect bounds in `direction`; callers normally reach this through
38/// `Modifier::backdrop_gradient_blur`, which converts from dp automatically.
39pub fn gradient_blur_effect(
40    start_radius_px: f32,
41    end_radius_px: f32,
42    direction: GradientBlurDirection,
43) -> RenderEffect {
44    let start_radius_px = start_radius_px.max(0.0);
45    let end_radius_px = end_radius_px.max(0.0);
46    let mut shader = RuntimeShader::new(GRADIENT_BLUR_WGSL);
47    shader.set_float(0, start_radius_px);
48    shader.set_float(1, end_radius_px);
49    shader.set_float(2, direction.uniform_code());
50    shader.set_input_padding(start_radius_px.max(end_radius_px).ceil());
51    RenderEffect::runtime_shader(shader)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn gradient_blur_carries_endpoint_radii_and_capture_padding() {
60        let RenderEffect::Shader { shader } =
61            gradient_blur_effect(0.5, 18.25, GradientBlurDirection::BottomToTop)
62        else {
63            panic!("gradient blur must use the spatial runtime shader");
64        };
65        assert_eq!(&shader.uniforms()[..3], &[0.5, 18.25, 3.0]);
66        assert_eq!(shader.input_padding(), 19.0);
67    }
68}