Skip to main content

fret_ui_kit/declarative/
glass.rs

1use fret_core::geometry::{Corners, Edges};
2use fret_core::scene::{EffectMode, EffectQuality};
3use fret_ui::element::{
4    AnyElement, ContainerProps, EffectLayerProps, LayoutStyle, Length, Overflow,
5};
6use fret_ui::{ElementContext, Invalidation, Theme, UiHost};
7
8use crate::declarative::reduced_transparency_queries;
9use crate::recipes::glass::{
10    GlassEffectRefinement, GlassEffectTokenKeys, GlassTokenKeys, resolve_glass_chrome,
11    resolve_glass_effect, resolve_glass_effect_chain_for_environment,
12};
13use crate::{ChromeRefinement, IntoUiElement, collect_children};
14
15#[derive(Debug, Clone)]
16pub struct GlassPanelProps {
17    pub layout: LayoutStyle,
18    pub mode: EffectMode,
19    pub quality: EffectQuality,
20    pub chrome: ChromeRefinement,
21    pub chrome_keys: GlassTokenKeys,
22    pub effect: GlassEffectRefinement,
23    pub effect_keys: GlassEffectTokenKeys,
24}
25
26impl Default for GlassPanelProps {
27    fn default() -> Self {
28        let mut layout = LayoutStyle::default();
29        layout.size.width = Length::Fill;
30        layout.size.height = Length::Fill;
31        Self {
32            layout,
33            mode: EffectMode::Backdrop,
34            quality: EffectQuality::Auto,
35            chrome: ChromeRefinement::default(),
36            chrome_keys: GlassTokenKeys::none(),
37            effect: GlassEffectRefinement::default(),
38            effect_keys: GlassEffectTokenKeys::none(),
39        }
40    }
41}
42
43pub fn glass_panel<H: UiHost, I, T>(
44    cx: &mut ElementContext<'_, H>,
45    props: GlassPanelProps,
46    children: impl FnOnce(&mut ElementContext<'_, H>) -> I,
47) -> AnyElement
48where
49    I: IntoIterator<Item = T>,
50    T: IntoUiElement<H>,
51{
52    let prefers_reduced_transparency =
53        reduced_transparency_queries::prefers_reduced_transparency(cx, Invalidation::Paint, false);
54
55    let theme = Theme::global(&*cx.app);
56    let chrome = resolve_glass_chrome(theme, &props.chrome, props.chrome_keys);
57    let effect = resolve_glass_effect(theme, &props.effect, props.effect_keys);
58    let chain = resolve_glass_effect_chain_for_environment(effect, prefers_reduced_transparency);
59    chain.report_if_degraded(&mut *cx.app);
60
61    // Structure:
62    //
63    // - Outer wrapper provides the rounded clip that the renderer can consume at the effect
64    //   boundary (ADR 0117 + ADR 0138).
65    // - Effect layer wraps a tinted/bordered container (tint + border are drawn after the blurred
66    //   backdrop inside the group).
67    let outer = ContainerProps {
68        layout: LayoutStyle {
69            overflow: Overflow::Clip,
70            ..props.layout
71        },
72        corner_radii: Corners::all(chrome.radius),
73        ..Default::default()
74    };
75
76    cx.container(outer, move |cx| {
77        let mut effect_layout = LayoutStyle::default();
78        effect_layout.size.width = Length::Fill;
79        effect_layout.size.height = Length::Fill;
80
81        let layer = cx.effect_layer_props(
82            EffectLayerProps {
83                layout: effect_layout,
84                mode: props.mode,
85                chain: chain.value,
86                quality: props.quality,
87            },
88            move |cx| {
89                let mut inner_layout = LayoutStyle::default();
90                inner_layout.size.width = Length::Fill;
91                inner_layout.size.height = Length::Fill;
92                let inner = ContainerProps {
93                    layout: inner_layout,
94                    padding: Edges::symmetric(chrome.padding_x, chrome.padding_y).into(),
95                    background: Some(chrome.tint),
96                    border: Edges::all(chrome.border_width),
97                    border_color: Some(chrome.border),
98                    corner_radii: Corners::all(chrome.radius),
99                    ..Default::default()
100                };
101
102                let items = children(cx);
103                let inner_children = collect_children(cx, items);
104
105                vec![cx.container(inner, move |_cx| inner_children)]
106            },
107        );
108
109        vec![layer]
110    })
111}