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#![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 IntoUiNode, filter: impl IntoVar<Filter>) -> UiNode {
28 filter_any(child, filter, false)
29}
30
31#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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
334fn 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
376fn 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
414fn 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
448fn 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
479fn 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
499fn 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#[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#[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}