1use crate::Color;
2use serde::{Deserialize, Serialize};
3
4use blockworx_geom::WorldPx;
5
6#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, strum::EnumIter)]
16pub enum Base {
17 B00,
18 B01,
19 B02,
20 B03,
21 B04,
22 B05,
23 B06,
24 B07,
25 B08,
26 B09,
27 B0A,
28 B0B,
29 B0C,
30 B0D,
31 B0E,
32 B0F,
33}
34
35#[derive(Clone, Copy, PartialEq)]
43pub struct Swatch {
44 pub base: Option<Base>,
45 pub alpha: f32,
46}
47
48impl Swatch {
49 pub const TRANSPARENT: Swatch = Swatch {
51 base: None,
52 alpha: 1.0,
53 };
54
55 pub const fn solid(base: Base) -> Self {
57 Self {
58 base: Some(base),
59 alpha: 1.0,
60 }
61 }
62
63 pub const fn faded(base: Base, alpha: f32) -> Self {
66 Self {
67 base: Some(base),
68 alpha,
69 }
70 }
71}
72
73impl From<Base> for Swatch {
74 fn from(base: Base) -> Self {
75 Swatch::solid(base)
76 }
77}
78
79#[derive(Clone, Copy)]
85pub struct PaletteStroke {
86 pub width: WorldPx,
87 pub color: Swatch,
88}
89
90impl PaletteStroke {
91 pub const NONE: PaletteStroke = PaletteStroke {
93 width: WorldPx::ZERO,
94 color: Swatch::TRANSPARENT,
95 };
96}
97
98impl From<(f32, Base)> for PaletteStroke {
99 fn from((width, base): (f32, Base)) -> Self {
100 PaletteStroke {
101 width: WorldPx::new(width),
102 color: Swatch::solid(base),
103 }
104 }
105}
106
107impl From<(f32, Swatch)> for PaletteStroke {
108 fn from((width, color): (f32, Swatch)) -> Self {
109 PaletteStroke {
110 width: WorldPx::new(width),
111 color,
112 }
113 }
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum Luminance {
120 Light,
121 Dark,
122}
123
124impl Luminance {
125 pub const fn is_dark(self) -> bool {
126 matches!(self, Luminance::Dark)
127 }
128}
129
130impl From<bool> for Luminance {
131 fn from(dark: bool) -> Self {
132 if dark {
133 Luminance::Dark
134 } else {
135 Luminance::Light
136 }
137 }
138}
139
140#[derive(Clone, PartialEq, Serialize, Deserialize)]
145pub struct Palette {
146 colors: [Color; 16],
147 is_dark: bool,
151}
152
153impl Palette {
154 pub fn get(&self, base: Base) -> Color {
156 self.colors[base as usize]
157 }
158
159 pub fn resolve(&self, swatch: Swatch) -> Color {
162 match swatch.base {
163 Some(base) => self.get(base).gamma_multiply(swatch.alpha),
164 None => Color::TRANSPARENT,
165 }
166 }
167
168 pub fn tokyo_night_moon() -> Self {
172 Self {
173 is_dark: true,
174 colors: [
175 rgb(0x22, 0x24, 0x36), rgb(0x2f, 0x33, 0x4d), rgb(0x3b, 0x42, 0x61), rgb(0x54, 0x5c, 0x7e), rgb(0x82, 0x8b, 0xb8), rgb(0xc8, 0xd3, 0xf5), rgb(0xd5, 0xdb, 0xf5), rgb(0xe6, 0xea, 0xfe), rgb(0xff, 0x75, 0x7f), rgb(0xff, 0x96, 0x6c), rgb(0xff, 0xc7, 0x77), rgb(0xc3, 0xe8, 0x8d), rgb(0x86, 0xe1, 0xfc), rgb(0x82, 0xaa, 0xff), rgb(0xc0, 0x99, 0xff), rgb(0xc5, 0x3b, 0x53), ],
192 }
193 }
194
195 pub fn is_dark(&self) -> bool {
197 self.is_dark
198 }
199
200 #[must_use]
203 pub fn toned(&self, saturation: Saturation) -> Self {
204 Self {
205 colors: self.colors.map(|color| saturation.applied(color)),
206 is_dark: self.is_dark,
207 }
208 }
209}
210
211#[derive(Clone, Copy, PartialEq, Eq, Debug)]
218pub enum Saturation {
219 Full,
220 Drained,
221}
222
223impl Saturation {
224 const REMAINING: f32 = 0.35;
228
229 pub fn applied(self, color: Color) -> Color {
230 match self {
231 Saturation::Full => color,
232 Saturation::Drained => {
233 let grey = luma(color);
234 let mix = |channel: u8| {
235 let kept = f32::from(channel) * Self::REMAINING;
236 let lost = f32::from(grey) * (1.0 - Self::REMAINING);
237 (kept + lost) as u8
238 };
239 Color::from_rgb(mix(color.r()), mix(color.g()), mix(color.b()))
240 }
241 }
242 }
243}
244
245fn luma(color: Color) -> u8 {
248 (0.299 * f32::from(color.r()) + 0.587 * f32::from(color.g()) + 0.114 * f32::from(color.b()))
249 as u8
250}
251
252impl Palette {
259 const fn base16(luminance: Luminance, hexes: [u32; 16]) -> Self {
261 let mut colors = [Color::TRANSPARENT; 16];
262 let mut i = 0;
263 while i < 16 {
264 let v = hexes[i];
265 colors[i] = Color::from_rgb((v >> 16) as u8, (v >> 8) as u8, v as u8);
266 i += 1;
267 }
268 Self {
269 colors,
270 is_dark: luminance.is_dark(),
271 }
272 }
273
274 pub fn catppuccin_mocha() -> Self {
275 Self::base16(
276 Luminance::Dark,
277 [
278 0x1e1e2e, 0x181825, 0x313244, 0x45475a, 0x585b70, 0xcdd6f4, 0xf5e0dc, 0xb4befe,
279 0xf38ba8, 0xfab387, 0xf9e2af, 0xa6e3a1, 0x94e2d5, 0x89b4fa, 0xcba6f7, 0xf2cdcd,
280 ],
281 )
282 }
283
284 pub fn catppuccin_latte() -> Self {
285 Self::base16(
286 Luminance::Light,
287 [
288 0xeff1f5, 0xe6e9ef, 0xccd0da, 0xbcc0cc, 0xacb0be, 0x4c4f69, 0xdc8a78, 0x7287fd,
289 0xd20f39, 0xfe640b, 0xdf8e1d, 0x40a02b, 0x179299, 0x1e66f5, 0x8839ef, 0xdd7878,
290 ],
291 )
292 }
293
294 pub fn gruvbox_dark() -> Self {
295 Self::base16(
296 Luminance::Dark,
297 [
298 0x1d2021, 0x3c3836, 0x504945, 0x665c54, 0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
299 0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26, 0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
300 ],
301 )
302 }
303
304 pub fn gruvbox_light() -> Self {
305 Self::base16(
306 Luminance::Light,
307 [
308 0xf9f5d7, 0xebdbb2, 0xd5c4a1, 0xbdae93, 0x665c54, 0x504945, 0x3c3836, 0x282828,
309 0x9d0006, 0xaf3a03, 0xb57614, 0x79740e, 0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
310 ],
311 )
312 }
313
314 pub fn rose_pine() -> Self {
315 Self::base16(
316 Luminance::Dark,
317 [
318 0x191724, 0x1f1d2e, 0x26233a, 0x6e6a86, 0x908caa, 0xe0def4, 0xe0def4, 0x524f67,
319 0xeb6f92, 0xf6c177, 0xebbcba, 0x31748f, 0x9ccfd8, 0xc4a7e7, 0xf6c177, 0x524f67,
320 ],
321 )
322 }
323
324 pub fn rose_pine_dawn() -> Self {
325 Self::base16(
326 Luminance::Light,
327 [
328 0xfaf4ed, 0xfffaf3, 0xf2e9de, 0x9893a5, 0x797593, 0x575279, 0x575279, 0xcecacd,
329 0xb4637a, 0xea9d34, 0xd7827e, 0x286983, 0x56949f, 0x907aa9, 0xea9d34, 0xcecacd,
330 ],
331 )
332 }
333
334 pub fn everforest_dark() -> Self {
335 Self::base16(
336 Luminance::Dark,
337 [
338 0x272e33, 0x2e383c, 0x414b50, 0x859289, 0x9da9a0, 0xd3c6aa, 0xedeada, 0xfffbef,
339 0xe67e80, 0xe69875, 0xdbbc7f, 0xa7c080, 0x83c092, 0x7fbbb3, 0xd699b6, 0x9da9a0,
340 ],
341 )
342 }
343
344 pub fn everforest_light() -> Self {
345 Self::base16(
346 Luminance::Light,
347 [
348 0xfffbef, 0xf8f5e4, 0xedeada, 0x939f91, 0x829181, 0x5c6a72, 0x414b50, 0x272e33,
349 0xf85552, 0xf57d26, 0xdfa000, 0x8da101, 0x35a77c, 0x3a94c5, 0xdf69ba, 0x829181,
350 ],
351 )
352 }
353
354 pub fn ayu_dark() -> Self {
355 Self::base16(
356 Luminance::Dark,
357 [
358 0x0b0e14, 0x131721, 0x202229, 0x3e4b59, 0xbfbdb6, 0xe6e1cf, 0xece8db, 0xf2f0e7,
359 0xf07178, 0xff8f40, 0xffb454, 0xaad94c, 0x95e6cb, 0x59c2ff, 0xd2a6ff, 0xe6b450,
360 ],
361 )
362 }
363
364 pub fn ayu_light() -> Self {
365 Self::base16(
366 Luminance::Light,
367 [
368 0xf8f9fa, 0xedeff1, 0xd2d4d8, 0xa0a6ac, 0x8a9199, 0x5c6166, 0x4e5257, 0x404447,
369 0xf07171, 0xfa8d3e, 0xf2ae49, 0x6cbf49, 0x4cbf99, 0x399ee6, 0xa37acc, 0xe6ba7e,
370 ],
371 )
372 }
373
374 pub fn tokyo_night_dark() -> Self {
375 Self::base16(
376 Luminance::Dark,
377 [
378 0x1a1b26, 0x16161e, 0x2f3549, 0x444b6a, 0x787c99, 0xa9b1d6, 0xcbccd1, 0xd5d6db,
379 0xc0caf5, 0xa9b1d6, 0x0db9d7, 0x9ece6a, 0xb4f9f8, 0x2ac3de, 0xbb9af7, 0xf7768e,
380 ],
381 )
382 }
383
384 pub fn tokyo_night_light() -> Self {
385 Self::base16(
386 Luminance::Light,
387 [
388 0xd5d6db, 0xcbccd1, 0xdfe0e5, 0x9699a3, 0x4c505e, 0x343b59, 0x1a1b26, 0x1a1b26,
389 0x343b58, 0x965027, 0x166775, 0x485e30, 0x3e6968, 0x34548a, 0x5a4a78, 0x8c4351,
390 ],
391 )
392 }
393}
394
395const fn rgb(r: u8, g: u8, b: u8) -> Color {
396 Color::from_rgb(r, g, b)
397}