orbtk_theming/
theme.rs

1use std::collections::HashMap;
2
3use ron::Value;
4
5use crate::{
6    config::{ThemeConfig, RESOURCE_KEY},
7    Selector, Style,
8};
9
10#[derive(Debug, Clone, Default, PartialEq)]
11pub struct Theme {
12    styles: HashMap<String, Style>,
13}
14
15impl Theme {
16    pub fn from_config(theme: ThemeConfig) -> Self {
17        let mut styles = HashMap::new();
18
19        for style_key in theme.styles.keys() {
20            let mut properties = HashMap::new();
21            Theme::read_properties(style_key, &theme, &mut properties);
22
23            let mut states = HashMap::new();
24
25            let base_key = theme.styles.get(style_key).unwrap().base.clone();
26
27            if let Some(base) = theme.styles.get(&base_key) {
28                for state_key in base.states.keys() {
29                    let mut state = HashMap::new();
30                    Theme::read_states(&base_key, state_key, &theme, &mut state);
31                    states.insert(state_key.clone(), state);
32                }
33            }
34
35            for state_key in theme.styles.get(style_key).unwrap().states.keys() {
36                let mut state = HashMap::new();
37                Theme::read_states(style_key, state_key, &theme, &mut state);
38                states.insert(state_key.clone(), state);
39            }
40
41            styles.insert(style_key.clone(), Style { properties, states });
42        }
43
44        Theme { styles }
45    }
46
47    pub fn style(&self, key: &str) -> Option<&Style> {
48        self.styles.get(key)
49    }
50
51    pub fn properties<'a>(&'a self, selector: &Selector) -> Option<&'a HashMap<String, Value>> {
52        if !selector.dirty() {
53            return None;
54        }
55
56        if let Some(style) = &selector.style {
57            if let Some(state) = &selector.state {
58                return self.styles.get(style)?.states.get(state);
59            }
60
61            return Some(&self.styles.get(style)?.properties);
62        }
63
64        None
65    }
66
67    fn read_properties(key: &str, theme: &ThemeConfig, properties: &mut HashMap<String, Value>) {
68        if key.is_empty() {
69            return;
70        }
71
72        if let Some(style) = theme.styles.get(key) {
73            Theme::read_properties(&style.base, theme, properties);
74
75            for (key, value) in &style.properties {
76                Theme::read_property(key, value, theme, properties);
77            }
78        }
79    }
80
81    fn read_states(
82        style_key: &str,
83        state_key: &str,
84        theme: &ThemeConfig,
85        states: &mut HashMap<String, Value>,
86    ) {
87        if style_key.is_empty() || state_key.is_empty() {
88            return;
89        }
90
91        if let Some(style) = theme.styles.get(style_key) {
92            for (key, value) in &style.properties {
93                Theme::read_property(key, value, theme, states);
94            }
95
96            if let Some(state) = style.states.get(state_key) {
97                for (key, value) in state {
98                    Theme::read_property(key, value, theme, states);
99                }
100            }
101        }
102    }
103
104    fn read_property(
105        key: &str,
106        value: &Value,
107        theme: &ThemeConfig,
108        map: &mut HashMap<String, Value>,
109    ) {
110        if let Ok(value) = value.clone().into_rust::<String>() {
111            if value.starts_with(RESOURCE_KEY) {
112                if let Some(value) = theme.resources.get(&value.replace(RESOURCE_KEY, "")) {
113                    map.insert(key.to_string(), value.clone());
114                }
115            } else {
116                map.insert(key.to_string(), Value::String(value));
117            }
118        } else {
119            map.insert(key.to_string(), value.clone());
120        }
121    }
122}