1use serde::{Deserialize, Serialize};
4
5pub type Rgba = u32;
7
8#[must_use]
10pub fn unpack(c: Rgba) -> [f32; 4] {
11 [
12 ((c >> 24) & 0xff) as f32 / 255.0,
13 ((c >> 16) & 0xff) as f32 / 255.0,
14 ((c >> 8) & 0xff) as f32 / 255.0,
15 (c & 0xff) as f32 / 255.0,
16 ]
17}
18
19#[must_use]
21pub fn pack(c: [f32; 4]) -> Rgba {
22 let q = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u32;
23 (q(c[0]) << 24) | (q(c[1]) << 16) | (q(c[2]) << 8) | q(c[3])
24}
25
26#[must_use]
28pub fn with_alpha(c: Rgba, factor: f32) -> Rgba {
29 let mut v = unpack(c);
30 v[3] *= factor;
31 pack(v)
32}
33
34#[must_use]
36pub fn premul_bytes(c: Rgba) -> u32 {
37 let [r, g, b, a] = unpack(c);
38 let q = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u32;
39 q(r * a) | (q(g * a) << 8) | (q(b * a) << 16) | (q(a) << 24)
40}
41
42#[must_use]
44pub fn parse_hex(s: &str) -> Option<Rgba> {
45 let h = s.strip_prefix('#')?;
46 let v = u32::from_str_radix(h, 16).ok()?;
47 match h.len() {
48 3 => {
49 let (r, g, b) = ((v >> 8) & 0xf, (v >> 4) & 0xf, v & 0xf);
50 Some(((r * 17) << 24) | ((g * 17) << 16) | ((b * 17) << 8) | 0xff)
51 }
52 6 => Some((v << 8) | 0xff),
53 8 => Some(v),
54 _ => None,
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase", default)]
61pub struct Theme {
62 pub background: Rgba,
64 pub foreground: Rgba,
66 pub selection: Rgba,
68 pub hover: Rgba,
70 pub problem: Rgba,
72 pub grid_minor: Rgba,
74 pub grid_major: Rgba,
76 pub grid_axis: Rgba,
78 pub marker: Rgba,
80 pub marquee_window: Rgba,
82 pub marquee_crossing: Rgba,
84 pub guide: Rgba,
86 pub sketch: Rgba,
88}
89
90impl Default for Theme {
91 fn default() -> Self {
92 Self::light()
93 }
94}
95
96impl Theme {
97 #[must_use]
99 pub const fn light() -> Self {
100 Self {
101 background: 0xffff_ffff,
102 foreground: 0x1f23_28ff,
103 selection: 0x0a66_d9ff,
104 hover: 0x4d94_ffff,
105 problem: 0xd12d_2dff,
106 grid_minor: 0xe9ec_f0ff,
107 grid_major: 0xd3d8_deff,
108 grid_axis: 0xb0b8_c2ff,
109 marker: 0xe0_6c00ff,
110 marquee_window: 0x0a66_d9ff,
111 marquee_crossing: 0x1e9e_4aff,
112 guide: 0x8a63_d2ff,
113 sketch: 0x0a66_d9ff,
114 }
115 }
116
117 #[must_use]
119 pub const fn dark() -> Self {
120 Self {
121 background: 0x1518_1cff,
122 foreground: 0xe6ea_eeff,
123 selection: 0x5aa2_ffff,
124 hover: 0x8bbf_ffff,
125 problem: 0xff6b_6bff,
126 grid_minor: 0x2025_2bff,
127 grid_major: 0x2c33_3bff,
128 grid_axis: 0x4a54_60ff,
129 marker: 0xffa6_33ff,
130 marquee_window: 0x5aa2_ffff,
131 marquee_crossing: 0x4cd3_7aff,
132 guide: 0xb59c_ffff,
133 sketch: 0x5aa2_ffff,
134 }
135 }
136
137 #[must_use]
139 pub fn resolve(&self, c: Rgba) -> Rgba {
140 if c == 0 { self.foreground } else { c }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn pack_roundtrip_and_hex() {
150 assert_eq!(pack(unpack(0x1234_56ff)), 0x1234_56ff);
151 assert_eq!(parse_hex("#fff"), Some(0xffff_ffff));
152 assert_eq!(parse_hex("#102030"), Some(0x1020_30ff));
153 assert_eq!(parse_hex("#10203040"), Some(0x1020_3040));
154 assert_eq!(parse_hex("102030"), None);
155 }
156
157 #[test]
158 fn premultiplied_layout() {
159 let v = premul_bytes(0xff00_0080);
161 assert_eq!(v & 0xff, 128);
162 assert_eq!(v >> 24, 128);
163 assert_eq!((v >> 8) & 0xff, 0);
164 }
165}