Krolyxon
Commit f2bd31a

Branches

Tags

Refs →

remagesrcconfigtheme.rs

115 lines · 3.3 KB · Rust

1//! Colour tokens, presets and palette derivation.
2
3/// An sRGB colour with straight alpha.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct Rgba(pub [u8; 4]);
6
7impl Rgba {
8 pub const fn rgb(hex: u32) -> Self {
9 Self([(hex >> 16) as u8, (hex >> 8) as u8, hex as u8, 255])
10 }
11
12 /// Mix `self` (share `t`) over `other` in sRGB space.
13 pub fn mix_over(self, other: Rgba, t: f32) -> Rgba {
14 let mut out = [0u8; 4];
15 for (i, o) in out.iter_mut().enumerate() {
16 let v = other.0[i] as f32 + (self.0[i] as f32 - other.0[i] as f32) * t;
17 *o = v.round().clamp(0.0, 255.0) as u8;
18 }
19 Rgba(out)
20 }
21
22 /// Linear-light RGBA for the GPU.
23 pub fn linear(self) -> [f32; 4] {
24 let f = |c: u8| {
25 let c = c as f32 / 255.0;
26 if c <= 0.04045 {
27 c / 12.92
28 } else {
29 ((c + 0.055) / 1.055).powf(2.4)
30 }
31 };
32 [
33 f(self.0[0]),
34 f(self.0[1]),
35 f(self.0[2]),
36 self.0[3] as f32 / 255.0,
37 ]
38 }
39}
40
41#[derive(Clone, Debug, PartialEq)]
42pub struct Theme {
43 pub background: Rgba,
44 pub foreground: Rgba,
45 pub surface: Rgba,
46 pub surface_raised: Rgba,
47 pub border: Rgba,
48 pub border_visible: Rgba,
49 pub text_disabled: Rgba,
50 pub text_secondary: Rgba,
51 pub text_primary: Rgba,
52 pub text_display: Rgba,
53 pub accent: Rgba,
54 pub success: Rgba,
55 pub warning: Rgba,
56 pub image_background: Rgba,
57}
58
59impl Theme {
60 /// Derive every token from background, foreground and accent. The fg shares reproduce the
61 /// Nothing dark palette exactly from black/white.
62 pub fn derive(background: Rgba, foreground: Rgba, accent: Rgba) -> Self {
63 let mix = |t: f32| foreground.mix_over(background, t);
64 Self {
65 background,
66 foreground,
67 surface: mix(0.067),
68 surface_raised: mix(0.10),
69 border: mix(0.133),
70 border_visible: mix(0.20),
71 text_disabled: mix(0.40),
72 text_secondary: mix(0.60),
73 text_primary: mix(0.91),
74 text_display: foreground,
75 accent,
76 success: Rgba::rgb(0x4A9E5C),
77 warning: Rgba::rgb(0xD4A843),
78 image_background: background,
79 }
80 }
81
82 pub fn nothing_dark() -> Self {
83 Self::derive(
84 Rgba::rgb(0x000000),
85 Rgba::rgb(0xFFFFFF),
86 Rgba::rgb(0xD71921),
87 )
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn derivation_reproduces_nothing_dark() {
97 let t = Theme::nothing_dark();
98 assert_eq!(t.surface, Rgba::rgb(0x111111));
99 assert_eq!(t.surface_raised, Rgba::rgb(0x1A1A1A));
100 assert_eq!(t.border, Rgba::rgb(0x222222));
101 assert_eq!(t.border_visible, Rgba::rgb(0x333333));
102 assert_eq!(t.text_disabled, Rgba::rgb(0x666666));
103 assert_eq!(t.text_secondary, Rgba::rgb(0x999999));
104 assert_eq!(t.text_primary, Rgba::rgb(0xE8E8E8));
105 assert_eq!(t.text_display, Rgba::rgb(0xFFFFFF));
106 }
107
108 #[test]
109 fn linear_conversion() {
110 assert_eq!(Rgba::rgb(0x000000).linear(), [0.0, 0.0, 0.0, 1.0]);
111 assert_eq!(Rgba::rgb(0xFFFFFF).linear(), [1.0, 1.0, 1.0, 1.0]);
112 let mid = Rgba::rgb(0x808080).linear()[0];
113 assert!((mid - 0.2158).abs() < 1e-3);
114 }
115}