zng_wgt_filter/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Color filter properties, [`opacity`](fn@opacity), [`filter`](fn@filter) and more.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use zng_color::filter::{ColorMatrix, Filter};
13use zng_wgt::prelude::*;
14
15/// Color filter, or combination of filters.
16///
17/// This property allows setting multiple filters at once, there is also a property for every
18/// filter for easier value updating.
19///
20/// # Performance
21///
22/// The performance for setting specific filter properties versus this one is the same, except for [`opacity`]
23/// which can be animated using only frame updates instead of generating a new frame every change.
24///
25/// [`opacity`]: fn@opacity
26#[property(CONTEXT, default(Filter::default()))]
27pub fn filter(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
28    filter_any(child, filter, false)
29}
30
31/// Backdrop filter, or combination of filters.
32///
33/// This property allows setting multiple filters at once, there is also a property for every
34/// filter for easier value updating.
35///
36/// The filters are applied to everything rendered behind the widget.
37///
38/// # Performance
39///
40/// The performance for setting specific filter properties versus this one is the same.
41///
42/// [`opacity`]: fn@opacity
43#[property(CONTEXT, default(Filter::default()))]
44pub fn backdrop_filter(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
45    backdrop_filter_any(child, filter)
46}
47
48/// Color filter, or combination of filters targeting the widget's descendants and not the widget itself.
49///
50/// This property allows setting multiple filters at once, there is also a property for every
51/// filter for easier value updating.
52///
53/// # Performance
54///
55/// The performance for setting specific filter properties versus this one is the same, except for [`child_opacity`]
56/// which can be animated using only frame updates instead of generating a new frame every change.
57///
58/// [`child_opacity`]: fn@child_opacity
59#[property(CHILD_CONTEXT, default(Filter::default()))]
60pub fn child_filter(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
61    filter_any(child, filter, true)
62}
63
64/// Inverts the colors of the widget.
65///
66/// Zero does not invert, one fully inverts.
67///
68/// This property is a shorthand way of setting [`filter`] to [`Filter::new_invert`] using variable mapping.
69///
70/// [`filter`]: fn@filter
71/// [`Filter::new_invert`]: zng_color::filter::Filter::new_invert
72#[property(CONTEXT, default(false))]
73pub fn invert_color(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
74    filter_render(child, amount.into_var().map(|&a| Filter::new_invert(a)), false)
75}
76
77/// Inverts the colors of everything behind the widget.
78///
79/// Zero does not invert, one fully inverts.
80///
81/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_invert`] using variable mapping.
82///
83/// [`backdrop_filter`]: fn@backdrop_filter
84/// [`Filter::new_invert`]: zng_color::filter::Filter::new_invert
85#[property(CONTEXT, default(false))]
86pub fn backdrop_invert(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
87    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_invert(a)))
88}
89
90/// Blur the widget.
91///
92/// This property is a shorthand way of setting [`filter`] to [`Filter::new_blur`] using variable mapping.
93///
94/// [`filter`]: fn@filter
95/// [`Filter::new_blur`]: zng_color::filter::Filter::new_blur
96#[property(CONTEXT, default(0))]
97pub fn blur(child: impl UiNode, radius: impl IntoVar<Length>) -> impl UiNode {
98    filter_layout(child, radius.into_var().map(|r| Filter::new_blur(r.clone())), false)
99}
100
101/// Blur the everything behind the widget.
102///
103/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_blur`] using variable mapping.
104///
105/// [`backdrop_filter`]: fn@backdrop_filter
106/// [`Filter::new_blur`]: zng_color::filter::Filter::new_blur
107#[property(CONTEXT, default(0))]
108pub fn backdrop_blur(child: impl UiNode, radius: impl IntoVar<Length>) -> impl UiNode {
109    backdrop_filter_layout(child, radius.into_var().map(|r| Filter::new_blur(r.clone())))
110}
111
112/// Sepia tone the widget.
113///
114/// zero is the original colors, one is the full desaturated brown look.
115///
116/// This property is a shorthand way of setting [`filter`] to [`Filter::new_sepia`] using variable mapping.
117///
118/// [`filter`]: fn@filter
119/// [`Filter::new_sepia`]: zng_color::filter::Filter::new_sepia
120#[property(CONTEXT, default(false))]
121pub fn sepia(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
122    filter_render(child, amount.into_var().map(|&a| Filter::new_sepia(a)), false)
123}
124
125/// Sepia tone everything behind the widget.
126///
127/// zero is the original colors, one is the full desaturated brown look.
128///
129/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_sepia`] using variable mapping.
130///
131/// [`backdrop_filter`]: fn@backdrop_filter
132/// [`Filter::new_sepia`]: zng_color::filter::Filter::new_sepia
133#[property(CONTEXT, default(false))]
134pub fn backdrop_sepia(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
135    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_sepia(a)))
136}
137
138/// Grayscale tone the widget.
139///
140/// Zero is the original colors, one if the full grayscale.
141///
142/// This property is a shorthand way of setting [`filter`] to [`Filter::new_grayscale`] using variable mapping.
143///
144/// [`filter`]: fn@filter
145/// [`Filter::new_grayscale`]: zng_color::filter::Filter::new_grayscale
146#[property(CONTEXT, default(false))]
147pub fn grayscale(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
148    filter_render(child, amount.into_var().map(|&a| Filter::new_grayscale(a)), false)
149}
150
151/// Grayscale tone everything behind the widget.
152///
153/// Zero is the original colors, one if the full grayscale.
154///
155/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_grayscale`] using variable mapping.
156///
157/// [`backdrop_filter`]: fn@backdrop_filter
158/// [`Filter::new_grayscale`]: zng_color::filter::Filter::new_grayscale
159#[property(CONTEXT, default(false))]
160pub fn backdrop_grayscale(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
161    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_grayscale(a)))
162}
163
164/// Drop-shadow effect for the widget.
165///
166/// The shadow is *pixel accurate*.
167///
168/// This property is a shorthand way of setting [`filter`] to [`Filter::new_drop_shadow`] using variable merging.
169///
170/// [`filter`]: fn@filter
171/// [`Filter::new_drop_shadow`]: zng_color::filter::Filter::new_drop_shadow
172#[property(CONTEXT, default((0, 0), 0, colors::BLACK.transparent()))]
173pub fn drop_shadow(
174    child: impl UiNode,
175    offset: impl IntoVar<Point>,
176    blur_radius: impl IntoVar<Length>,
177    color: impl IntoVar<Rgba>,
178) -> impl UiNode {
179    filter_layout(
180        child,
181        merge_var!(offset.into_var(), blur_radius.into_var(), color.into_var(), |o, r, &c| {
182            Filter::new_drop_shadow(o.clone(), r.clone(), c)
183        }),
184        false,
185    )
186}
187
188/// Adjust the widget colors brightness.
189///
190/// Zero removes all brightness, one is the original brightness.
191///
192/// This property is a shorthand way of setting [`filter`] to [`Filter::new_brightness`] using variable mapping.
193///
194/// [`filter`]: fn@filter
195/// [`Filter::new_brightness`]: zng_color::filter::Filter::new_brightness
196#[property(CONTEXT, default(1.0))]
197pub fn brightness(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
198    filter_render(child, amount.into_var().map(|&a| Filter::new_brightness(a)), false)
199}
200
201/// Adjust color brightness of everything behind the widget.
202///
203/// Zero removes all brightness, one is the original brightness.
204///
205/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_brightness`] using variable mapping.
206///
207/// [`backdrop_filter`]: fn@backdrop_filter
208/// [`Filter::new_brightness`]: zng_color::filter::Filter::new_brightness
209#[property(CONTEXT, default(1.0))]
210pub fn backdrop_brightness(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
211    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_brightness(a)))
212}
213
214/// Adjust the widget colors contrast.
215///
216/// Zero removes all contrast, one is the original contrast.
217///
218/// This property is a shorthand way of setting [`filter`] to [`Filter::new_contrast`] using variable mapping.
219///
220/// [`filter`]: fn@filter
221/// [`Filter::new_contrast`]: zng_color::filter::Filter::new_contrast
222#[property(CONTEXT, default(1.0))]
223pub fn contrast(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
224    filter_render(child, amount.into_var().map(|&a| Filter::new_contrast(a)), false)
225}
226
227/// Adjust the color contrast of everything behind the widget.
228///
229/// Zero removes all contrast, one is the original contrast.
230///
231/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_contrast`] using variable mapping.
232///
233/// [`backdrop_filter`]: fn@backdrop_filter
234/// [`Filter::new_contrast`]: zng_color::filter::Filter::new_contrast
235#[property(CONTEXT, default(1.0))]
236pub fn backdrop_contrast(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
237    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_contrast(a)))
238}
239
240/// Adjust the widget colors saturation.
241///
242/// Zero fully desaturates, one is the original saturation.
243///
244/// This property is a shorthand way of setting [`filter`] to [`Filter::new_saturate`] using variable mapping.
245///
246/// [`filter`]: fn@filter
247/// [`Filter::new_saturate`]: zng_color::filter::Filter::new_saturate
248#[property(CONTEXT, default(1.0))]
249pub fn saturate(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
250    filter_render(child, amount.into_var().map(|&a| Filter::new_saturate(a)), false)
251}
252
253/// Adjust color saturation of everything behind the widget.
254///
255/// Zero fully desaturates, one is the original saturation.
256///
257/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_saturate`] using variable mapping.
258///
259/// [`backdrop_filter`]: fn@backdrop_filter
260/// [`Filter::new_saturate`]: zng_color::filter::Filter::new_saturate
261#[property(CONTEXT, default(1.0))]
262pub fn backdrop_saturate(child: impl UiNode, amount: impl IntoVar<Factor>) -> impl UiNode {
263    backdrop_filter_render(child, amount.into_var().map(|&a| Filter::new_saturate(a)))
264}
265
266/// Hue shift the widget colors.
267///
268/// Adds `angle` to the [`hue`] of the widget colors.
269///
270/// This property is a shorthand way of setting [`filter`] to [`Filter::new_hue_rotate`] using variable mapping.
271///
272/// [`filter`]: fn@filter
273/// [`hue`]: Hsla::hue
274/// [`Filter::new_hue_rotate`]: zng_color::filter::Filter::new_hue_rotate
275#[property(CONTEXT, default(0.deg()))]
276pub fn hue_rotate(child: impl UiNode, angle: impl IntoVar<AngleDegree>) -> impl UiNode {
277    filter_render(child, angle.into_var().map(|&a| Filter::new_hue_rotate(a)), false)
278}
279
280/// Hue shift the colors behind the widget.
281///
282/// Adds `angle` to the [`hue`] of the widget colors.
283///
284/// This property is a shorthand way of setting [`backdrop_filter`] to [`Filter::new_hue_rotate`] using variable mapping.
285///
286/// [`backdrop_filter`]: fn@backdrop_filter
287/// [`hue`]: Hsla::hue
288/// [`Filter::new_hue_rotate`]: zng_color::filter::Filter::new_hue_rotate
289#[property(CONTEXT, default(0.deg()))]
290pub fn backdrop_hue_rotate(child: impl UiNode, angle: impl IntoVar<AngleDegree>) -> impl UiNode {
291    backdrop_filter_render(child, angle.into_var().map(|&a| Filter::new_hue_rotate(a)))
292}
293
294/// Custom color filter.
295///
296/// The color matrix is in the format of SVG color matrix, [0..5] is the first matrix row.
297#[property(CONTEXT, default(ColorMatrix::identity()))]
298pub fn color_matrix(child: impl UiNode, matrix: impl IntoVar<ColorMatrix>) -> impl UiNode {
299    filter_render(child, matrix.into_var().map(|&m| Filter::new_color_matrix(m)), false)
300}
301
302/// Custom backdrop filter.
303///
304/// The color matrix is in the format of SVG color matrix, [0..5] is the first matrix row.
305#[property(CONTEXT, default(ColorMatrix::identity()))]
306pub fn backdrop_color_matrix(child: impl UiNode, matrix: impl IntoVar<ColorMatrix>) -> impl UiNode {
307    backdrop_filter_render(child, matrix.into_var().map(|&m| Filter::new_color_matrix(m)))
308}
309
310/// Opacity/transparency of the widget.
311///
312/// This property provides the same visual result as setting [`filter`] to [`Filter::new_opacity`],
313/// **but** updating the opacity is faster in this property.
314///
315/// [`filter`]: fn@filter
316/// [`Filter::new_opacity`]: zng_color::filter::Filter::new_opacity
317#[property(CONTEXT, default(1.0))]
318pub fn opacity(child: impl UiNode, alpha: impl IntoVar<Factor>) -> impl UiNode {
319    opacity_impl(child, alpha, false)
320}
321
322/// Opacity/transparency of the widget's child.
323///
324/// This property provides the same visual result as setting [`child_filter`] to [`Filter::new_opacity`],
325/// **but** updating the opacity is faster in this property.
326///
327/// [`child_filter`]: fn@child_filter
328/// [`Filter::new_opacity`]: zng_color::filter::Filter::new_opacity
329#[property(CHILD_CONTEXT, default(1.0))]
330pub fn child_opacity(child: impl UiNode, alpha: impl IntoVar<Factor>) -> impl UiNode {
331    opacity_impl(child, alpha, true)
332}
333
334/// impl any filter, may need layout or not.
335fn filter_any(child: impl UiNode, filter: impl IntoVar<Filter>, target_child: bool) -> impl UiNode {
336    let filter = filter.into_var();
337    let mut render_filter = None;
338    match_node(child, move |child, op| match op {
339        UiNodeOp::Init => {
340            WIDGET.sub_var(&filter);
341            render_filter = filter.with(Filter::try_render);
342        }
343        UiNodeOp::Update { .. } => {
344            filter.with_new(|f| {
345                if let Some(f) = f.try_render() {
346                    render_filter = Some(f);
347                    WIDGET.render();
348                } else {
349                    render_filter = None;
350                    WIDGET.layout();
351                }
352            });
353        }
354        UiNodeOp::Layout { .. } => {
355            filter.with(|f| {
356                if f.needs_layout() {
357                    let f = Some(f.layout());
358                    if render_filter != f {
359                        render_filter = f;
360                        WIDGET.render();
361                    }
362                }
363            });
364        }
365        UiNodeOp::Render { frame } => {
366            if target_child {
367                frame.push_filter(MixBlendMode::Normal.into(), render_filter.as_ref().unwrap(), |frame| {
368                    child.render(frame)
369                });
370            } else {
371                frame.push_inner_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
372            }
373        }
374        _ => {}
375    })
376}
377
378/// impl any backdrop filter, may need layout or not.
379fn backdrop_filter_any(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
380    let filter = filter.into_var();
381    let mut render_filter = None;
382    match_node(child, move |child, op| match op {
383        UiNodeOp::Init => {
384            WIDGET.sub_var(&filter);
385            render_filter = filter.with(Filter::try_render);
386        }
387        UiNodeOp::Update { .. } => {
388            filter.with_new(|f| {
389                if let Some(f) = f.try_render() {
390                    render_filter = Some(f);
391                    WIDGET.render();
392                } else {
393                    render_filter = None;
394                    WIDGET.layout();
395                }
396            });
397        }
398        UiNodeOp::Layout { .. } => {
399            filter.with(|f| {
400                if f.needs_layout() {
401                    let f = Some(f.layout());
402                    if render_filter != f {
403                        render_filter = f;
404                        WIDGET.render();
405                    }
406                }
407            });
408        }
409        UiNodeOp::Render { frame } => {
410            frame.push_inner_backdrop_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
411        }
412        _ => {}
413    })
414}
415
416/// impl filters that need layout.
417fn filter_layout(child: impl UiNode, filter: impl IntoVar<Filter>, target_child: bool) -> impl UiNode {
418    let filter = filter.into_var();
419
420    let mut render_filter = None;
421    match_node(child, move |child, op| match op {
422        UiNodeOp::Init => {
423            WIDGET.sub_var_layout(&filter);
424        }
425        UiNodeOp::Layout { .. } => {
426            filter.with(|f| {
427                if f.needs_layout() {
428                    let f = Some(f.layout());
429                    if render_filter != f {
430                        render_filter = f;
431                        WIDGET.render();
432                    }
433                }
434            });
435        }
436        UiNodeOp::Render { frame } => {
437            if target_child {
438                frame.push_filter(MixBlendMode::Normal.into(), render_filter.as_ref().unwrap(), |frame| {
439                    child.render(frame)
440                });
441            } else {
442                frame.push_inner_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
443            }
444        }
445        _ => {}
446    })
447}
448
449/// impl backdrop filters that need layout.
450fn backdrop_filter_layout(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
451    let filter = filter.into_var();
452
453    let mut render_filter = None;
454    match_node(child, move |child, op| match op {
455        UiNodeOp::Init => {
456            WIDGET.sub_var_layout(&filter);
457        }
458        UiNodeOp::Layout { .. } => {
459            filter.with(|f| {
460                if f.needs_layout() {
461                    let f = Some(f.layout());
462                    if render_filter != f {
463                        render_filter = f;
464                        WIDGET.render();
465                    }
466                }
467            });
468        }
469        UiNodeOp::Render { frame } => {
470            frame.push_inner_backdrop_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
471        }
472        _ => {}
473    })
474}
475
476/// impl filters that only need render.
477fn filter_render(child: impl UiNode, filter: impl IntoVar<Filter>, target_child: bool) -> impl UiNode {
478    let filter = filter.into_var().map(|f| f.try_render().unwrap());
479    match_node(child, move |child, op| match op {
480        UiNodeOp::Init => {
481            WIDGET.sub_var_render(&filter);
482        }
483        UiNodeOp::Render { frame } => {
484            if target_child {
485                filter.with(|f| {
486                    frame.push_filter(MixBlendMode::Normal.into(), f, |frame| child.render(frame));
487                });
488            } else {
489                frame.push_inner_filter(filter.get(), |frame| child.render(frame));
490            }
491        }
492        _ => {}
493    })
494}
495
496/// impl backdrop filter that only need render.
497fn backdrop_filter_render(child: impl UiNode, filter: impl IntoVar<Filter>) -> impl UiNode {
498    let filter = filter.into_var().map(|f| f.try_render().unwrap());
499    match_node(child, move |child, op| match op {
500        UiNodeOp::Init => {
501            WIDGET.sub_var_render(&filter);
502        }
503        UiNodeOp::Render { frame } => {
504            frame.push_inner_backdrop_filter(filter.get(), |frame| child.render(frame));
505        }
506        _ => {}
507    })
508}
509
510fn opacity_impl(child: impl UiNode, alpha: impl IntoVar<Factor>, target_child: bool) -> impl UiNode {
511    let frame_key = FrameValueKey::new_unique();
512    let alpha = alpha.into_var();
513
514    match_node(child, move |child, op| match op {
515        UiNodeOp::Init => {
516            WIDGET.sub_var_render_update(&alpha);
517        }
518        UiNodeOp::Render { frame } => {
519            let opacity = frame_key.bind_var(&alpha, |f| f.0);
520            if target_child {
521                frame.push_opacity(opacity, |frame| child.render(frame));
522            } else {
523                frame.push_inner_opacity(opacity, |frame| child.render(frame));
524            }
525        }
526        UiNodeOp::RenderUpdate { update } => {
527            update.update_f32_opt(frame_key.update_var(&alpha, |f| f.0));
528            child.render_update(update);
529        }
530        _ => {}
531    })
532}
533
534/// Sets how the widget blends with the parent widget.
535#[property(CONTEXT, default(MixBlendMode::default()))]
536pub fn mix_blend(child: impl UiNode, mode: impl IntoVar<MixBlendMode>) -> impl UiNode {
537    let mode = mode.into_var();
538    match_node(child, move |c, op| match op {
539        UiNodeOp::Init => {
540            WIDGET.sub_var_render(&mode);
541        }
542        UiNodeOp::Render { frame } => {
543            frame.push_inner_blend(mode.get().into(), |frame| c.render(frame));
544        }
545        _ => {}
546    })
547}
548
549/// Sets how the widget's child content blends with the widget.
550#[property(CHILD_CONTEXT, default(MixBlendMode::default()))]
551pub fn child_mix_blend(child: impl UiNode, mode: impl IntoVar<MixBlendMode>) -> impl UiNode {
552    let mode = mode.into_var();
553    match_node(child, move |c, op| match op {
554        UiNodeOp::Init => {
555            WIDGET.sub_var_render(&mode);
556        }
557        UiNodeOp::Render { frame } => {
558            frame.push_filter(mode.get().into(), &vec![], |frame| c.render(frame));
559        }
560        _ => {}
561    })
562}