1use crate::map::theme::{
14 ColorMode::{Dark, Light},
15 Theme::*,
16};
17use egui::ecolor::Color32;
18use egui::{FontId, Stroke};
19use std::ops::{Div, Mul};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
28pub enum Theme {
29 SlateOcean,
31 #[default]
33 NebulaViolet,
34 TerminalGreen,
36 EmberForge,
38 SolarAmber,
40 ArticCyan,
42 CrimsonSignal,
44 MidnightIndigo,
46 CopperRose,
48 LimeCircuit,
50 CoralReef,
52 GraphiteMono,
54 PlumStatic,
56 SandstoneTrail,
58}
59
60impl Theme {
61 pub const fn colors(self, mode: ColorMode) -> ThemeColors {
64 match (self, mode) {
65 (SlateOcean, Dark) => ThemeColors {
66 node: Color32::from_rgb(0x6E, 0x93, 0xBF),
67 segment: Color32::from_rgb(0x4A, 0x5A, 0x6E),
68 selected: Color32::from_rgb(0x38, 0xBD, 0xF8),
69 alert: Color32::from_rgb(0xFF, 0x8A, 0x4C),
70 text: Color32::from_rgb(0xD7, 0xDE, 0xE6),
71 },
72 (SlateOcean, Light) => ThemeColors {
73 node: Color32::from_rgb(0x2E, 0x4C, 0x6D),
74 segment: Color32::from_rgb(0x8A, 0x99, 0xA8),
75 selected: Color32::from_rgb(0x0E, 0xA5, 0xE9),
76 alert: Color32::from_rgb(0xE0, 0x59, 0x2A),
77 text: Color32::from_rgb(0x2B, 0x33, 0x3D),
78 },
79 (NebulaViolet, Light) => ThemeColors {
80 node: Color32::from_rgb(0x5B, 0x3E, 0x96),
81 segment: Color32::from_rgb(0xB3, 0xA4, 0xD6),
82 selected: Color32::from_rgb(0x16, 0xB8, 0xA6),
83 alert: Color32::from_rgb(0xE6, 0x33, 0x7A),
84 text: Color32::from_rgb(0x37, 0x2F, 0x45),
85 },
86 (NebulaViolet, Dark) => ThemeColors {
87 node: Color32::from_rgb(0xA7, 0x8B, 0xFA),
88 segment: Color32::from_rgb(0x5C, 0x4A, 0x80),
89 selected: Color32::from_rgb(0x2D, 0xD4, 0xBF),
90 alert: Color32::from_rgb(0xFF, 0x5F, 0xA3),
91 text: Color32::from_rgb(0xE4, 0xDC, 0xF2),
92 },
93 (TerminalGreen, Light) => ThemeColors {
94 node: Color32::from_rgb(0x1F, 0x7A, 0x3D),
95 segment: Color32::from_rgb(0x9B, 0xB8, 0x9E),
96 selected: Color32::from_rgb(0x25, 0x63, 0xEB),
97 alert: Color32::from_rgb(0xD6, 0xA4, 0x29),
98 text: Color32::from_rgb(0x2A, 0x33, 0x2C),
99 },
100 (TerminalGreen, Dark) => ThemeColors {
101 node: Color32::from_rgb(0x4A, 0xDE, 0x80),
102 segment: Color32::from_rgb(0x3A, 0x52, 0x40),
103 selected: Color32::from_rgb(0x60, 0xA5, 0xFA),
104 alert: Color32::from_rgb(0xFF, 0xC9, 0x4D),
105 text: Color32::from_rgb(0xD7, 0xE6, 0xDA),
106 },
107 (EmberForge, Light) => ThemeColors {
108 node: Color32::from_rgb(0x8C, 0x4A, 0x1F),
109 segment: Color32::from_rgb(0xC9, 0xA9, 0x8C),
110 selected: Color32::from_rgb(0xC4, 0x25, 0x8C),
111 alert: Color32::from_rgb(0x2E, 0x86, 0xAB),
112 text: Color32::from_rgb(0x36, 0x2E, 0x28),
113 },
114 (EmberForge, Dark) => ThemeColors {
115 node: Color32::from_rgb(0xD9, 0x7F, 0x3D),
116 segment: Color32::from_rgb(0x5A, 0x46, 0x32),
117 selected: Color32::from_rgb(0xF4, 0x72, 0xB6),
118 alert: Color32::from_rgb(0x4F, 0xC3, 0xE8),
119 text: Color32::from_rgb(0xED, 0xE0, 0xD3),
120 },
121 (SolarAmber, Dark) => ThemeColors {
122 node: Color32::from_rgb(0x8A, 0x6D, 0x1E),
123 segment: Color32::from_rgb(0xD8, 0xC4, 0x8F),
124 selected: Color32::from_rgb(0x2D, 0x6E, 0x5E),
125 alert: Color32::from_rgb(0xC1, 0x44, 0x2A),
126 text: Color32::from_rgb(0x36, 0x2E, 0x1C),
127 },
128 (SolarAmber, Light) => ThemeColors {
129 node: Color32::from_rgb(0xF0, 0xC2, 0x4C),
130 segment: Color32::from_rgb(0x5A, 0x4E, 0x2E),
131 selected: Color32::from_rgb(0x4F, 0xBF, 0x9E),
132 alert: Color32::from_rgb(0xE8, 0x65, 0x4A),
133 text: Color32::from_rgb(0xEF, 0xE4, 0xC4),
134 },
135 (ArticCyan, Dark) => ThemeColors {
136 node: Color32::from_rgb(0x12, 0x70, 0x8A),
137 segment: Color32::from_rgb(0x9A, 0xC6, 0xD1),
138 selected: Color32::from_rgb(0xF5, 0xA5, 0x24),
139 alert: Color32::from_rgb(0xE0, 0x52, 0x7A),
140 text: Color32::from_rgb(0x1F, 0x2E, 0x30),
141 },
142 (ArticCyan, Light) => ThemeColors {
143 node: Color32::from_rgb(0x4F, 0xE0, 0xFF),
144 segment: Color32::from_rgb(0x37, 0x5E, 0x68),
145 selected: Color32::from_rgb(0xFB, 0xBF, 0x24),
146 alert: Color32::from_rgb(0xFF, 0x6B, 0x95),
147 text: Color32::from_rgb(0xD3, 0xEA, 0xEF),
148 },
149 (CrimsonSignal, Dark) => ThemeColors {
150 node: Color32::from_rgb(0xE3, 0x5B, 0x6B),
151 segment: Color32::from_rgb(0x5C, 0x45, 0x48),
152 selected: Color32::from_rgb(0x58, 0xA6, 0xFF),
153 alert: Color32::from_rgb(0xFF, 0xC4, 0x59),
154 text: Color32::from_rgb(0xE8, 0xD6, 0xD8),
155 },
156 (CrimsonSignal, Light) => ThemeColors {
157 node: Color32::from_rgb(0x7A, 0x1F, 0x2B),
158 segment: Color32::from_rgb(0xB9, 0xA8, 0xA8),
159 selected: Color32::from_rgb(0x1F, 0x6F, 0xEB),
160 alert: Color32::from_rgb(0xE8, 0xA6, 0x28),
161 text: Color32::from_rgb(0x33, 0x26, 0x28),
162 },
163
164 (MidnightIndigo, Dark) => ThemeColors {
165 node: Color32::from_rgb(0x7B, 0x82, 0xE0),
166 segment: Color32::from_rgb(0x3A, 0x3D, 0x66),
167 selected: Color32::from_rgb(0x2D, 0xD4, 0xC8),
168 alert: Color32::from_rgb(0xFF, 0xD1, 0x66),
169 text: Color32::from_rgb(0xD8, 0xDA, 0xF0),
170 },
171 (MidnightIndigo, Light) => ThemeColors {
172 node: Color32::from_rgb(0x2B, 0x2F, 0x77),
173 segment: Color32::from_rgb(0xA6, 0xA9, 0xC9),
174 selected: Color32::from_rgb(0x00, 0xB8, 0xA9),
175 alert: Color32::from_rgb(0xE0, 0xA4, 0x00),
176 text: Color32::from_rgb(0x26, 0x29, 0x40),
177 },
178
179 (CopperRose, Dark) => ThemeColors {
180 node: Color32::from_rgb(0xE0, 0x8B, 0x6F),
181 segment: Color32::from_rgb(0x5E, 0x45, 0x3D),
182 selected: Color32::from_rgb(0x4F, 0xC3, 0xAE),
183 alert: Color32::from_rgb(0xFF, 0xC6, 0x5C),
184 text: Color32::from_rgb(0xEF, 0xDA, 0xD0),
185 },
186 (CopperRose, Light) => ThemeColors {
187 node: Color32::from_rgb(0x9C, 0x4A, 0x3C),
188 segment: Color32::from_rgb(0xD9, 0xB8, 0xAE),
189 selected: Color32::from_rgb(0x2F, 0x7A, 0x6B),
190 alert: Color32::from_rgb(0xE0, 0xA2, 0x3C),
191 text: Color32::from_rgb(0x3D, 0x2E, 0x29),
192 },
193
194 (LimeCircuit, Dark) => ThemeColors {
195 node: Color32::from_rgb(0xA8, 0xE0, 0x5F),
196 segment: Color32::from_rgb(0x44, 0x52, 0x30),
197 selected: Color32::from_rgb(0xA7, 0x8B, 0xFA),
198 alert: Color32::from_rgb(0xFF, 0x85, 0x52),
199 text: Color32::from_rgb(0xDC, 0xEA, 0xC0),
200 },
201 (LimeCircuit, Light) => ThemeColors {
202 node: Color32::from_rgb(0x4D, 0x7A, 0x1F),
203 segment: Color32::from_rgb(0xB9, 0xC7, 0x9A),
204 selected: Color32::from_rgb(0x7B, 0x3F, 0xE4),
205 alert: Color32::from_rgb(0xE8, 0x5D, 0x2E),
206 text: Color32::from_rgb(0x2E, 0x33, 0x20),
207 },
208
209 (CoralReef, Dark) => ThemeColors {
210 node: Color32::from_rgb(0xFF, 0x8B, 0x73),
211 segment: Color32::from_rgb(0x38, 0x65, 0x60),
212 selected: Color32::from_rgb(0x5C, 0xA8, 0xE0),
213 alert: Color32::from_rgb(0xFF, 0xC1, 0x5E),
214 text: Color32::from_rgb(0xD6, 0xED, 0xE8),
215 },
216 (CoralReef, Light) => ThemeColors {
217 node: Color32::from_rgb(0xD6, 0x5A, 0x45),
218 segment: Color32::from_rgb(0xA8, 0xD4, 0xCE),
219 selected: Color32::from_rgb(0x1D, 0x5C, 0x9E),
220 alert: Color32::from_rgb(0xF2, 0xA9, 0x3C),
221 text: Color32::from_rgb(0x33, 0x40, 0x3E),
222 },
223
224 (GraphiteMono, Dark) => ThemeColors {
225 node: Color32::from_rgb(0xD6, 0xD6, 0xD2),
226 segment: Color32::from_rgb(0x4A, 0x4A, 0x46),
227 selected: Color32::from_rgb(0x4F, 0xB3, 0xF5),
228 alert: Color32::from_rgb(0xFF, 0x6B, 0x5C),
229 text: Color32::from_rgb(0xE8, 0xE8, 0xE4),
230 },
231 (GraphiteMono, Light) => ThemeColors {
232 node: Color32::from_rgb(0x3A, 0x3A, 0x3A),
233 segment: Color32::from_rgb(0xB8, 0xB8, 0xB4),
234 selected: Color32::from_rgb(0x1F, 0x8F, 0xE0),
235 alert: Color32::from_rgb(0xE0, 0x48, 0x3A),
236 text: Color32::from_rgb(0x23, 0x23, 0x23),
237 },
238
239 (PlumStatic, Dark) => ThemeColors {
240 node: Color32::from_rgb(0xC9, 0x94, 0xBB),
241 segment: Color32::from_rgb(0x4A, 0x3A, 0x45),
242 selected: Color32::from_rgb(0x52, 0xC9, 0x9A),
243 alert: Color32::from_rgb(0xFF, 0xA0, 0x5C),
244 text: Color32::from_rgb(0xEB, 0xD9, 0xE5),
245 },
246 (PlumStatic, Light) => ThemeColors {
247 node: Color32::from_rgb(0x6B, 0x3B, 0x5E),
248 segment: Color32::from_rgb(0xC7, 0xAE, 0xC0),
249 selected: Color32::from_rgb(0x2E, 0x8B, 0x6E),
250 alert: Color32::from_rgb(0xE0, 0x79, 0x3D),
251 text: Color32::from_rgb(0x36, 0x2B, 0x33),
252 },
253
254 (SandstoneTrail, Dark) => ThemeColors {
255 node: Color32::from_rgb(0xC9, 0xAD, 0x72),
256 segment: Color32::from_rgb(0x4E, 0x45, 0x30),
257 selected: Color32::from_rgb(0x4F, 0xA8, 0xCC),
258 alert: Color32::from_rgb(0xF0, 0x70, 0x8A),
259 text: Color32::from_rgb(0xE6, 0xD9, 0xB8),
260 },
261 (SandstoneTrail, Light) => ThemeColors {
262 node: Color32::from_rgb(0x6B, 0x5A, 0x3A),
263 segment: Color32::from_rgb(0xDC, 0xCB, 0xA0),
264 selected: Color32::from_rgb(0x2A, 0x6E, 0x8C),
265 alert: Color32::from_rgb(0xD1, 0x49, 0x5B),
266 text: Color32::from_rgb(0x3A, 0x31, 0x21),
267 },
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq)]
276pub struct ThemeColors {
277 pub node: Color32,
279 pub segment: Color32,
281 pub selected: Color32,
283 pub alert: Color32,
285 pub text: Color32,
287}
288
289#[derive(Clone, Copy, Debug, PartialEq, Eq)]
291pub enum ColorMode {
292 Light,
294 Dark,
296}
297
298pub trait MapTheme {
338 fn colors(&self, mode: ColorMode) -> ThemeColors;
340}
341
342impl MapTheme for Theme {
343 fn colors(&self, mode: ColorMode) -> ThemeColors {
344 Theme::colors(*self, mode)
345 }
346}
347
348#[derive(Clone, Debug)]
363pub struct Style {
364 pub border: Option<Stroke>,
366 pub line: Option<Stroke>,
368 pub fill_color: Color32,
370 pub text_color: Color32,
372 pub font: Option<FontId>,
374 pub background_color: Color32,
376 pub alert_color: Color32,
378}
379
380impl Style {
381 pub fn new() -> Self {
383 Style {
384 border: None,
385 line: None,
386 fill_color: Color32::TRANSPARENT,
387 text_color: Color32::TRANSPARENT,
388 font: None,
389 background_color: Color32::TRANSPARENT,
390 alert_color: Color32::TRANSPARENT,
391 }
392 }
393}
394
395impl Default for Style {
396 fn default() -> Self {
397 Style::new()
398 }
399}
400
401impl Style {
402 fn scaled(mut self, factor: f32) -> Self {
405 if let Some(border) = self.border.as_mut() {
406 border.width *= factor;
407 }
408 if let Some(line) = self.line.as_mut() {
409 line.width *= factor;
410 }
411 if let Some(font) = self.font.as_mut() {
412 font.size *= factor;
413 }
414 self
415 }
416}
417
418impl Mul<i64> for Style {
419 type Output = Self;
420
421 fn mul(self, rhs: i64) -> Self::Output {
422 self.scaled(rhs as f32)
423 }
424}
425
426impl Mul<i32> for Style {
427 type Output = Self;
428
429 fn mul(self, rhs: i32) -> Self::Output {
430 self.scaled(rhs as f32)
431 }
432}
433
434impl Mul<f32> for Style {
435 type Output = Self;
436
437 fn mul(self, rhs: f32) -> Self::Output {
438 self.scaled(rhs)
439 }
440}
441
442impl Mul<f64> for Style {
443 type Output = Self;
444
445 fn mul(self, rhs: f64) -> Self::Output {
446 self.scaled(rhs as f32)
447 }
448}
449
450impl Div<i64> for Style {
451 type Output = Self;
452
453 fn div(self, rhs: i64) -> Self::Output {
454 self.scaled(1.0 / rhs as f32)
455 }
456}
457
458impl Div<i32> for Style {
459 type Output = Self;
460
461 fn div(self, rhs: i32) -> Self::Output {
462 self.scaled(1.0 / rhs as f32)
463 }
464}
465
466impl Div<f32> for Style {
467 type Output = Self;
468
469 fn div(self, rhs: f32) -> Self::Output {
470 self.scaled(1.0 / rhs)
471 }
472}
473
474impl Div<f64> for Style {
475 type Output = Self;
476
477 fn div(self, rhs: f64) -> Self::Output {
478 self.scaled(1.0 / rhs as f32)
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 const ALL_THEMES: [Theme; 14] = [
487 Theme::SlateOcean,
488 Theme::NebulaViolet,
489 Theme::TerminalGreen,
490 Theme::EmberForge,
491 Theme::SolarAmber,
492 Theme::ArticCyan,
493 Theme::CrimsonSignal,
494 Theme::MidnightIndigo,
495 Theme::CopperRose,
496 Theme::LimeCircuit,
497 Theme::CoralReef,
498 Theme::GraphiteMono,
499 Theme::PlumStatic,
500 Theme::SandstoneTrail,
501 ];
502
503 #[test]
504 fn every_theme_has_distinct_light_and_dark_palettes() {
505 for theme in ALL_THEMES {
512 let light = theme.colors(ColorMode::Light);
513 let dark = theme.colors(ColorMode::Dark);
514 assert_ne!(
515 light, dark,
516 "{theme:?}'s Light and Dark palettes must not be identical"
517 );
518 }
519 }
520
521 #[test]
522 fn solar_amber_light_matches_its_defined_palette() {
523 assert_eq!(
529 Theme::SolarAmber.colors(ColorMode::Light),
530 ThemeColors {
531 node: Color32::from_rgb(0xF0, 0xC2, 0x4C),
532 segment: Color32::from_rgb(0x5A, 0x4E, 0x2E),
533 selected: Color32::from_rgb(0x4F, 0xBF, 0x9E),
534 alert: Color32::from_rgb(0xE8, 0x65, 0x4A),
535 text: Color32::from_rgb(0xEF, 0xE4, 0xC4),
536 }
537 );
538 }
539
540 #[test]
541 fn theme_implements_map_theme_by_delegating_to_colors() {
542 let theme: &dyn MapTheme = &Theme::TerminalGreen;
543 assert_eq!(
544 theme.colors(ColorMode::Dark),
545 Theme::TerminalGreen.colors(ColorMode::Dark)
546 );
547 assert_eq!(
548 theme.colors(ColorMode::Light),
549 Theme::TerminalGreen.colors(ColorMode::Light)
550 );
551 }
552}