Skip to main content

caixa_theme/
palette.rs

1//! Nord — Arctic-themed palette. Reference: <https://www.nordtheme.com/>.
2//!
3//! Polar Night → Snow Storm → Frost → Aurora. All members are const — use
4//! them directly, e.g. `Nord::NORD11` for red.
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct Rgb {
10    pub r: u8,
11    pub g: u8,
12    pub b: u8,
13}
14
15impl Rgb {
16    #[must_use]
17    pub const fn new(r: u8, g: u8, b: u8) -> Self {
18        Self { r, g, b }
19    }
20
21    #[must_use]
22    pub const fn from_hex(hex: u32) -> Self {
23        Self {
24            r: ((hex >> 16) & 0xFF) as u8,
25            g: ((hex >> 8) & 0xFF) as u8,
26            b: (hex & 0xFF) as u8,
27        }
28    }
29
30    #[must_use]
31    pub fn to_hex(self) -> String {
32        format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
33    }
34
35    /// ANSI 24-bit SGR foreground sequence — `\x1b[38;2;R;G;Bm`.
36    #[must_use]
37    pub fn fg_ansi(self) -> String {
38        format!("\x1b[38;2;{};{};{}m", self.r, self.g, self.b)
39    }
40
41    /// ANSI 24-bit SGR background sequence — `\x1b[48;2;R;G;Bm`.
42    #[must_use]
43    pub fn bg_ansi(self) -> String {
44        format!("\x1b[48;2;{};{};{}m", self.r, self.g, self.b)
45    }
46}
47
48/// Reset SGR — ends a color.
49pub const ANSI_RESET: &str = "\x1b[0m";
50
51/// Nord palette — 16 entries, named Nord0 … Nord15 per the canonical spec.
52#[allow(non_snake_case)]
53pub struct Nord;
54
55impl Nord {
56    // Polar Night — backgrounds, UI chrome.
57    pub const NORD0: Rgb = Rgb::new(0x2E, 0x34, 0x40);
58    pub const NORD1: Rgb = Rgb::new(0x3B, 0x42, 0x52);
59    pub const NORD2: Rgb = Rgb::new(0x43, 0x4C, 0x5E);
60    pub const NORD3: Rgb = Rgb::new(0x4C, 0x56, 0x6A);
61    // Snow Storm — foregrounds.
62    pub const NORD4: Rgb = Rgb::new(0xD8, 0xDE, 0xE9);
63    pub const NORD5: Rgb = Rgb::new(0xE5, 0xE9, 0xF0);
64    pub const NORD6: Rgb = Rgb::new(0xEC, 0xEF, 0xF4);
65    // Frost — classes, types, primary accent.
66    pub const NORD7: Rgb = Rgb::new(0x8F, 0xBC, 0xBB);
67    pub const NORD8: Rgb = Rgb::new(0x88, 0xC0, 0xD0);
68    pub const NORD9: Rgb = Rgb::new(0x81, 0xA1, 0xC1);
69    pub const NORD10: Rgb = Rgb::new(0x5E, 0x81, 0xAC);
70    // Aurora — diagnostics, literals.
71    pub const NORD11: Rgb = Rgb::new(0xBF, 0x61, 0x6A); // red   — errors
72    pub const NORD12: Rgb = Rgb::new(0xD0, 0x87, 0x70); // orange — warnings
73    pub const NORD13: Rgb = Rgb::new(0xEB, 0xCB, 0x8B); // yellow — hints
74    pub const NORD14: Rgb = Rgb::new(0xA3, 0xBE, 0x8C); // green  — added / info
75    pub const NORD15: Rgb = Rgb::new(0xB4, 0x8E, 0xAD); // purple — strings / symbols
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn hex_round_trip() {
84        let c = Rgb::from_hex(0x5E_81_AC);
85        assert_eq!(c.to_hex(), "#5E81AC");
86    }
87
88    #[test]
89    fn palette_table_matches_rgb_new_byte_slots() {
90        // Fail-before-pass-after pin that the Rgb::new(r, g, b) table
91        // form the palette entries route through preserves the canonical
92        // Nord byte triples the pre-lift Rgb::from_hex(0xRRGGBB) form
93        // encoded — one spot-check per Polar Night / Snow Storm / Frost
94        // / Aurora quadrant to catch any accidental byte transposition
95        // (r↔g, g↔b) on a future edit. The Rgb::new(r, g, b) surface
96        // aligns the source triple with the runtime field triple, which
97        // is why the pre-lift Rgb::from_hex(0xRRGGBB) hex-nibble split
98        // (once the sole use of the u32→(u8, u8, u8) shift math at
99        // compile time inside the Nord table) is no longer needed here
100        // — Rgb::from_hex stays a public API for consumers with a hex
101        // literal in hand, but no compile-time constant in this crate
102        // still walks it.
103        assert_eq!(Nord::NORD0, Rgb::new(0x2E, 0x34, 0x40));
104        assert_eq!(Nord::NORD6, Rgb::new(0xEC, 0xEF, 0xF4));
105        assert_eq!(Nord::NORD10, Rgb::new(0x5E, 0x81, 0xAC));
106        assert_eq!(Nord::NORD11, Rgb::new(0xBF, 0x61, 0x6A));
107    }
108
109    #[test]
110    fn ansi_fg_shape() {
111        let c = Nord::NORD11;
112        let s = c.fg_ansi();
113        assert!(s.starts_with("\x1b[38;2;"));
114    }
115}