/// StyleSheet — named style rules for components. /// /// Instead of applying modifiers inline everywhere, you can define named /// style rules in a StyleSheet and apply them with `.apply("card")`. /// This is the el-ui equivalent of CSS class names, but type-safe and /// resolved at compile time. use std::collections::HashMap; use crate::modifier::StyleSet; /// A stylesheet: a named collection of StyleSet rules. #[derive(Debug, Clone, Default)] pub struct StyleSheet { rules: HashMap, } impl StyleSheet { pub fn new() -> Self { Self::default() } /// Define a named style rule. pub fn define(&mut self, name: impl Into, style: StyleSet) -> &mut Self { self.rules.insert(name.into(), style); self } /// Look up a named style rule. /// Returns None if the rule doesn't exist. pub fn get(&self, name: &str) -> Option<&StyleSet> { self.rules.get(name) } /// Apply a named style to a StyleSet by merging — caller's explicit /// values win, the stylesheet fills in the rest. /// /// This is an additive operation: fields that are Some in the named /// rule overwrite fields that are None in the target. pub fn apply(&self, name: &str, target: &mut StyleSet) { if let Some(rule) = self.rules.get(name) { if target.padding.is_none() { target.padding = rule.padding; } if target.margin.is_none() { target.margin = rule.margin; } if target.background.is_none() { target.background = rule.background.clone(); } if target.foreground.is_none() { target.foreground = rule.foreground.clone(); } if target.font.is_none() { target.font = rule.font.clone(); } if target.radius.is_none() { target.radius = rule.radius; } if target.shadow.is_none() { target.shadow = rule.shadow; } if target.opacity.is_none() { target.opacity = rule.opacity; } if target.border_width.is_none() { target.border_width = rule.border_width; } if target.border_color.is_none() { target.border_color = rule.border_color.clone(); } if target.width.is_none() { target.width = rule.width.clone(); } if target.height.is_none() { target.height = rule.height.clone(); } if target.max_width.is_none() { target.max_width = rule.max_width.clone(); } } } /// The number of rules defined. pub fn len(&self) -> usize { self.rules.len() } pub fn is_empty(&self) -> bool { self.rules.is_empty() } } #[cfg(test)] mod tests { use super::*; use crate::color::Color; use crate::modifier::StyleSet; fn card_style() -> StyleSet { let mut s = StyleSet::default(); s.padding = Some([16; 4]); s.radius = Some([8; 4]); s.background = Some(Color::Surface); s } #[test] fn stylesheet_define_and_get() { let mut ss = StyleSheet::new(); ss.define("card", card_style()); assert!(ss.get("card").is_some()); } #[test] fn stylesheet_missing_rule_returns_none() { let ss = StyleSheet::new(); assert!(ss.get("nonexistent").is_none()); } #[test] fn stylesheet_apply_fills_missing_values() { let mut ss = StyleSheet::new(); ss.define("card", card_style()); let mut target = StyleSet::default(); ss.apply("card", &mut target); assert_eq!(target.padding, Some([16; 4])); assert_eq!(target.background, Some(Color::Surface)); } #[test] fn stylesheet_apply_does_not_overwrite_existing() { let mut ss = StyleSheet::new(); ss.define("card", card_style()); let mut target = StyleSet::default(); target.background = Some(Color::Primary); // already set ss.apply("card", &mut target); // Should NOT be overwritten by the stylesheet's Surface assert_eq!(target.background, Some(Color::Primary)); } #[test] fn stylesheet_len() { let mut ss = StyleSheet::new(); assert_eq!(ss.len(), 0); ss.define("card", card_style()); assert_eq!(ss.len(), 1); } #[test] fn stylesheet_apply_nonexistent_is_noop() { let ss = StyleSheet::new(); let mut target = StyleSet::default(); ss.apply("does-not-exist", &mut target); // should not panic assert!(target.padding.is_none()); } }