Skip to main content

rustmotion_core/css/
cascade.rs

1use super::style::CssStyle;
2
3pub fn inherit_from(parent: &CssStyle, child: &mut CssStyle) {
4    if child.color.is_none() {
5        child.color = parent.color.clone();
6    }
7    if child.font_family.is_none() {
8        child.font_family = parent.font_family.clone();
9    }
10    if child.font_size.is_none() {
11        child.font_size = parent.font_size.clone();
12    }
13    if child.font_weight.is_none() {
14        child.font_weight = parent.font_weight.clone();
15    }
16    if child.font_style.is_none() {
17        child.font_style = parent.font_style;
18    }
19    if child.line_height.is_none() {
20        child.line_height = parent.line_height.clone();
21    }
22    if child.letter_spacing.is_none() {
23        child.letter_spacing = parent.letter_spacing.clone();
24    }
25    if child.text_align.is_none() {
26        child.text_align = parent.text_align;
27    }
28    if child.white_space.is_none() {
29        child.white_space = parent.white_space;
30    }
31    if child.overflow_wrap.is_none() {
32        child.overflow_wrap = parent.overflow_wrap;
33    }
34    if child.visibility.is_none() {
35        child.visibility = parent.visibility;
36    }
37    if child.text_decoration.is_none() {
38        child.text_decoration = parent.text_decoration.clone();
39    }
40}
41
42pub fn overlay_resolved_typography(resolved: &CssStyle, own: &mut CssStyle) {
43    if resolved.color.is_some() {
44        own.color = resolved.color.clone();
45    }
46    if resolved.font_family.is_some() {
47        own.font_family = resolved.font_family.clone();
48    }
49    if resolved.font_size.is_some() {
50        own.font_size = resolved.font_size.clone();
51    }
52    if resolved.font_weight.is_some() {
53        own.font_weight = resolved.font_weight.clone();
54    }
55    if resolved.font_style.is_some() {
56        own.font_style = resolved.font_style;
57    }
58    if resolved.line_height.is_some() {
59        own.line_height = resolved.line_height.clone();
60    }
61    if resolved.letter_spacing.is_some() {
62        own.letter_spacing = resolved.letter_spacing.clone();
63    }
64    if resolved.text_align.is_some() {
65        own.text_align = resolved.text_align;
66    }
67    if resolved.white_space.is_some() {
68        own.white_space = resolved.white_space;
69    }
70    if resolved.overflow_wrap.is_some() {
71        own.overflow_wrap = resolved.overflow_wrap;
72    }
73    if resolved.visibility.is_some() {
74        own.visibility = resolved.visibility;
75    }
76    if resolved.text_decoration.is_some() {
77        own.text_decoration = resolved.text_decoration.clone();
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::css::style::{Color, Display, FontStyle, TextAlign};
85
86    #[test]
87    fn child_inherits_color_when_unset() {
88        let parent = CssStyle {
89            color: Some(Color::String("#ff0000".into())),
90            ..Default::default()
91        };
92        let mut child = CssStyle::default();
93        inherit_from(&parent, &mut child);
94        assert!(matches!(child.color, Some(Color::String(_))));
95    }
96
97    #[test]
98    fn child_overrides_parent_color() {
99        let parent = CssStyle {
100            color: Some(Color::String("#ff0000".into())),
101            ..Default::default()
102        };
103        let mut child = CssStyle {
104            color: Some(Color::String("#00ff00".into())),
105            ..Default::default()
106        };
107        inherit_from(&parent, &mut child);
108        match child.color {
109            Some(Color::String(s)) => assert_eq!(s, "#00ff00"),
110            _ => panic!("expected child color preserved"),
111        }
112    }
113
114    #[test]
115    fn non_inheritable_not_propagated() {
116        let parent = CssStyle {
117            display: Some(Display::Flex),
118            ..Default::default()
119        };
120        let mut child = CssStyle::default();
121        inherit_from(&parent, &mut child);
122        assert!(child.display.is_none());
123    }
124
125    #[test]
126    fn font_props_inherited() {
127        let parent = CssStyle {
128            font_family: Some("Arial".into()),
129            font_style: Some(FontStyle::Italic),
130            text_align: Some(TextAlign::Center),
131            ..Default::default()
132        };
133        let mut child = CssStyle::default();
134        inherit_from(&parent, &mut child);
135        assert_eq!(child.font_family.as_deref(), Some("Arial"));
136        assert_eq!(child.font_style, Some(FontStyle::Italic));
137        assert_eq!(child.text_align, Some(TextAlign::Center));
138    }
139
140    #[test]
141    fn overlay_resolved_typography_replaces_a_component_own_static_font_size() {
142        use crate::css::units::Length;
143
144        let resolved = CssStyle {
145            font_size: Some(Length::Px(120.0)),
146            ..Default::default()
147        };
148        let mut own = CssStyle {
149            font_size: Some(Length::Px(20.0)),
150            ..Default::default()
151        };
152        overlay_resolved_typography(&resolved, &mut own);
153        assert_eq!(
154            own.font_size,
155            Some(Length::Px(120.0)),
156            "the box's own already-animated resolved font-size must win over the component's \
157             declared static one, unlike inherit_from's fill-only-if-none rule"
158        );
159    }
160
161    #[test]
162    fn overlay_resolved_typography_replaces_a_component_own_static_letter_spacing() {
163        use crate::css::units::Length;
164
165        let resolved = CssStyle {
166            letter_spacing: Some(Length::Px(18.0)),
167            ..Default::default()
168        };
169        let mut own = CssStyle {
170            letter_spacing: Some(Length::Px(0.0)),
171            ..Default::default()
172        };
173        overlay_resolved_typography(&resolved, &mut own);
174        assert_eq!(own.letter_spacing, Some(Length::Px(18.0)));
175    }
176
177    #[test]
178    fn overlay_resolved_typography_leaves_a_field_alone_when_resolved_lacks_it() {
179        let resolved = CssStyle::default();
180        let mut own = CssStyle {
181            color: Some(Color::String("#00ff00".into())),
182            ..Default::default()
183        };
184        overlay_resolved_typography(&resolved, &mut own);
185        match own.color {
186            Some(Color::String(s)) => assert_eq!(s, "#00ff00"),
187            other => panic!("resolved had no color; own's must be left alone, got {other:?}"),
188        }
189    }
190}