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#![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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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
334fn 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
378fn 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
416fn 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
449fn 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
476fn 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
496fn 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#[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#[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}