Skip to main content

native_theme/model/
spacing.rs

1// Theme spacing scale
2
3use serde::{Deserialize, Serialize};
4
5/// Named spacing scale from extra-extra-small to extra-extra-large.
6///
7/// All values are in logical pixels. The scale provides a consistent
8/// spacing vocabulary across platforms.
9#[serde_with::skip_serializing_none]
10#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
11#[serde(default)]
12#[non_exhaustive]
13pub struct ThemeSpacing {
14    /// Extra-extra-small spacing (e.g., 2px).
15    pub xxs: Option<f32>,
16
17    /// Extra-small spacing (e.g., 4px).
18    pub xs: Option<f32>,
19
20    /// Small spacing (e.g., 8px).
21    pub s: Option<f32>,
22
23    /// Medium spacing (e.g., 12px).
24    pub m: Option<f32>,
25
26    /// Large spacing (e.g., 16px).
27    pub l: Option<f32>,
28
29    /// Extra-large spacing (e.g., 24px).
30    pub xl: Option<f32>,
31
32    /// Extra-extra-large spacing (e.g., 32px).
33    pub xxl: Option<f32>,
34}
35
36impl_merge!(ThemeSpacing {
37    option { xxs, xs, s, m, l, xl, xxl }
38});
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn default_is_empty() {
46        assert!(ThemeSpacing::default().is_empty());
47    }
48
49    #[test]
50    fn not_empty_when_field_set() {
51        let s = ThemeSpacing {
52            m: Some(12.0),
53            ..Default::default()
54        };
55        assert!(!s.is_empty());
56    }
57
58    #[test]
59    fn merge_some_replaces_none() {
60        let mut base = ThemeSpacing::default();
61        let overlay = ThemeSpacing {
62            s: Some(8.0),
63            m: Some(12.0),
64            l: Some(16.0),
65            ..Default::default()
66        };
67        base.merge(&overlay);
68        assert_eq!(base.s, Some(8.0));
69        assert_eq!(base.m, Some(12.0));
70        assert_eq!(base.l, Some(16.0));
71    }
72
73    #[test]
74    fn merge_none_preserves_base() {
75        let mut base = ThemeSpacing {
76            xxs: Some(2.0),
77            xs: Some(4.0),
78            ..Default::default()
79        };
80        let overlay = ThemeSpacing::default();
81        base.merge(&overlay);
82        assert_eq!(base.xxs, Some(2.0));
83        assert_eq!(base.xs, Some(4.0));
84    }
85
86    #[test]
87    fn serde_toml_round_trip() {
88        let spacing = ThemeSpacing {
89            xxs: Some(2.0),
90            xs: Some(4.0),
91            s: Some(8.0),
92            m: Some(12.0),
93            l: Some(16.0),
94            xl: Some(24.0),
95            xxl: Some(32.0),
96        };
97        let toml_str = toml::to_string(&spacing).unwrap();
98        let deserialized: ThemeSpacing = toml::from_str(&toml_str).unwrap();
99        assert_eq!(deserialized, spacing);
100    }
101}