oxygengine_user_interface/
ui_theme_asset_protocol.rs1use 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}