sequoia_view/
filter_view.rs1#![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)]
131pub struct EffectNode {
132 child: AnyView,
133 effect: Effect,
134}
135
136impl EffectNode {
137 pub fn new<V: View + 'static>(child: V, effect: Effect) -> Self {
138 Self {
139 child: AnyView::new(child),
140 effect,
141 }
142 }
143
144 pub fn effect(&self) -> &Effect {
145 &self.effect
146 }
147}
148
149impl View for EffectNode {
150 fn body(&self) -> impl View + 'static {
151 crate::views::Unit
152 }
153
154 fn children_override(&self) -> Option<Vec<AnyView>> {
155 Some(vec![self.child.clone()])
156 }
157
158 fn layout_style(&self) -> LayoutStyle {
159 LayoutStyle {
160 width: Length::Fit,
161 height: Length::Fit,
162 ..LayoutStyle::default()
163 }
164 }
165
166 fn foreground_filter(&self) -> Option<Filter> {
167 match &self.effect {
168 Effect::Blur(f) => Some(*f),
169 _ => None,
170 }
171 }
172
173 fn paint_self(&self, ctx: &mut PaintCtx) {
174 match &self.effect {
175 Effect::Backdrop(b) if !b.is_empty() => {
176 ctx.push_backdrop(ctx.rect, *b);
177 }
178 Effect::Shadow(spec) if spec.is_visible() => {
179 ctx.list.push_shadow(sequoia_core::Shadow {
180 rect: ctx.rect,
181 color: spec.color,
182 radius: spec.spread.max(0.0),
183 blur: spec.blur,
184 spread: spec.spread,
185 offset: spec.offset,
186 inset: false,
187 });
188 }
189 _ => {}
190 }
191 }
192}
193
194pub trait FilterExt: View + Sized + 'static {
195 fn blur_own(self, radius: f32) -> Filtered {
196 Filtered::new(self, Filter::blur(radius))
197 }
198
199 fn blur_own_with(self, build: impl FnOnce(Filter) -> Filter) -> Filtered {
200 Filtered::new(self, build(Filter::NONE))
201 }
202
203 fn blur_behind(self, radius: f32) -> BackdropFiltered {
204 BackdropFiltered::new(self, Backdrop::blur(radius))
205 }
206
207 fn blur_behind_with(self, build: impl FnOnce(Backdrop) -> Backdrop) -> BackdropFiltered {
208 BackdropFiltered::new(self, build(Backdrop::NONE))
209 }
210
211 fn effect(self, effect: Effect) -> EffectNode {
212 EffectNode::new(self, effect)
213 }
214}
215
216impl<T: View + 'static> FilterExt for T {}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn blur_shortcuts_are_canonical() {
224 let w = crate::views::Unit.blur_behind(16.0);
225 assert_eq!(w.style().blur, 16.0);
226 assert!(!w.style().is_empty());
227 let w = crate::views::Unit.blur_own(4.0);
228 assert!(!w.style().is_empty());
229 let off = crate::views::Unit.blur_own(0.0);
230 assert!(off.style().is_empty());
231 }
232
233 #[test]
234 fn blur_with_builders() {
235 use sequoia_style::gradient;
236 let w = crate::views::Unit.blur_own_with(|b| b.blur_radius(4.0).mask(gradient(1.0, 0.0)));
237 assert!(w.style().mask.is_some());
238 let w =
239 crate::views::Unit.blur_behind_with(|b| b.blur_radius(8.0).mask(gradient(1.0, 0.0)));
240 assert!(w.style().mask.is_some());
241 }
242
243 #[test]
244 fn effect_node_routes_backdrop_and_shadow() {
245 let b = Effect::Backdrop(Backdrop::blur(8.0));
246 assert!(!b.is_empty());
247 let s = Effect::Shadow(sequoia_style::ShadowSpec::new(
248 sequoia_core::Color::BLACK,
249 8.0,
250 ));
251 assert!(!s.is_empty());
252 let _ = crate::views::Unit.effect(b);
253 let _ = crate::views::Unit.effect(s);
254 }
255}