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