Skip to main content

drawlang_render/
theme.rs

1//! Theme palettes. Every color a diagram uses comes from here unless the
2//! source gives an explicit `#hex`; `@tokens` resolve against the active
3//! palette so one source renders well in both themes.
4
5use drawlang_core::model::{Color, Theme};
6
7#[derive(Debug, Clone)]
8pub struct Palette {
9    pub bg: &'static str,
10    pub surface: &'static str,
11    /// Fill for boxes nested inside another node (e.g. HBM inside a GPU).
12    pub inner: &'static str,
13    pub ink: &'static str,
14    pub muted: &'static str,
15    pub faint: &'static str,
16    pub accent: &'static str,
17    pub accent2: &'static str,
18    pub hot: &'static str,
19    pub ok: &'static str,
20    pub warn: &'static str,
21    /// Default node border.
22    pub node_border: &'static str,
23    /// Default edge stroke.
24    pub edge: &'static str,
25    /// Edge label text.
26    pub edge_label: &'static str,
27    /// Group border + label.
28    pub group_border: &'static str,
29    pub group_label: &'static str,
30    /// Group interior wash opacity (over `ink`).
31    pub group_wash_opacity: f64,
32}
33
34pub const PAPER: Palette = Palette {
35    bg: "#FAF9F7",
36    surface: "#FFFFFF",
37    inner: "#F4F2ED",
38    ink: "#21242D",
39    muted: "#8B8D98",
40    faint: "#E2DFD8",
41    accent: "#2F6FED",
42    accent2: "#8E4EC6",
43    hot: "#E5484D",
44    ok: "#30A46C",
45    warn: "#B98900",
46    node_border: "#3A3F4B",
47    edge: "#6E7180",
48    edge_label: "#4B4F5C",
49    group_border: "#CDC9C0",
50    group_label: "#7A7D89",
51    group_wash_opacity: 0.025,
52};
53
54pub const DARK: Palette = Palette {
55    bg: "#15171C",
56    surface: "#20242C",
57    inner: "#2A2F39",
58    ink: "#E8EAF0",
59    muted: "#9BA1AE",
60    faint: "#32363F",
61    accent: "#5C8DEF",
62    accent2: "#B083F0",
63    hot: "#F2555A",
64    ok: "#3DD68C",
65    warn: "#FFC53D",
66    node_border: "#555B68",
67    edge: "#878D9A",
68    edge_label: "#B3B8C4",
69    group_border: "#3B404B",
70    group_label: "#9BA1AE",
71    group_wash_opacity: 0.16,
72};
73
74pub fn palette(theme: Theme) -> &'static Palette {
75    match theme {
76        Theme::Paper => &PAPER,
77        Theme::Dark => &DARK,
78    }
79}
80
81impl Palette {
82    pub fn token(&self, name: &str) -> &'static str {
83        match name {
84            "accent" => self.accent,
85            "accent2" => self.accent2,
86            "hot" => self.hot,
87            "ok" => self.ok,
88            "warn" => self.warn,
89            "muted" => self.muted,
90            "faint" => self.faint,
91            "ink" => self.ink,
92            "bg" => self.bg,
93            "surface" => self.surface,
94            // Analyze validates token names; this is a safety net.
95            _ => self.ink,
96        }
97    }
98
99    /// Resolve an optional model color with a palette default.
100    pub fn resolve(&self, c: Option<&Color>, default: &'static str) -> String {
101        match c {
102            None => default.to_string(),
103            Some(Color::Token(t)) => self.token(t).to_string(),
104            Some(Color::Hex(h)) => format!("#{h}"),
105        }
106    }
107}