Skip to main content

native_theme/model/
fonts.rs

1// Theme font configuration
2
3use serde::{Deserialize, Serialize};
4
5/// Font family and size settings for UI text.
6///
7/// All fields are optional, allowing partial theme definitions
8/// that can be merged with other themes.
9#[serde_with::skip_serializing_none]
10#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
11#[serde(default)]
12#[non_exhaustive]
13pub struct ThemeFonts {
14    /// Primary UI font family name.
15    pub family: Option<String>,
16
17    /// Primary UI font size in points.
18    pub size: Option<f32>,
19
20    /// Monospace font family name (for code, terminal, etc.).
21    pub mono_family: Option<String>,
22
23    /// Monospace font size in points.
24    pub mono_size: Option<f32>,
25}
26
27impl_merge!(ThemeFonts {
28    option { family, size, mono_family, mono_size }
29});
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn default_is_empty() {
37        assert!(ThemeFonts::default().is_empty());
38    }
39
40    #[test]
41    fn not_empty_when_family_set() {
42        let f = ThemeFonts {
43            family: Some("Noto Sans".into()),
44            ..Default::default()
45        };
46        assert!(!f.is_empty());
47    }
48
49    #[test]
50    fn merge_some_replaces_none() {
51        let mut base = ThemeFonts::default();
52        let overlay = ThemeFonts {
53            family: Some("Inter".into()),
54            size: Some(14.0),
55            ..Default::default()
56        };
57        base.merge(&overlay);
58        assert_eq!(base.family.as_deref(), Some("Inter"));
59        assert_eq!(base.size, Some(14.0));
60    }
61
62    #[test]
63    fn merge_none_preserves_base() {
64        let mut base = ThemeFonts {
65            family: Some("Noto Sans".into()),
66            size: Some(12.0),
67            ..Default::default()
68        };
69        let overlay = ThemeFonts::default();
70        base.merge(&overlay);
71        assert_eq!(base.family.as_deref(), Some("Noto Sans"));
72        assert_eq!(base.size, Some(12.0));
73    }
74
75    #[test]
76    fn serde_toml_round_trip() {
77        let fonts = ThemeFonts {
78            family: Some("Inter".into()),
79            size: Some(14.0),
80            mono_family: Some("JetBrains Mono".into()),
81            mono_size: Some(13.0),
82        };
83        let toml_str = toml::to_string(&fonts).unwrap();
84        let deserialized: ThemeFonts = toml::from_str(&toml_str).unwrap();
85        assert_eq!(deserialized, fonts);
86    }
87
88    #[test]
89    fn serde_skips_none_fields() {
90        let fonts = ThemeFonts {
91            family: Some("Inter".into()),
92            ..Default::default()
93        };
94        let toml_str = toml::to_string(&fonts).unwrap();
95        assert!(toml_str.contains("family"));
96        assert!(!toml_str.contains("size"));
97        assert!(!toml_str.contains("mono_family"));
98        assert!(!toml_str.contains("mono_size"));
99    }
100}