oxygengine_user_interface/
ui_theme_asset_protocol.rs

1use core::{
2    assets::protocol::{AssetLoadResult, AssetProtocol},
3    prefab::Prefab,
4};
5use raui_core::{
6    widget::{
7        unit::text::{TextBoxDirection, TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},
8        utils::Color,
9    },
10    Scalar,
11};
12use raui_material::theme::*;
13use serde::{Deserialize, Serialize};
14use std::{collections::HashMap, str::from_utf8};
15
16#[derive(Debug, Serialize, Deserialize)]
17pub enum UiTheme {
18    AllWhite(#[serde(default)] ThemePropsExtras),
19    Flat {
20        #[serde(default)]
21        default: Color,
22        #[serde(default)]
23        primary: Color,
24        #[serde(default)]
25        secondary: Color,
26        #[serde(default)]
27        background: Color,
28        #[serde(default)]
29        extras: ThemePropsExtras,
30    },
31    FlatLight(#[serde(default)] ThemePropsExtras),
32    FlatDark(#[serde(default)] ThemePropsExtras),
33    Custom(#[serde(default)] ThemeProps),
34}
35
36impl Default for UiTheme {
37    fn default() -> Self {
38        Self::AllWhite(Default::default())
39    }
40}
41
42impl UiTheme {
43    pub fn props(&self) -> ThemeProps {
44        match self {
45            Self::AllWhite(extras) => Self::merge(new_all_white_theme(), extras),
46            Self::Flat {
47                default,
48                primary,
49                secondary,
50                background,
51                extras,
52            } => Self::merge(
53                make_default_theme(*default, *primary, *secondary, *background),
54                extras,
55            ),
56            Self::FlatLight(extras) => Self::merge(new_light_theme(), extras),
57            Self::FlatDark(extras) => Self::merge(new_dark_theme(), extras),
58            Self::Custom(props) => props.clone(),
59        }
60    }
61
62    pub fn merge(mut props: ThemeProps, extras: &ThemePropsExtras) -> ThemeProps {
63        extras.active_colors.merge_to(&mut props.active_colors);
64        extras
65            .background_colors
66            .merge_to(&mut props.background_colors);
67        props
68            .content_backgrounds
69            .extend(extras.content_backgrounds.clone());
70        for (k, v) in &extras.button_backgrounds {
71            v.merge_to(props.button_backgrounds.entry(k.to_owned()).or_default());
72        }
73        props
74            .icons_level_sizes
75            .extend(extras.icons_level_sizes.clone());
76        for (k, v) in &extras.text_variants {
77            v.merge_to(props.text_variants.entry(k.to_owned()).or_default());
78        }
79        for (k, v) in &extras.text_families {
80            make_text_family(k, v, &mut props.text_variants);
81        }
82        for (k, v) in &extras.switch_variants {
83            v.merge_to(props.switch_variants.entry(k.to_owned()).or_default());
84        }
85        props
86            .modal_shadow_variants
87            .extend(extras.modal_shadow_variants.clone());
88        props
89    }
90}
91
92impl Prefab for UiTheme {}
93
94#[derive(Debug, Default, Clone, Serialize, Deserialize)]
95pub struct ThemeColorSetExtras {
96    #[serde(default)]
97    pub main: Option<Color>,
98    #[serde(default)]
99    pub light: Option<Color>,
100    #[serde(default)]
101    pub dark: Option<Color>,
102}
103
104impl ThemeColorSetExtras {
105    pub fn merge_to(&self, other: &mut ThemeColorSet) {
106        if let Some(v) = self.main {
107            other.main = v;
108        }
109        if let Some(v) = self.light {
110            other.light = v;
111        }
112        if let Some(v) = self.dark {
113            other.dark = v;
114        }
115    }
116}
117
118#[derive(Debug, Default, Clone, Serialize, Deserialize)]
119pub struct ThemeColorsExtras {
120    #[serde(default)]
121    pub default: ThemeColorSetExtras,
122    #[serde(default)]
123    pub primary: ThemeColorSetExtras,
124    #[serde(default)]
125    pub secondary: ThemeColorSetExtras,
126}
127
128impl ThemeColorsExtras {
129    pub fn merge_to(&self, other: &mut ThemeColors) {
130        self.default.merge_to(&mut other.default);
131        self.primary.merge_to(&mut other.primary);
132        self.secondary.merge_to(&mut other.secondary);
133    }
134}
135
136#[derive(Debug, Default, Clone, Serialize, Deserialize)]
137pub struct ThemeColorsBundleExtras {
138    #[serde(default)]
139    pub main: ThemeColorsExtras,
140    #[serde(default)]
141    pub contrast: ThemeColorsExtras,
142}
143
144impl ThemeColorsBundleExtras {
145    pub fn merge_to(&self, other: &mut ThemeColorsBundle) {
146        self.main.merge_to(&mut other.main);
147        self.contrast.merge_to(&mut other.contrast);
148    }
149}
150
151#[derive(Debug, Default, Clone, Serialize, Deserialize)]
152pub struct ThemedTextMaterialExtras {
153    #[serde(default)]
154    pub horizontal_align: Option<TextBoxHorizontalAlign>,
155    #[serde(default)]
156    pub vertical_align: Option<TextBoxVerticalAlign>,
157    #[serde(default)]
158    pub direction: Option<TextBoxDirection>,
159    #[serde(default)]
160    pub font: Option<TextBoxFont>,
161}
162
163impl ThemedTextMaterialExtras {
164    pub fn merge_to(&self, other: &mut ThemedTextMaterial) {
165        if let Some(v) = self.horizontal_align {
166            other.horizontal_align = v;
167        }
168        if let Some(v) = self.vertical_align {
169            other.vertical_align = v;
170        }
171        if let Some(v) = self.direction {
172            other.direction = v;
173        }
174        if let Some(v) = &self.font {
175            other.font = v.clone();
176        }
177    }
178}
179
180#[derive(Debug, Default, Clone, Serialize, Deserialize)]
181pub struct ThemedButtonMaterialExtras {
182    #[serde(default)]
183    pub default: Option<ThemedImageMaterial>,
184    #[serde(default)]
185    pub selected: Option<ThemedImageMaterial>,
186    #[serde(default)]
187    pub trigger: Option<ThemedImageMaterial>,
188}
189
190impl ThemedButtonMaterialExtras {
191    pub fn merge_to(&self, other: &mut ThemedButtonMaterial) {
192        if let Some(v) = &self.default {
193            other.default = v.clone();
194        }
195        if let Some(v) = &self.selected {
196            other.selected = v.clone();
197        }
198        if let Some(v) = &self.trigger {
199            other.trigger = v.clone();
200        }
201    }
202}
203
204#[derive(Debug, Default, Clone, Serialize, Deserialize)]
205pub struct ThemedSwitchMaterialExtras {
206    #[serde(default)]
207    pub on: Option<ThemedImageMaterial>,
208    #[serde(default)]
209    pub off: Option<ThemedImageMaterial>,
210}
211
212impl ThemedSwitchMaterialExtras {
213    pub fn merge_to(&self, other: &mut ThemedSwitchMaterial) {
214        if let Some(v) = &self.on {
215            other.on = v.clone();
216        }
217        if let Some(v) = &self.off {
218            other.off = v.clone();
219        }
220    }
221}
222
223#[derive(Debug, Default, Clone, Serialize, Deserialize)]
224pub struct ThemePropsExtras {
225    #[serde(default)]
226    pub active_colors: ThemeColorsBundleExtras,
227    #[serde(default)]
228    pub background_colors: ThemeColorsBundleExtras,
229    #[serde(default)]
230    pub content_backgrounds: HashMap<String, ThemedImageMaterial>,
231    #[serde(default)]
232    pub button_backgrounds: HashMap<String, ThemedButtonMaterialExtras>,
233    #[serde(default)]
234    pub icons_level_sizes: Vec<Scalar>,
235    #[serde(default)]
236    pub text_variants: HashMap<String, ThemedTextMaterialExtras>,
237    #[serde(default)]
238    pub text_families: HashMap<String, ThemedTextMaterial>,
239    #[serde(default)]
240    pub switch_variants: HashMap<String, ThemedSwitchMaterialExtras>,
241    #[serde(default)]
242    pub modal_shadow_variants: HashMap<String, Color>,
243}
244
245pub struct UiThemeAsset(UiTheme);
246
247impl UiThemeAsset {
248    pub fn get(&self) -> &UiTheme {
249        &self.0
250    }
251}
252
253pub struct UiThemeAssetProtocol;
254
255impl AssetProtocol for UiThemeAssetProtocol {
256    fn name(&self) -> &str {
257        "ui-theme"
258    }
259
260    fn on_load(&mut self, data: Vec<u8>) -> AssetLoadResult {
261        let data = from_utf8(&data).unwrap();
262        match UiTheme::from_prefab_str(data) {
263            Ok(result) => AssetLoadResult::Data(Box::new(UiThemeAsset(result))),
264            Err(error) => AssetLoadResult::Error(format!(
265                "Error loading user interface theme asset: {:?}",
266                error
267            )),
268        }
269    }
270}
271
272fn make_text_family(
273    base_id: &str,
274    base_material: &ThemedTextMaterial,
275    text_variants: &mut HashMap<String, ThemedTextMaterial>,
276) {
277    {
278        let mut material = base_material.clone();
279        material.font.size *= 2.0;
280        text_variants.insert(format!("{}1", base_id), material);
281    }
282    {
283        let mut material = base_material.clone();
284        material.font.size *= 1.5;
285        text_variants.insert(format!("{}2", base_id), material);
286    }
287    {
288        let mut material = base_material.clone();
289        material.font.size *= 1.17;
290        text_variants.insert(format!("{}3", base_id), material);
291    }
292    {
293        text_variants.insert(format!("{}4", base_id), base_material.clone());
294    }
295    {
296        let mut material = base_material.clone();
297        material.font.size *= 0.83;
298        text_variants.insert(format!("{}5", base_id), material);
299    }
300    {
301        let mut material = base_material.clone();
302        material.font.size *= 0.67;
303        text_variants.insert(format!("{}6", base_id), material);
304    }
305    text_variants.insert(base_id.to_owned(), base_material.clone());
306}