zng_wgt_filter/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/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 IntoUiNode, filter: impl IntoVar<Filter>) -> 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 IntoUiNode, filter: impl IntoVar<Filter>) -> 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 IntoUiNode, filter: impl IntoVar<Filter>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, radius: impl IntoVar<Length>) -> 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 IntoUiNode, radius: impl IntoVar<Length>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode,
175    offset: impl IntoVar<Point>,
176    blur_radius: impl IntoVar<Length>,
177    color: impl IntoVar<Rgba>,
178) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, amount: impl IntoVar<Factor>) -> 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 IntoUiNode, angle: impl IntoVar<AngleDegree>) -> 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 IntoUiNode, angle: impl IntoVar<AngleDegree>) -> 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 IntoUiNode, matrix: impl IntoVar<ColorMatrix>) -> 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 IntoUiNode, matrix: impl IntoVar<ColorMatrix>) -> 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 IntoUiNode, alpha: impl IntoVar<Factor>) -> 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 IntoUiNode, alpha: impl IntoVar<Factor>) -> UiNode {
331    opacity_impl(child, alpha, true)
332}
333
334/// impl any filter, may need layout or not.
335fn filter_any(child: impl IntoUiNode, filter: impl IntoVar<Filter>, target_child: bool) -> 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, render_filter.as_ref().unwrap(), |frame| child.render(frame));
368            } else {
369                frame.push_inner_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
370            }
371        }
372        _ => {}
373    })
374}
375
376/// impl any backdrop filter, may need layout or not.
377fn backdrop_filter_any(child: impl IntoUiNode, filter: impl IntoVar<Filter>) -> UiNode {
378    let filter = filter.into_var();
379    let mut render_filter = None;
380    match_node(child, move |child, op| match op {
381        UiNodeOp::Init => {
382            WIDGET.sub_var(&filter);
383            render_filter = filter.with(Filter::try_render);
384        }
385        UiNodeOp::Update { .. } => {
386            filter.with_new(|f| {
387                if let Some(f) = f.try_render() {
388                    render_filter = Some(f);
389                    WIDGET.render();
390                } else {
391                    render_filter = None;
392                    WIDGET.layout();
393                }
394            });
395        }
396        UiNodeOp::Layout { .. } => {
397            filter.with(|f| {
398                if f.needs_layout() {
399                    let f = Some(f.layout());
400                    if render_filter != f {
401                        render_filter = f;
402                        WIDGET.render();
403                    }
404                }
405            });
406        }
407        UiNodeOp::Render { frame } => {
408            frame.push_inner_backdrop_filter(render_filter.clone().unwrap(), |frame| child.render(frame));
409        }
410        _ => {}
411    })
412}
413
414/// impl filters that need layout.
415fn filter_layout(child: impl IntoUiNode, filter: impl IntoVar<Filter>, target_child: bool) -> UiNode {
416    let filter = filter.into_var();
417
418    let mut render_filter = None;
419    match_node(child, move |child, op| match op {
420        UiNodeOp::Init => {
421            WIDGET.sub_var_layout(&filter);
422        }
423        UiNodeOp::Layout { .. } => {
424            filter.with(|f| {
425                debug_assert!(f.needs_layout());
426                let f = Some(f.layout());
427                if render_filter != f {
428                    render_filter = f;
429                    WIDGET.render();
430                }
431            });
432        }
433        UiNodeOp::Render { frame } => {
434            if let Some(filter) = &render_filter {
435                if target_child {
436                    frame.push_filter(MixBlendMode::Normal, filter, |frame| child.render(frame));
437                } else {
438                    frame.push_inner_filter(filter.clone(), |frame| child.render(frame));
439                }
440            } else {
441                tracing::error!("filter_layout render called before any layout");
442            }
443        }
444        _ => {}
445    })
446}
447
448/// impl backdrop filters that need layout.
449fn backdrop_filter_layout(child: impl IntoUiNode, filter: impl IntoVar<Filter>) -> UiNode {
450    let filter = filter.into_var();
451
452    let mut render_filter = None;
453    match_node(child, move |child, op| match op {
454        UiNodeOp::Init => {
455            WIDGET.sub_var_layout(&filter);
456        }
457        UiNodeOp::Layout { .. } => {
458            filter.with(|f| {
459                debug_assert!(f.needs_layout());
460
461                let f = Some(f.layout());
462                if render_filter != f {
463                    render_filter = f;
464                    WIDGET.render();
465                }
466            });
467        }
468        UiNodeOp::Render { frame } => {
469            if let Some(filter) = render_filter.clone() {
470                frame.push_inner_backdrop_filter(filter, |frame| child.render(frame));
471            } else {
472                tracing::error!("filter_layout render called before any layout");
473            }
474        }
475        _ => {}
476    })
477}
478
479/// impl filters that only need render.
480fn filter_render(child: impl IntoUiNode, filter: impl IntoVar<Filter>, target_child: bool) -> UiNode {
481    let filter = filter.into_var().map(|f| f.try_render().unwrap());
482    match_node(child, move |child, op| match op {
483        UiNodeOp::Init => {
484            WIDGET.sub_var_render(&filter);
485        }
486        UiNodeOp::Render { frame } => {
487            if target_child {
488                filter.with(|f| {
489                    frame.push_filter(MixBlendMode::Normal, f, |frame| child.render(frame));
490                });
491            } else {
492                frame.push_inner_filter(filter.get(), |frame| child.render(frame));
493            }
494        }
495        _ => {}
496    })
497}
498
499/// impl backdrop filter that only need render.
500fn backdrop_filter_render(child: impl IntoUiNode, filter: impl IntoVar<Filter>) -> UiNode {
501    let filter = filter.into_var().map(|f| f.try_render().unwrap());
502    match_node(child, move |child, op| match op {
503        UiNodeOp::Init => {
504            WIDGET.sub_var_render(&filter);
505        }
506        UiNodeOp::Render { frame } => {
507            frame.push_inner_backdrop_filter(filter.get(), |frame| child.render(frame));
508        }
509        _ => {}
510    })
511}
512
513fn opacity_impl(child: impl IntoUiNode, alpha: impl IntoVar<Factor>, target_child: bool) -> UiNode {
514    let frame_key = FrameValueKey::new_unique();
515    let alpha = alpha.into_var();
516
517    match_node(child, move |child, op| match op {
518        UiNodeOp::Init => {
519            WIDGET.sub_var_render_update(&alpha);
520        }
521        UiNodeOp::Render { frame } => {
522            let opacity = frame_key.bind_var(&alpha, |f| f.0);
523            if target_child {
524                frame.push_opacity(opacity, |frame| child.render(frame));
525            } else {
526                frame.push_inner_opacity(opacity, |frame| child.render(frame));
527            }
528        }
529        UiNodeOp::RenderUpdate { update } => {
530            update.update_f32_opt(frame_key.update_var(&alpha, |f| f.0));
531            child.render_update(update);
532        }
533        _ => {}
534    })
535}
536
537/// Sets how the widget blends with the parent widget.
538#[property(CONTEXT, default(MixBlendMode::default()))]
539pub fn mix_blend(child: impl IntoUiNode, mode: impl IntoVar<MixBlendMode>) -> UiNode {
540    let mode = mode.into_var();
541    match_node(child, move |c, op| match op {
542        UiNodeOp::Init => {
543            WIDGET.sub_var_render(&mode);
544        }
545        UiNodeOp::Render { frame } => {
546            frame.push_inner_blend(mode.get(), |frame| c.render(frame));
547        }
548        _ => {}
549    })
550}
551
552/// Sets how the widget's child content blends with the widget.
553#[property(CHILD_CONTEXT, default(MixBlendMode::default()))]
554pub fn child_mix_blend(child: impl IntoUiNode, mode: impl IntoVar<MixBlendMode>) -> UiNode {
555    let mode = mode.into_var();
556    match_node(child, move |c, op| match op {
557        UiNodeOp::Init => {
558            WIDGET.sub_var_render(&mode);
559        }
560        UiNodeOp::Render { frame } => {
561            frame.push_filter(mode.get(), &vec![], |frame| c.render(frame));
562        }
563        _ => {}
564    })
565}