srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/theme.rs
blob: 1717fe941f9dceec2a64566ec9f03a39ee193827 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/// Default decoration colours and border width, applied to every window at
/// creation (before rules run, so a rule's own `border_color`/`border_width`
/// still wins - see `WindowManager::add_window`) and read live by a
/// backend's titlebar rendering.
///
/// Read from `theme.colors.*`/`theme.decorations.*` in `crates/srdwm/src/
/// main.rs`'s `apply_general_settings`. Before that wiring existed, these
/// were hardcoded Rust constants scattered across `crates/wayland` (the
/// Nord palette every default here still matches, so an unconfigured
/// session looks identical to before) - found the same way `window_gap`
/// and `general.animations` were: config already validated/defaulted these
/// keys, nothing ever read them.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeConfig {
    pub titlebar_bg: (u8, u8, u8),
    pub titlebar_fg_focused: (u8, u8, u8),
    pub titlebar_fg_unfocused: (u8, u8, u8),
    pub default_border_color: (u8, u8, u8),
    pub default_border_width: u32,
}

impl Default for ThemeConfig {
    fn default() -> Self {
        Self {
            titlebar_bg: (0x2e, 0x34, 0x40),
            titlebar_fg_focused: (0x88, 0xc0, 0xd0),
            titlebar_fg_unfocused: (0x4c, 0x56, 0x6a),
            default_border_color: (136, 192, 208), // Nord accent, matches legacy theme default
            default_border_width: 2,
        }
    }
}

/// Parses a `"#rrggbb"` string into its channels. Returns `None` for
/// anything else - `crates/config` already validates this shape at load
/// time (`is_valid_hex_color`) and logs a warning for a malformed value, so
/// a caller here can fall back to a default silently rather than erroring
/// a second time.
pub fn parse_hex_color(s: &str) -> Option<(u8, u8, u8)> {
    if s.len() != 7 || !s.starts_with('#') {
        return None;
    }
    let r = u8::from_str_radix(&s[1..3], 16).ok()?;
    let g = u8::from_str_radix(&s[3..5], 16).ok()?;
    let b = u8::from_str_radix(&s[5..7], 16).ok()?;
    Some((r, g, b))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_a_well_formed_hex_color() {
        assert_eq!(parse_hex_color("#88c0d0"), Some((0x88, 0xc0, 0xd0)));
    }

    #[test]
    fn rejects_missing_hash_or_wrong_length() {
        assert_eq!(parse_hex_color("88c0d0"), None);
        assert_eq!(parse_hex_color("#88c0d"), None);
        assert_eq!(parse_hex_color("#88c0d00"), None);
    }

    #[test]
    fn rejects_non_hex_digits() {
        assert_eq!(parse_hex_color("#zzzzzz"), None);
    }

    #[test]
    fn default_matches_the_legacy_hardcoded_nord_palette() {
        let t = ThemeConfig::default();
        assert_eq!(t.titlebar_bg, (0x2e, 0x34, 0x40));
        assert_eq!(t.titlebar_fg_focused, (0x88, 0xc0, 0xd0));
        assert_eq!(t.default_border_color, (136, 192, 208));
    }
}