use core::{
assets::protocol::{AssetLoadResult, AssetProtocol},
prefab::Prefab,
};
use raui_core::{
widget::{
unit::text::{TextBoxDirection, TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},
utils::Color,
},
Scalar,
};
use raui_material::theme::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, str::from_utf8};
#[derive(Debug, Serialize, Deserialize)]
pub enum UiTheme {
AllWhite(#[serde(default)] ThemePropsExtras),
Flat {
#[serde(default)]
default: Color,
#[serde(default)]
primary: Color,
#[serde(default)]
secondary: Color,
#[serde(default)]
background: Color,
#[serde(default)]
extras: ThemePropsExtras,
},
FlatLight(#[serde(default)] ThemePropsExtras),
FlatDark(#[serde(default)] ThemePropsExtras),
Custom(#[serde(default)] ThemeProps),
}
impl Default for UiTheme {
fn default() -> Self {
Self::AllWhite(Default::default())
}
}
impl UiTheme {
pub fn props(&self) -> ThemeProps {
match self {
Self::AllWhite(extras) => Self::merge(new_all_white_theme(), extras),
Self::Flat {
default,
primary,
secondary,
background,
extras,
} => Self::merge(
make_default_theme(*default, *primary, *secondary, *background),
extras,
),
Self::FlatLight(extras) => Self::merge(new_light_theme(), extras),
Self::FlatDark(extras) => Self::merge(new_dark_theme(), extras),
Self::Custom(props) => props.clone(),
}
}
pub fn merge(mut props: ThemeProps, extras: &ThemePropsExtras) -> ThemeProps {
extras.active_colors.merge_to(&mut props.active_colors);
extras
.background_colors
.merge_to(&mut props.background_colors);
props
.content_backgrounds
.extend(extras.content_backgrounds.clone());
for (k, v) in &extras.button_backgrounds {
v.merge_to(props.button_backgrounds.entry(k.to_owned()).or_default());
}
props
.icons_level_sizes
.extend(extras.icons_level_sizes.clone());
for (k, v) in &extras.text_variants {
v.merge_to(props.text_variants.entry(k.to_owned()).or_default());
}
for (k, v) in &extras.text_families {
make_text_family(k, v, &mut props.text_variants);
}
for (k, v) in &extras.switch_variants {
v.merge_to(props.switch_variants.entry(k.to_owned()).or_default());
}
props
.modal_shadow_variants
.extend(extras.modal_shadow_variants.clone());
props
}
}
impl Prefab for UiTheme {}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemeColorSetExtras {
#[serde(default)]
pub main: Option<Color>,
#[serde(default)]
pub light: Option<Color>,
#[serde(default)]
pub dark: Option<Color>,
}
impl ThemeColorSetExtras {
pub fn merge_to(&self, other: &mut ThemeColorSet) {
if let Some(v) = self.main {
other.main = v;
}
if let Some(v) = self.light {
other.light = v;
}
if let Some(v) = self.dark {
other.dark = v;
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemeColorsExtras {
#[serde(default)]
pub default: ThemeColorSetExtras,
#[serde(default)]
pub primary: ThemeColorSetExtras,
#[serde(default)]
pub secondary: ThemeColorSetExtras,
}
impl ThemeColorsExtras {
pub fn merge_to(&self, other: &mut ThemeColors) {
self.default.merge_to(&mut other.default);
self.primary.merge_to(&mut other.primary);
self.secondary.merge_to(&mut other.secondary);
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemeColorsBundleExtras {
#[serde(default)]
pub main: ThemeColorsExtras,
#[serde(default)]
pub contrast: ThemeColorsExtras,
}
impl ThemeColorsBundleExtras {
pub fn merge_to(&self, other: &mut ThemeColorsBundle) {
self.main.merge_to(&mut other.main);
self.contrast.merge_to(&mut other.contrast);
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemedTextMaterialExtras {
#[serde(default)]
pub horizontal_align: Option<TextBoxHorizontalAlign>,
#[serde(default)]
pub vertical_align: Option<TextBoxVerticalAlign>,
#[serde(default)]
pub direction: Option<TextBoxDirection>,
#[serde(default)]
pub font: Option<TextBoxFont>,
}
impl ThemedTextMaterialExtras {
pub fn merge_to(&self, other: &mut ThemedTextMaterial) {
if let Some(v) = self.horizontal_align {
other.horizontal_align = v;
}
if let Some(v) = self.vertical_align {
other.vertical_align = v;
}
if let Some(v) = self.direction {
other.direction = v;
}
if let Some(v) = &self.font {
other.font = v.clone();
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemedButtonMaterialExtras {
#[serde(default)]
pub default: Option<ThemedImageMaterial>,
#[serde(default)]
pub selected: Option<ThemedImageMaterial>,
#[serde(default)]
pub trigger: Option<ThemedImageMaterial>,
}
impl ThemedButtonMaterialExtras {
pub fn merge_to(&self, other: &mut ThemedButtonMaterial) {
if let Some(v) = &self.default {
other.default = v.clone();
}
if let Some(v) = &self.selected {
other.selected = v.clone();
}
if let Some(v) = &self.trigger {
other.trigger = v.clone();
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemedSwitchMaterialExtras {
#[serde(default)]
pub on: Option<ThemedImageMaterial>,
#[serde(default)]
pub off: Option<ThemedImageMaterial>,
}
impl ThemedSwitchMaterialExtras {
pub fn merge_to(&self, other: &mut ThemedSwitchMaterial) {
if let Some(v) = &self.on {
other.on = v.clone();
}
if let Some(v) = &self.off {
other.off = v.clone();
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ThemePropsExtras {
#[serde(default)]
pub active_colors: ThemeColorsBundleExtras,
#[serde(default)]
pub background_colors: ThemeColorsBundleExtras,
#[serde(default)]
pub content_backgrounds: HashMap<String, ThemedImageMaterial>,
#[serde(default)]
pub button_backgrounds: HashMap<String, ThemedButtonMaterialExtras>,
#[serde(default)]
pub icons_level_sizes: Vec<Scalar>,
#[serde(default)]
pub text_variants: HashMap<String, ThemedTextMaterialExtras>,
#[serde(default)]
pub text_families: HashMap<String, ThemedTextMaterial>,
#[serde(default)]
pub switch_variants: HashMap<String, ThemedSwitchMaterialExtras>,
#[serde(default)]
pub modal_shadow_variants: HashMap<String, Color>,
}
pub struct UiThemeAsset(UiTheme);
impl UiThemeAsset {
pub fn get(&self) -> &UiTheme {
&self.0
}
}
pub struct UiThemeAssetProtocol;
impl AssetProtocol for UiThemeAssetProtocol {
fn name(&self) -> &str {
"ui-theme"
}
fn on_load(&mut self, data: Vec<u8>) -> AssetLoadResult {
let data = from_utf8(&data).unwrap();
match UiTheme::from_prefab_str(data) {
Ok(result) => AssetLoadResult::Data(Box::new(UiThemeAsset(result))),
Err(error) => AssetLoadResult::Error(format!(
"Error loading user interface theme asset: {:?}",
error
)),
}
}
}
fn make_text_family(
base_id: &str,
base_material: &ThemedTextMaterial,
text_variants: &mut HashMap<String, ThemedTextMaterial>,
) {
{
let mut material = base_material.clone();
material.font.size *= 2.0;
text_variants.insert(format!("{}1", base_id), material);
}
{
let mut material = base_material.clone();
material.font.size *= 1.5;
text_variants.insert(format!("{}2", base_id), material);
}
{
let mut material = base_material.clone();
material.font.size *= 1.17;
text_variants.insert(format!("{}3", base_id), material);
}
{
text_variants.insert(format!("{}4", base_id), base_material.clone());
}
{
let mut material = base_material.clone();
material.font.size *= 0.83;
text_variants.insert(format!("{}5", base_id), material);
}
{
let mut material = base_material.clone();
material.font.size *= 0.67;
text_variants.insert(format!("{}6", base_id), material);
}
text_variants.insert(base_id.to_owned(), base_material.clone());
}