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}