1use blockworx_paint::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)]
145pub struct Palette {
146 colors: [Color; 16],
147 is_dark: bool,
150}
151
152impl Palette {
153 pub fn get(&self, base: Base) -> Color {
155 self.colors[base as usize]
156 }
157
158 pub fn resolve(&self, swatch: Swatch) -> Color {
161 match swatch.base {
162 Some(base) => self.get(base).gamma_multiply(swatch.alpha),
163 None => Color::TRANSPARENT,
164 }
165 }
166
167 pub fn tokyo_night_moon() -> Self {
171 Self {
172 is_dark: true,
173 colors: [
174 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), ],
191 }
192 }
193
194 pub fn is_dark(&self) -> bool {
196 self.is_dark
197 }
198
199 pub fn toned(&self, saturation: Saturation) -> Self {
202 Self {
203 colors: self.colors.map(|color| saturation.applied(color)),
204 is_dark: self.is_dark,
205 }
206 }
207}
208
209#[derive(Clone, Copy, PartialEq, Eq, Debug)]
216pub enum Saturation {
217 Full,
218 Drained,
219}
220
221impl Saturation {
222 const REMAINING: f32 = 0.35;
226
227 pub fn applied(self, color: Color) -> Color {
228 match self {
229 Saturation::Full => color,
230 Saturation::Drained => {
231 let grey = luma(color);
232 let mix = |channel: u8| {
233 let kept = f32::from(channel) * Self::REMAINING;
234 let lost = f32::from(grey) * (1.0 - Self::REMAINING);
235 (kept + lost) as u8
236 };
237 Color::from_rgb(mix(color.r()), mix(color.g()), mix(color.b()))
238 }
239 }
240 }
241}
242
243fn luma(color: Color) -> u8 {
246 (0.299 * f32::from(color.r()) + 0.587 * f32::from(color.g()) + 0.114 * f32::from(color.b()))
247 as u8
248}
249
250impl Palette {
257 const fn base16(luminance: Luminance, hexes: [u32; 16]) -> Self {
259 let mut colors = [Color::TRANSPARENT; 16];
260 let mut i = 0;
261 while i < 16 {
262 let v = hexes[i];
263 colors[i] = Color::from_rgb((v >> 16) as u8, (v >> 8) as u8, v as u8);
264 i += 1;
265 }
266 Self {
267 colors,
268 is_dark: luminance.is_dark(),
269 }
270 }
271
272 pub fn catppuccin_mocha() -> Self {
273 Self::base16(
274 Luminance::Dark,
275 [
276 0x1e1e2e, 0x181825, 0x313244, 0x45475a, 0x585b70, 0xcdd6f4, 0xf5e0dc, 0xb4befe,
277 0xf38ba8, 0xfab387, 0xf9e2af, 0xa6e3a1, 0x94e2d5, 0x89b4fa, 0xcba6f7, 0xf2cdcd,
278 ],
279 )
280 }
281
282 pub fn catppuccin_latte() -> Self {
283 Self::base16(
284 Luminance::Light,
285 [
286 0xeff1f5, 0xe6e9ef, 0xccd0da, 0xbcc0cc, 0xacb0be, 0x4c4f69, 0xdc8a78, 0x7287fd,
287 0xd20f39, 0xfe640b, 0xdf8e1d, 0x40a02b, 0x179299, 0x1e66f5, 0x8839ef, 0xdd7878,
288 ],
289 )
290 }
291
292 pub fn gruvbox_dark() -> Self {
293 Self::base16(
294 Luminance::Dark,
295 [
296 0x1d2021, 0x3c3836, 0x504945, 0x665c54, 0xbdae93, 0xd5c4a1, 0xebdbb2, 0xfbf1c7,
297 0xfb4934, 0xfe8019, 0xfabd2f, 0xb8bb26, 0x8ec07c, 0x83a598, 0xd3869b, 0xd65d0e,
298 ],
299 )
300 }
301
302 pub fn gruvbox_light() -> Self {
303 Self::base16(
304 Luminance::Light,
305 [
306 0xf9f5d7, 0xebdbb2, 0xd5c4a1, 0xbdae93, 0x665c54, 0x504945, 0x3c3836, 0x282828,
307 0x9d0006, 0xaf3a03, 0xb57614, 0x79740e, 0x427b58, 0x076678, 0x8f3f71, 0xd65d0e,
308 ],
309 )
310 }
311
312 pub fn rose_pine() -> Self {
313 Self::base16(
314 Luminance::Dark,
315 [
316 0x191724, 0x1f1d2e, 0x26233a, 0x6e6a86, 0x908caa, 0xe0def4, 0xe0def4, 0x524f67,
317 0xeb6f92, 0xf6c177, 0xebbcba, 0x31748f, 0x9ccfd8, 0xc4a7e7, 0xf6c177, 0x524f67,
318 ],
319 )
320 }
321
322 pub fn rose_pine_dawn() -> Self {
323 Self::base16(
324 Luminance::Light,
325 [
326 0xfaf4ed, 0xfffaf3, 0xf2e9de, 0x9893a5, 0x797593, 0x575279, 0x575279, 0xcecacd,
327 0xb4637a, 0xea9d34, 0xd7827e, 0x286983, 0x56949f, 0x907aa9, 0xea9d34, 0xcecacd,
328 ],
329 )
330 }
331
332 pub fn everforest_dark() -> Self {
333 Self::base16(
334 Luminance::Dark,
335 [
336 0x272e33, 0x2e383c, 0x414b50, 0x859289, 0x9da9a0, 0xd3c6aa, 0xedeada, 0xfffbef,
337 0xe67e80, 0xe69875, 0xdbbc7f, 0xa7c080, 0x83c092, 0x7fbbb3, 0xd699b6, 0x9da9a0,
338 ],
339 )
340 }
341
342 pub fn everforest_light() -> Self {
343 Self::base16(
344 Luminance::Light,
345 [
346 0xfffbef, 0xf8f5e4, 0xedeada, 0x939f91, 0x829181, 0x5c6a72, 0x414b50, 0x272e33,
347 0xf85552, 0xf57d26, 0xdfa000, 0x8da101, 0x35a77c, 0x3a94c5, 0xdf69ba, 0x829181,
348 ],
349 )
350 }
351
352 pub fn ayu_dark() -> Self {
353 Self::base16(
354 Luminance::Dark,
355 [
356 0x0b0e14, 0x131721, 0x202229, 0x3e4b59, 0xbfbdb6, 0xe6e1cf, 0xece8db, 0xf2f0e7,
357 0xf07178, 0xff8f40, 0xffb454, 0xaad94c, 0x95e6cb, 0x59c2ff, 0xd2a6ff, 0xe6b450,
358 ],
359 )
360 }
361
362 pub fn ayu_light() -> Self {
363 Self::base16(
364 Luminance::Light,
365 [
366 0xf8f9fa, 0xedeff1, 0xd2d4d8, 0xa0a6ac, 0x8a9199, 0x5c6166, 0x4e5257, 0x404447,
367 0xf07171, 0xfa8d3e, 0xf2ae49, 0x6cbf49, 0x4cbf99, 0x399ee6, 0xa37acc, 0xe6ba7e,
368 ],
369 )
370 }
371
372 pub fn tokyo_night_dark() -> Self {
373 Self::base16(
374 Luminance::Dark,
375 [
376 0x1a1b26, 0x16161e, 0x2f3549, 0x444b6a, 0x787c99, 0xa9b1d6, 0xcbccd1, 0xd5d6db,
377 0xc0caf5, 0xa9b1d6, 0x0db9d7, 0x9ece6a, 0xb4f9f8, 0x2ac3de, 0xbb9af7, 0xf7768e,
378 ],
379 )
380 }
381
382 pub fn tokyo_night_light() -> Self {
383 Self::base16(
384 Luminance::Light,
385 [
386 0xd5d6db, 0xcbccd1, 0xdfe0e5, 0x9699a3, 0x4c505e, 0x343b59, 0x1a1b26, 0x1a1b26,
387 0x343b58, 0x965027, 0x166775, 0x485e30, 0x3e6968, 0x34548a, 0x5a4a78, 0x8c4351,
388 ],
389 )
390 }
391}
392
393impl Palette {
394 pub fn egui_visuals(&self) -> egui::Visuals {
400 use crate::canvas::egui_compat::IntoEgui as _;
401 use egui::Stroke;
402 let c = |b| self.get(b).egui();
403 let mut v = if self.is_dark {
404 egui::Visuals::dark()
405 } else {
406 egui::Visuals::light()
407 };
408
409 v.override_text_color = Some(c(Base::B05));
410 v.panel_fill = c(Base::B00);
411 v.window_fill = c(Base::B01);
412 v.window_stroke = Stroke::new(1.0, c(Base::B02));
413 v.extreme_bg_color = c(Base::B00); v.faint_bg_color = c(Base::B01);
415 v.code_bg_color = c(Base::B01);
416 v.hyperlink_color = c(Base::B0D);
417 v.selection.bg_fill = c(Base::B0D).gamma_multiply(0.4);
418 v.selection.stroke = Stroke::new(1.0, c(Base::B05));
419
420 let w = &mut v.widgets;
421 w.noninteractive.bg_fill = c(Base::B01);
422 w.noninteractive.weak_bg_fill = c(Base::B01);
423 w.noninteractive.bg_stroke = Stroke::new(1.0, c(Base::B02));
424 w.noninteractive.fg_stroke = Stroke::new(1.0, c(Base::B05));
425
426 w.inactive.bg_fill = c(Base::B02);
427 w.inactive.weak_bg_fill = c(Base::B01);
428 w.inactive.bg_stroke = Stroke::new(1.0, c(Base::B02));
429 w.inactive.fg_stroke = Stroke::new(1.0, c(Base::B05));
430
431 w.hovered.bg_fill = c(Base::B03);
432 w.hovered.weak_bg_fill = c(Base::B02);
433 w.hovered.bg_stroke = Stroke::new(1.0, c(Base::B0D));
434 w.hovered.fg_stroke = Stroke::new(1.5, c(Base::B06));
435
436 w.active.bg_fill = c(Base::B0D);
437 w.active.weak_bg_fill = c(Base::B03);
438 w.active.bg_stroke = Stroke::new(1.0, c(Base::B0D));
439 w.active.fg_stroke = Stroke::new(2.0, c(Base::B00));
440
441 w.open.bg_fill = c(Base::B02);
442 w.open.weak_bg_fill = c(Base::B01);
443 w.open.bg_stroke = Stroke::new(1.0, c(Base::B02));
444 w.open.fg_stroke = Stroke::new(1.0, c(Base::B05));
445
446 v
447 }
448}
449
450const fn rgb(r: u8, g: u8, b: u8) -> Color {
451 Color::from_rgb(r, g, b)
452}