Skip to main content

sequoia_view/
filter_view.rs

1#![forbid(unsafe_code)]
2
3use sequoia_core::Length;
4use sequoia_layout::LayoutStyle;
5use sequoia_style::{Backdrop, Effect, Filter};
6
7use crate::any::AnyView;
8use crate::view::{PaintCtx, View};
9
10pub struct DrawGradientMask<'a>(pub &'a sequoia_draw::Gradient);
11
12impl sequoia_style::GradientAlphas for DrawGradientMask<'_> {
13    fn endpoint_alphas(&self) -> (f32, f32) {
14        let stops = self.0.stops();
15        if stops.is_empty() {
16            return (1.0, 1.0);
17        }
18        (
19            stops.first().map(|s| s.color.a).unwrap_or(1.0),
20            stops.last().map(|s| s.color.a).unwrap_or(1.0),
21        )
22    }
23
24    fn direction(&self) -> (f32, f32) {
25        let d = self.0.direction();
26        (d.x, d.y)
27    }
28}
29
30pub fn draw_gradient_mask(gradient: &sequoia_draw::Gradient) -> sequoia_style::Mask {
31    sequoia_style::gradient_mask(DrawGradientMask(gradient))
32}
33
34#[derive(Clone)]
35pub struct Filtered {
36    child: AnyView,
37    filter: Filter,
38}
39
40impl Filtered {
41    pub fn new<V: View + 'static>(child: V, filter: Filter) -> Self {
42        Self {
43            child: AnyView::new(child),
44            filter,
45        }
46    }
47
48    pub fn style(&self) -> Filter {
49        self.filter
50    }
51}
52
53impl View for Filtered {
54    fn body(&self) -> impl View + 'static {
55        crate::views::Unit
56    }
57
58    fn children_override(&self) -> Option<Vec<AnyView>> {
59        Some(vec![self.child.clone()])
60    }
61
62    fn layout_style(&self) -> LayoutStyle {
63        LayoutStyle {
64            width: Length::Fit,
65            height: Length::Fit,
66            ..LayoutStyle::default()
67        }
68    }
69
70    fn foreground_filter(&self) -> Option<Filter> {
71        Some(self.filter)
72    }
73
74    fn paint_self(&self, _ctx: &mut PaintCtx) {}
75}
76
77#[derive(Clone)]
78pub struct BackdropFiltered {
79    child: AnyView,
80    filter: Backdrop,
81}
82
83impl BackdropFiltered {
84    pub fn new<V: View + 'static>(child: V, filter: Backdrop) -> Self {
85        Self {
86            child: AnyView::new(child),
87            filter,
88        }
89    }
90
91    pub fn style(&self) -> Backdrop {
92        self.filter
93    }
94}
95
96impl View for BackdropFiltered {
97    fn body(&self) -> impl View + 'static {
98        crate::views::Unit
99    }
100
101    fn children_override(&self) -> Option<Vec<AnyView>> {
102        Some(vec![self.child.clone()])
103    }
104
105    fn layout_style(&self) -> LayoutStyle {
106        LayoutStyle {
107            width: Length::Fit,
108            height: Length::Fit,
109            ..LayoutStyle::default()
110        }
111    }
112
113    fn paint_self(&self, ctx: &mut PaintCtx) {
114        if self.filter.is_empty() {
115            return;
116        }
117        ctx.push_backdrop(ctx.rect, self.filter);
118    }
119}
120
121#[derive(Clone)]
122pub struct EffectNode {
123    child: AnyView,
124    effect: Effect,
125}
126
127impl EffectNode {
128    pub fn new<V: View + 'static>(child: V, effect: Effect) -> Self {
129        Self {
130            child: AnyView::new(child),
131            effect,
132        }
133    }
134
135    pub fn effect(&self) -> &Effect {
136        &self.effect
137    }
138}
139
140impl View for EffectNode {
141    fn body(&self) -> impl View + 'static {
142        crate::views::Unit
143    }
144
145    fn children_override(&self) -> Option<Vec<AnyView>> {
146        Some(vec![self.child.clone()])
147    }
148
149    fn layout_style(&self) -> LayoutStyle {
150        LayoutStyle {
151            width: Length::Fit,
152            height: Length::Fit,
153            ..LayoutStyle::default()
154        }
155    }
156
157    fn foreground_filter(&self) -> Option<Filter> {
158        match &self.effect {
159            Effect::Blur(f) => Some(*f),
160            _ => None,
161        }
162    }
163
164    fn paint_self(&self, ctx: &mut PaintCtx) {
165        match &self.effect {
166            Effect::Backdrop(b) if !b.is_empty() => {
167                ctx.push_backdrop(ctx.rect, *b);
168            }
169            Effect::Shadow(spec) if spec.is_visible() => {
170                ctx.list.push_shadow(sequoia_core::Shadow {
171                    rect: ctx.rect,
172                    color: spec.color,
173                    radius: spec.spread.max(0.0),
174                    blur: spec.blur,
175                    spread: spec.spread,
176                    offset: spec.offset,
177                    inset: false,
178                });
179            }
180            _ => {}
181        }
182    }
183}
184
185pub trait FilterExt: View + Sized + 'static {
186    fn blur_own(self, radius: f32) -> Filtered {
187        Filtered::new(self, Filter::blur(radius))
188    }
189
190    fn blur_own_with(self, build: impl FnOnce(Filter) -> Filter) -> Filtered {
191        Filtered::new(self, build(Filter::NONE))
192    }
193
194    fn blur_behind(self, radius: f32) -> BackdropFiltered {
195        BackdropFiltered::new(self, Backdrop::blur(radius))
196    }
197
198    fn blur_behind_with(self, build: impl FnOnce(Backdrop) -> Backdrop) -> BackdropFiltered {
199        BackdropFiltered::new(self, build(Backdrop::NONE))
200    }
201
202    fn effect(self, effect: Effect) -> EffectNode {
203        EffectNode::new(self, effect)
204    }
205}
206
207impl<T: View + 'static> FilterExt for T {}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn blur_shortcuts_are_canonical() {
215        let w = crate::views::Unit.blur_behind(16.0);
216        assert_eq!(w.style().blur, 16.0);
217        assert!(!w.style().is_empty());
218        let w = crate::views::Unit.blur_own(4.0);
219        assert!(!w.style().is_empty());
220        let off = crate::views::Unit.blur_own(0.0);
221        assert!(off.style().is_empty());
222    }
223
224    #[test]
225    fn blur_with_builders() {
226        use sequoia_style::gradient;
227        let w = crate::views::Unit.blur_own_with(|b| b.blur_radius(4.0).mask(gradient(1.0, 0.0)));
228        assert!(w.style().mask.is_some());
229        let w =
230            crate::views::Unit.blur_behind_with(|b| b.blur_radius(8.0).mask(gradient(1.0, 0.0)));
231        assert!(w.style().mask.is_some());
232    }
233
234    #[test]
235    fn effect_node_routes_backdrop_and_shadow() {
236        let b = Effect::Backdrop(Backdrop::blur(8.0));
237        assert!(!b.is_empty());
238        let s = Effect::Shadow(sequoia_style::ShadowSpec::new(
239            sequoia_core::Color::BLACK,
240            8.0,
241        ));
242        assert!(!s.is_empty());
243        let _ = crate::views::Unit.effect(b);
244        let _ = crate::views::Unit.effect(s);
245    }
246}