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)]
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}