ori_core/style/
stylesheet.rs1use std::{fmt::Display, fs, io, path::Path, str::FromStr};
2
3use crate::{StyleAttribute, StyleAttributes, StyleCache, StyleSelectors, StyleSpecificity};
4
5use super::parse::StyleParseError;
6
7#[derive(Debug)]
9pub enum StyleLoadError {
10 Parse(StyleParseError),
12 Io(io::Error),
14}
15
16impl From<StyleParseError> for StyleLoadError {
17 fn from(error: StyleParseError) -> Self {
18 Self::Parse(error)
19 }
20}
21
22impl From<io::Error> for StyleLoadError {
23 fn from(error: io::Error) -> Self {
24 Self::Io(error)
25 }
26}
27
28impl Display for StyleLoadError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::Parse(error) => write!(f, "Parse error: {}", error),
32 Self::Io(error) => write!(f, "IO error: {}", error),
33 }
34 }
35}
36
37#[macro_export]
38macro_rules! include_stylesheet {
39 ($($tt:tt)*) => {
40 <$crate::Stylesheet as ::std::str::FromStr>::from_str(include_str!($($tt)*)).unwrap()
41 };
42}
43
44macro_rules! theme {
45 ($name:ident, $folder:literal => $($style:literal),* $(,)?) => {
46 pub const $name: &str = concat!(
47 $(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/style/", $folder, "/", $style))),*
48 );
49 };
50}
51
52theme!(
53 DAY_THEME,
54 "day" =>
55 "default.css",
56 "button.css",
57 "checkbox.css",
58 "knob.css",
59 "scroll.css",
60 "text-input.css",
61 "text.css",
62);
63
64theme!(
65 NIGHT_THEME,
66 "night" =>
67 "default.css",
68 "button.css",
69 "checkbox.css",
70 "knob.css",
71 "scroll.css",
72 "text-input.css",
73 "text.css",
74);
75
76#[derive(Clone, Debug, Default)]
81pub struct Stylesheet {
82 pub rules: Vec<StyleRule>,
83 pub cache: StyleCache,
84}
85
86impl Stylesheet {
87 pub fn new() -> Self {
89 Self {
90 rules: Vec::new(),
91 cache: StyleCache::new(),
92 }
93 }
94
95 pub fn day_theme() -> Self {
96 Self::from_str(DAY_THEME).expect("Failed to parse day theme, this is a bug with ori")
97 }
98
99 pub fn night_theme() -> Self {
100 Self::from_str(NIGHT_THEME).expect("Failed to parse night theme, this is a bug with ori")
101 }
102
103 pub fn add_rule(&mut self, rule: StyleRule) {
105 self.cache.clear();
106 self.rules.push(rule);
107 }
108
109 pub fn extend(&mut self, rules: impl IntoIterator<Item = StyleRule>) {
111 self.cache.clear();
112 self.rules.extend(rules);
113 }
114
115 pub fn get_attribute(&self, selectors: &StyleSelectors, name: &str) -> Option<StyleAttribute> {
116 let (attribute, _) = self.get_attribute_specificity(selectors, name)?;
117 Some(attribute.clone())
118 }
119
120 pub fn get_attribute_specificity(
121 &self,
122 selectors: &StyleSelectors,
123 name: &str,
124 ) -> Option<(StyleAttribute, StyleSpecificity)> {
125 if let Some(result) = self.cache.get_attribute(selectors, name) {
126 return Some(result);
127 }
128
129 let mut specificity = StyleSpecificity::default();
130 let mut result = None;
131
132 for rule in self.rules.iter() {
133 if selectors.select(&rule.selector) {
134 let s = rule.selector.specificity();
135
136 if s < specificity {
137 continue;
138 }
139
140 if let Some(attribute) = rule.get_attribute(name) {
141 specificity = s;
142 result = Some((attribute, s));
143 }
144 }
145 }
146
147 let (attribute, specificity) = result?;
148 self.cache.insert(selectors, attribute.clone(), specificity);
149 Some((attribute.clone(), specificity))
150 }
151
152 pub fn load(path: impl AsRef<Path>) -> Result<Self, StyleLoadError> {
154 let input = fs::read_to_string(path)?;
155 Ok(Self::from_str(&input)?)
156 }
157}
158
159impl IntoIterator for Stylesheet {
160 type Item = StyleRule;
161 type IntoIter = std::vec::IntoIter<Self::Item>;
162
163 fn into_iter(self) -> Self::IntoIter {
164 self.rules.into_iter()
165 }
166}
167
168#[derive(Clone, Debug)]
173pub struct StyleRule {
174 pub selector: StyleSelectors,
175 pub attributes: StyleAttributes,
176}
177
178impl StyleRule {
179 pub fn new(selector: StyleSelectors) -> Self {
181 Self {
182 selector,
183 attributes: StyleAttributes::new(),
184 }
185 }
186
187 pub fn add_attribute(&mut self, attribute: StyleAttribute) {
189 self.attributes.add(attribute);
190 }
191
192 pub fn add_attributes(&mut self, attributes: Vec<StyleAttribute>) {
194 self.attributes.extend(attributes);
195 }
196
197 pub fn get_attribute(&self, name: &str) -> Option<&StyleAttribute> {
199 self.attributes.get(name)
200 }
201}