Skip to main content

sequoia_style/
effect.rs

1#![forbid(unsafe_code)]
2
3use sequoia_core::{Color, SpanMask};
4
5use crate::filter::Mask;
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct Backdrop {
9    pub blur: f32,
10
11    pub dim: Color,
12
13    pub noise: f32,
14
15    pub radius: f32,
16
17    pub mask: Option<Mask>,
18}
19
20impl Backdrop {
21    pub const NONE: Self = Self {
22        blur: 0.0,
23        dim: Color::TRANSPARENT,
24        noise: 0.0,
25        radius: 0.0,
26        mask: None,
27    };
28
29    pub const XS: Self = Self::blur(2.0);
30    pub const SM: Self = Self::blur(3.0);
31    pub const MD: Self = Self::blur(4.0);
32
33    pub const fn blur(radius: f32) -> Self {
34        Self {
35            blur: if radius > 0.0 { radius } else { 0.0 },
36            ..Self::NONE
37        }
38    }
39
40    pub fn blur_radius(mut self, radius: f32) -> Self {
41        self.blur = radius.max(0.0);
42        self
43    }
44
45    pub const fn dim(mut self, color: Color) -> Self {
46        self.dim = color;
47        self
48    }
49
50    pub const fn noise(mut self, amount: f32) -> Self {
51        self.noise = if amount < 0.0 {
52            0.0
53        } else if amount > 1.0 {
54            1.0
55        } else {
56            amount
57        };
58        self
59    }
60
61    pub const fn radius(mut self, radius: f32) -> Self {
62        self.radius = if radius > 0.0 { radius } else { 0.0 };
63        self
64    }
65
66    pub fn mask(mut self, mask: Mask) -> Self {
67        self.mask = Some(mask);
68        self
69    }
70
71    pub fn is_empty(self) -> bool {
72        !(self.blur > 0.0 && self.blur.is_finite() || self.dim.a > 0.0 || self.noise > 0.0)
73    }
74
75    pub fn blur_px(self) -> f32 {
76        if self.blur.is_finite() {
77            self.blur.max(0.0)
78        } else {
79            0.0
80        }
81    }
82
83    pub fn span_mask(&self) -> SpanMask {
84        self.mask.map(|m| m.span_mask()).unwrap_or(SpanMask::None)
85    }
86}
87
88impl Default for Backdrop {
89    fn default() -> Self {
90        Self::NONE
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn none_is_empty() {
100        assert!(Backdrop::NONE.is_empty());
101        assert!(Backdrop::blur(0.0).is_empty());
102        assert!(Backdrop::blur(f32::NAN).is_empty());
103    }
104
105    #[test]
106    fn blur_dim_noise_are_visible() {
107        assert!(!Backdrop::blur(16.0).is_empty());
108        assert!(!Backdrop::NONE.dim(Color::BLACK.with_alpha(0.25)).is_empty());
109        assert!(!Backdrop::NONE.noise(0.06).is_empty());
110    }
111
112    #[test]
113    fn builder_chain() {
114        let b = Backdrop::blur(16.0)
115            .dim(Color::BLACK.with_alpha(0.25))
116            .noise(0.06)
117            .radius(12.0);
118        assert_eq!(b.blur, 16.0);
119        assert_eq!(b.radius, 12.0);
120        assert!((b.noise - 0.06).abs() < 1e-6);
121
122        assert!(Backdrop::NONE.radius(12.0).is_empty());
123
124        assert_eq!(Backdrop::blur(-4.0).blur, 0.0);
125    }
126}