Skip to main content

oxiui_theme/
inheritance.rs

1//! CSS-style property inheritance for computed styles.
2//!
3//! In CSS, some properties are inherited from parent to child by default
4//! (notably `color`, `font-size`, and `font-weight`) while others are not
5//! (spacing, border, opacity).  This module provides [`resolve`] which applies
6//! those rules between a parent and child [`ComputedStyle`].
7
8use crate::stylesheet::{ComputedStyle, CssValue};
9
10/// Resolve a child style against its parent by applying CSS inheritance rules.
11///
12/// **Inheritable** properties (`color`, `font-size`, `font-weight`): if the
13/// child's value is `None` or explicitly `inherit`, the parent's value is
14/// copied in.  If the child sets `initial` or `unset`, the property is cleared
15/// back to `None`.
16///
17/// **Non-inheritable** properties (`background-color`, `padding`, `margin`,
18/// `border-*`, `opacity`): these are left as-is on the child; the parent's
19/// values are ignored.
20pub fn resolve(parent: &ComputedStyle, child: &mut ComputedStyle) {
21    // ── color (inheritable) ───────────────────────────────────────────────────
22    if matches!(child.color, Some(CssValue::Inherit)) || child.color.is_none() {
23        child.color = parent.color.clone();
24    }
25    if matches!(child.color, Some(CssValue::Initial) | Some(CssValue::Unset)) {
26        child.color = None;
27    }
28
29    // ── font-size (inheritable) ───────────────────────────────────────────────
30    if child.font_size.is_none() {
31        child.font_size = parent.font_size;
32    }
33
34    // ── font-weight (inheritable) ─────────────────────────────────────────────
35    if child.font_weight.is_none() {
36        child.font_weight = parent.font_weight;
37    }
38
39    // background-color, padding, margin, border-*, opacity: NOT inherited.
40    // They stay as-is on the child.
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::stylesheet::CssValue;
47    use oxiui_core::Color;
48
49    fn color_val(r: u8, g: u8, b: u8) -> CssValue {
50        CssValue::Color(Color(r, g, b, 255))
51    }
52
53    #[test]
54    fn color_flows_from_parent_when_child_has_none() {
55        let parent = ComputedStyle {
56            color: Some(color_val(255, 0, 0)),
57            ..Default::default()
58        };
59        let mut child = ComputedStyle::default();
60        resolve(&parent, &mut child);
61        assert_eq!(child.color, Some(color_val(255, 0, 0)));
62    }
63
64    #[test]
65    fn color_inherit_keyword_copies_parent() {
66        let parent = ComputedStyle {
67            color: Some(color_val(0, 255, 0)),
68            ..Default::default()
69        };
70        let mut child = ComputedStyle {
71            color: Some(CssValue::Inherit),
72            ..Default::default()
73        };
74        resolve(&parent, &mut child);
75        assert_eq!(child.color, Some(color_val(0, 255, 0)));
76    }
77
78    #[test]
79    fn color_initial_clears_to_none() {
80        let parent = ComputedStyle {
81            color: Some(color_val(0, 0, 255)),
82            ..Default::default()
83        };
84        let mut child = ComputedStyle {
85            color: Some(CssValue::Initial),
86            ..Default::default()
87        };
88        resolve(&parent, &mut child);
89        assert!(child.color.is_none());
90    }
91
92    #[test]
93    fn padding_not_inherited() {
94        let parent = ComputedStyle {
95            padding: Some(16.0),
96            ..Default::default()
97        };
98        let mut child = ComputedStyle::default();
99        resolve(&parent, &mut child);
100        assert!(child.padding.is_none(), "padding must not be inherited");
101    }
102
103    #[test]
104    fn font_size_flows_from_parent() {
105        let parent = ComputedStyle {
106            font_size: Some(18.0),
107            ..Default::default()
108        };
109        let mut child = ComputedStyle::default();
110        resolve(&parent, &mut child);
111        assert_eq!(child.font_size, Some(18.0));
112    }
113
114    #[test]
115    fn font_weight_flows_from_parent() {
116        let parent = ComputedStyle {
117            font_weight: Some(700.0),
118            ..Default::default()
119        };
120        let mut child = ComputedStyle::default();
121        resolve(&parent, &mut child);
122        assert_eq!(child.font_weight, Some(700.0));
123    }
124
125    #[test]
126    fn own_font_size_not_overridden_by_parent() {
127        let parent = ComputedStyle {
128            font_size: Some(18.0),
129            ..Default::default()
130        };
131        let mut child = ComputedStyle {
132            font_size: Some(12.0),
133            ..Default::default()
134        };
135        resolve(&parent, &mut child);
136        assert_eq!(child.font_size, Some(12.0));
137    }
138}