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}