Skip to main content

native_theme/model/
icon_sizes.rs

1// Icon size configuration
2
3use serde::{Deserialize, Serialize};
4
5/// Per-context icon sizes in logical pixels.
6///
7/// Defines the expected icon size for each visual context. All fields are
8/// optional to support partial overlays.
9#[serde_with::skip_serializing_none]
10#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
11#[serde(default)]
12pub struct IconSizes {
13    /// Icon size for toolbar buttons (e.g., 24px).
14    pub toolbar: Option<f32>,
15    /// Small icon size for inline use (e.g., 16px).
16    pub small: Option<f32>,
17    /// Large icon size for menus/lists (e.g., 32px).
18    pub large: Option<f32>,
19    /// Icon size for dialog buttons (e.g., 22px).
20    pub dialog: Option<f32>,
21    /// Icon size for panel headers (e.g., 20px).
22    pub panel: Option<f32>,
23}
24
25impl_merge!(IconSizes {
26    option { toolbar, small, large, dialog, panel }
27});
28
29#[cfg(test)]
30#[allow(clippy::unwrap_used, clippy::expect_used)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn icon_sizes_default_is_empty() {
36        assert!(IconSizes::default().is_empty());
37    }
38
39    #[test]
40    fn icon_sizes_not_empty_when_toolbar_set() {
41        let s = IconSizes {
42            toolbar: Some(24.0),
43            ..Default::default()
44        };
45        assert!(!s.is_empty());
46    }
47
48    #[test]
49    fn icon_sizes_not_empty_when_any_field_set() {
50        for sizes in [
51            IconSizes {
52                toolbar: Some(24.0),
53                ..Default::default()
54            },
55            IconSizes {
56                small: Some(16.0),
57                ..Default::default()
58            },
59            IconSizes {
60                large: Some(32.0),
61                ..Default::default()
62            },
63            IconSizes {
64                dialog: Some(22.0),
65                ..Default::default()
66            },
67            IconSizes {
68                panel: Some(20.0),
69                ..Default::default()
70            },
71        ] {
72            assert!(!sizes.is_empty());
73        }
74    }
75
76    #[test]
77    fn icon_sizes_merge_overlay_wins() {
78        let mut base = IconSizes {
79            toolbar: Some(24.0),
80            small: Some(16.0),
81            large: None,
82            dialog: None,
83            panel: None,
84        };
85        let overlay = IconSizes {
86            toolbar: None,
87            small: Some(18.0),
88            large: Some(32.0),
89            dialog: None,
90            panel: None,
91        };
92        base.merge(&overlay);
93        assert_eq!(base.toolbar, Some(24.0)); // preserved
94        assert_eq!(base.small, Some(18.0)); // overlay wins
95        assert_eq!(base.large, Some(32.0)); // overlay sets
96        assert_eq!(base.dialog, None);
97        assert_eq!(base.panel, None);
98    }
99
100    #[test]
101    fn icon_sizes_merge_none_preserves_base() {
102        let mut base = IconSizes {
103            toolbar: Some(24.0),
104            small: Some(16.0),
105            large: Some(32.0),
106            dialog: Some(22.0),
107            panel: Some(20.0),
108        };
109        let overlay = IconSizes::default();
110        base.merge(&overlay);
111        assert_eq!(base.toolbar, Some(24.0));
112        assert_eq!(base.small, Some(16.0));
113        assert_eq!(base.large, Some(32.0));
114        assert_eq!(base.dialog, Some(22.0));
115        assert_eq!(base.panel, Some(20.0));
116    }
117
118    #[test]
119    fn icon_sizes_toml_round_trip() {
120        let sizes = IconSizes {
121            toolbar: Some(24.0),
122            small: Some(16.0),
123            large: Some(32.0),
124            dialog: Some(22.0),
125            panel: Some(20.0),
126        };
127        let toml_str = toml::to_string(&sizes).unwrap();
128        let deserialized: IconSizes = toml::from_str(&toml_str).unwrap();
129        assert_eq!(deserialized, sizes);
130    }
131
132    #[test]
133    fn icon_sizes_toml_partial_round_trip() {
134        let sizes = IconSizes {
135            toolbar: Some(24.0),
136            ..Default::default()
137        };
138        let toml_str = toml::to_string(&sizes).unwrap();
139        let deserialized: IconSizes = toml::from_str(&toml_str).unwrap();
140        assert_eq!(deserialized, sizes);
141        assert!(deserialized.small.is_none());
142    }
143}