Skip to main content

fret_ui_kit/recipes/
pixelate.rs

1use fret_core::{EffectChain, EffectStep, Px};
2use fret_ui::Theme;
3
4use crate::ChromeRefinement;
5use crate::recipes::effect_recipe::clamp_u32_from_metric;
6use crate::style::{ColorFallback, ColorRef, MetricFallback, MetricRef};
7
8#[derive(Debug, Clone, Copy)]
9pub struct PixelateTokenKeys {
10    pub padding_x: Option<&'static str>,
11    pub padding_y: Option<&'static str>,
12    pub radius: Option<&'static str>,
13    pub border_width: Option<&'static str>,
14    pub bg: Option<&'static str>,
15    pub border: Option<&'static str>,
16}
17
18impl PixelateTokenKeys {
19    pub const fn none() -> Self {
20        Self {
21            padding_x: None,
22            padding_y: None,
23            radius: None,
24            border_width: None,
25            bg: None,
26            border: None,
27        }
28    }
29}
30
31#[derive(Debug, Clone, Copy)]
32pub struct ResolvedPixelateChrome {
33    pub padding_x: Px,
34    pub padding_y: Px,
35    pub radius: Px,
36    pub border_width: Px,
37    pub background: fret_core::Color,
38    pub border_color: fret_core::Color,
39}
40
41pub fn resolve_pixelate_chrome(
42    theme: &Theme,
43    style: &ChromeRefinement,
44    keys: PixelateTokenKeys,
45) -> ResolvedPixelateChrome {
46    let default_padding_x = MetricRef::Token {
47        key: "component.pixelate.padding_x",
48        fallback: MetricFallback::ThemePaddingSm,
49    };
50    let default_padding_y = MetricRef::Token {
51        key: "component.pixelate.padding_y",
52        fallback: MetricFallback::ThemePaddingSm,
53    };
54    let default_radius = MetricRef::Token {
55        key: "component.pixelate.radius",
56        fallback: MetricFallback::ThemeRadiusSm,
57    };
58
59    let padding_x = style
60        .padding
61        .as_ref()
62        .and_then(|p| p.left.as_ref().or(p.right.as_ref()))
63        .map(|m| m.resolve(theme))
64        .or_else(|| keys.padding_x.and_then(|k| theme.metric_by_key(k)))
65        .or_else(|| theme.metric_by_key("component.pixelate.padding_x"))
66        .unwrap_or_else(|| default_padding_x.resolve(theme));
67    let padding_y = style
68        .padding
69        .as_ref()
70        .and_then(|p| p.top.as_ref().or(p.bottom.as_ref()))
71        .map(|m| m.resolve(theme))
72        .or_else(|| keys.padding_y.and_then(|k| theme.metric_by_key(k)))
73        .or_else(|| theme.metric_by_key("component.pixelate.padding_y"))
74        .unwrap_or_else(|| default_padding_y.resolve(theme));
75    let radius = style
76        .radius
77        .as_ref()
78        .map(|m| m.resolve(theme))
79        .or_else(|| keys.radius.and_then(|k| theme.metric_by_key(k)))
80        .or_else(|| theme.metric_by_key("component.pixelate.radius"))
81        .unwrap_or_else(|| default_radius.resolve(theme));
82    let border_width = style
83        .border_width
84        .as_ref()
85        .map(|m| m.resolve(theme))
86        .or_else(|| keys.border_width.and_then(|k| theme.metric_by_key(k)))
87        .or_else(|| theme.metric_by_key("component.pixelate.border_width"))
88        .unwrap_or(Px(1.0));
89
90    let default_bg = ColorRef::Token {
91        key: "component.pixelate.bg",
92        fallback: ColorFallback::ThemePanelBackground,
93    };
94    let default_border = ColorRef::Token {
95        key: "component.pixelate.border",
96        fallback: ColorFallback::ThemePanelBorder,
97    };
98
99    let background = style
100        .background
101        .as_ref()
102        .map(|c| c.resolve(theme))
103        .or_else(|| keys.bg.and_then(|k| theme.color_by_key(k)))
104        .or_else(|| theme.color_by_key("component.pixelate.bg"))
105        .unwrap_or_else(|| default_bg.resolve(theme));
106    let border_color = style
107        .border_color
108        .as_ref()
109        .map(|c| c.resolve(theme))
110        .or_else(|| keys.border.and_then(|k| theme.color_by_key(k)))
111        .or_else(|| theme.color_by_key("component.pixelate.border"))
112        .unwrap_or_else(|| default_border.resolve(theme));
113
114    ResolvedPixelateChrome {
115        padding_x: Px(padding_x.0.max(0.0)),
116        padding_y: Px(padding_y.0.max(0.0)),
117        radius: Px(radius.0.max(0.0)),
118        border_width: Px(border_width.0.max(0.0)),
119        background,
120        border_color,
121    }
122}
123
124#[derive(Debug, Clone, Default)]
125pub struct PixelateEffectRefinement {
126    pub scale: Option<u32>,
127}
128
129#[derive(Debug, Clone, Copy)]
130pub struct PixelateEffectTokenKeys {
131    pub scale: Option<&'static str>,
132}
133
134impl PixelateEffectTokenKeys {
135    pub const fn none() -> Self {
136        Self { scale: None }
137    }
138}
139
140#[derive(Debug, Clone, Copy)]
141pub struct ResolvedPixelateEffect {
142    pub scale: u32,
143}
144
145pub fn resolve_pixelate_effect(
146    theme: &Theme,
147    refinement: &PixelateEffectRefinement,
148    keys: PixelateEffectTokenKeys,
149) -> ResolvedPixelateEffect {
150    let scale = refinement.scale.unwrap_or_else(|| {
151        let metric = keys
152            .scale
153            .and_then(|k| theme.metric_by_key(k))
154            .or_else(|| theme.metric_by_key("component.pixelate.scale"))
155            .unwrap_or(Px(8.0));
156        clamp_u32_from_metric(metric, 1, 64, 8)
157    });
158
159    ResolvedPixelateEffect {
160        scale: scale.clamp(1, 256),
161    }
162}
163
164pub fn pixelate_effect_chain(effect: ResolvedPixelateEffect) -> EffectChain {
165    EffectChain::from_steps(&[EffectStep::Pixelate {
166        scale: effect.scale,
167    }])
168}