Skip to main content

fret_ui_kit/declarative/
bloom.rs

1use fret_core::geometry::{Corners, Edges};
2use fret_core::scene::{BlendMode, EffectMode, EffectQuality};
3use fret_ui::element::{
4    AnyElement, CompositeGroupProps, ContainerProps, EffectLayerProps, FocusTraversalGateProps,
5    HitTestGateProps, InsetStyle, LayoutStyle, Length, Overflow, PositionStyle, SizeStyle,
6};
7use fret_ui::{ElementContext, Theme, UiHost};
8
9use crate::recipes::bloom::{BloomEffect, bloom_effect_chain};
10use crate::recipes::surface::{SurfaceTokenKeys, resolve_surface_chrome};
11use crate::{ChromeRefinement, IntoUiElement, collect_children};
12
13#[derive(Debug, Clone)]
14pub struct BloomPanelProps {
15    pub layout: LayoutStyle,
16    pub mode: EffectMode,
17    pub quality: EffectQuality,
18    pub chrome: ChromeRefinement,
19    pub chrome_keys: SurfaceTokenKeys,
20    pub effect: BloomEffect,
21}
22
23impl Default for BloomPanelProps {
24    fn default() -> Self {
25        let mut layout = LayoutStyle::default();
26        layout.size.width = Length::Fill;
27        layout.size.height = Length::Fill;
28        Self {
29            layout,
30            mode: EffectMode::FilterContent,
31            quality: EffectQuality::Auto,
32            chrome: ChromeRefinement::default(),
33            chrome_keys: SurfaceTokenKeys {
34                padding_x: None,
35                padding_y: None,
36                radius: None,
37                border_width: None,
38                bg: None,
39                border: None,
40            },
41            effect: BloomEffect::default(),
42        }
43    }
44}
45
46pub fn bloom_panel<H: UiHost, I, T>(
47    cx: &mut ElementContext<'_, H>,
48    props: BloomPanelProps,
49    mut children: impl FnMut(&mut ElementContext<'_, H>) -> I,
50) -> AnyElement
51where
52    I: IntoIterator<Item = T>,
53    T: IntoUiElement<H>,
54{
55    let theme = Theme::global(&*cx.app);
56    let chrome = resolve_surface_chrome(theme, &props.chrome, props.chrome_keys);
57
58    let chain = bloom_effect_chain(props.effect);
59    chain.report_if_degraded(&mut *cx.app);
60
61    let outer = ContainerProps {
62        layout: LayoutStyle {
63            overflow: Overflow::Visible,
64            ..props.layout
65        },
66        corner_radii: Corners::all(chrome.radius),
67        ..Default::default()
68    };
69
70    cx.container(outer, move |cx| {
71        let mut inner_layout = LayoutStyle::default();
72        inner_layout.size.width = Length::Fill;
73        inner_layout.size.height = Length::Fill;
74
75        let inner = ContainerProps {
76            layout: inner_layout,
77            padding: Edges::symmetric(chrome.padding_x, chrome.padding_y).into(),
78            background: Some(chrome.background),
79            border: Edges::all(chrome.border_width),
80            border_color: Some(chrome.border_color),
81            corner_radii: Corners::all(chrome.radius),
82            ..Default::default()
83        };
84
85        let base_items = children(cx);
86        let base_children = collect_children(cx, base_items);
87        let glow_items = children(cx);
88        let glow_children = collect_children(cx, glow_items);
89
90        let base = cx.container(inner, move |_cx| base_children);
91
92        let overlay_layout = LayoutStyle {
93            position: PositionStyle::Absolute,
94            inset: InsetStyle {
95                top: Some(fret_core::Px(0.0)).into(),
96                right: Some(fret_core::Px(0.0)).into(),
97                bottom: Some(fret_core::Px(0.0)).into(),
98                left: Some(fret_core::Px(0.0)).into(),
99            },
100            size: SizeStyle {
101                width: Length::Fill,
102                height: Length::Fill,
103                ..Default::default()
104            },
105            ..Default::default()
106        };
107
108        let overlay = cx.focus_traversal_gate_props(
109            FocusTraversalGateProps {
110                layout: overlay_layout,
111                traverse: false,
112            },
113            move |cx| {
114                let mut hit_layout = LayoutStyle::default();
115                hit_layout.size.width = Length::Fill;
116                hit_layout.size.height = Length::Fill;
117
118                [cx.hit_test_gate_props(
119                    HitTestGateProps {
120                        layout: hit_layout,
121                        hit_test: false,
122                    },
123                    move |cx| {
124                        let mut fill = LayoutStyle::default();
125                        fill.size.width = Length::Fill;
126                        fill.size.height = Length::Fill;
127
128                        let group = cx.composite_group_props(
129                            CompositeGroupProps {
130                                layout: fill,
131                                mode: BlendMode::Add,
132                                quality: props.quality,
133                            },
134                            move |cx| {
135                                let mut effect_layout = LayoutStyle::default();
136                                effect_layout.size.width = Length::Fill;
137                                effect_layout.size.height = Length::Fill;
138
139                                vec![cx.effect_layer_props(
140                                    EffectLayerProps {
141                                        layout: effect_layout,
142                                        mode: props.mode,
143                                        chain: chain.value,
144                                        quality: props.quality,
145                                    },
146                                    move |_cx| glow_children,
147                                )]
148                            },
149                        );
150
151                        vec![group]
152                    },
153                )]
154            },
155        );
156
157        vec![base, overlay]
158    })
159}