1mod cache;
24mod motion;
25mod paint;
26mod registry;
27mod selector;
28mod source;
29mod style;
30mod validate;
31
32use std::collections::BTreeMap;
33use std::sync::Arc;
34
35pub use motion::Motion;
36pub(crate) use motion::{MOTION_KEYS, parse_duration};
37pub(crate) use paint::Expr;
38pub use paint::Paint;
39pub use registry::{Resolved, ThemeRegistry};
40pub use selector::{Selector, State};
41pub use style::{PropValue, StyleProps, WORD_PROPS};
42
43use crate::animation::CellAnimation;
44use crate::color::Rgb;
45use crate::icons::IconGlyphs;
46use cache::StyleCache;
47
48pub const REQUIRED_COLORS: [&str; 20] = [
50 "canvas", "surface", "raised", "active", "overlay", "accent", "accent-2", "text", "dim", "muted", "ink", "success",
51 "warning", "danger", "info", "series-1", "series-2", "series-3", "series-4", "series-5",
52];
53
54pub const SERIES_COLORS: usize = 5;
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct Theme {
64 id: String,
65 name: String,
66 colors: BTreeMap<String, Rgb>,
67 motion: Motion,
68 typography: BTreeMap<String, StyleProps>,
69 rules: Vec<(Selector, StyleProps)>,
70 icon_set: String,
71 icons: BTreeMap<String, IconGlyphs>,
72 animations: BTreeMap<String, Arc<CellAnimation>>,
73 cache: StyleCache,
75}
76
77impl Theme {
78 #[must_use]
80 pub fn id(&self) -> &str {
81 &self.id
82 }
83
84 #[must_use]
86 pub fn name(&self) -> &str {
87 &self.name
88 }
89
90 #[must_use]
92 pub fn color(&self, token: &str) -> Option<Rgb> {
93 self.colors.get(token).copied()
94 }
95
96 pub fn solid(&self, expression: &str) -> Result<Rgb, String> {
104 paint::Expr::parse(expression)?.solid(&self.colors)
105 }
106
107 #[must_use]
114 pub fn series_color(&self, index: usize) -> Rgb {
115 let token = format!("series-{}", index % SERIES_COLORS + 1);
116 self.color(&token).unwrap_or_else(|| self.colors.get("accent").copied().unwrap_or(Rgb::new(0, 0, 0)))
117 }
118
119 pub fn colors(&self) -> impl Iterator<Item = (&str, Rgb)> {
121 self.colors.iter().map(|(name, color)| (name.as_str(), *color))
122 }
123
124 #[must_use]
126 pub fn motion(&self) -> Motion {
127 self.motion
128 }
129
130 #[must_use]
132 pub fn typography(&self, role: &str) -> Option<&StyleProps> {
133 self.typography.get(role)
134 }
135
136 #[must_use]
138 pub fn icon_set(&self) -> &str {
139 &self.icon_set
140 }
141
142 #[must_use]
144 pub fn icon_overrides(&self) -> &BTreeMap<String, IconGlyphs> {
145 &self.icons
146 }
147
148 #[must_use]
150 pub fn animation_overrides(&self) -> &BTreeMap<String, Arc<CellAnimation>> {
151 &self.animations
152 }
153
154 #[must_use]
160 pub fn style(&self, widget: &str, variant: Option<&str>, states: &[State]) -> StyleProps {
161 self.cache.get_or_insert(widget, variant, states, || self.layer_rules(widget, variant, states))
162 }
163
164 fn layer_rules(&self, widget: &str, variant: Option<&str>, states: &[State]) -> StyleProps {
166 let mut matching: Vec<(usize, &(Selector, StyleProps))> = self
167 .rules
168 .iter()
169 .enumerate()
170 .filter(|(_, (selector, _))| selector.matches(widget, variant, states))
171 .collect();
172 matching.sort_by_key(|(order, (selector, _))| (selector.specificity(), *order));
173 let mut props = StyleProps::default();
174 for (_, (_, rule)) in matching {
175 props.overlay(rule);
176 }
177 props
178 }
179}