1use ratatui::style::Color;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Default)]
10pub struct SystemMetrics {
11 pub cpu_usage: f32,
13 pub memory_usage: f32,
15 pub network_rx_rate: f32,
17 pub network_tx_rate: f32,
19 pub disk_read_rate: f32,
21 pub disk_write_rate: f32,
23 pub battery_level: Option<f32>,
25 pub battery_charging: Option<bool>,
27}
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
31pub enum TimeFormat {
32 #[default]
33 TwentyFourHour,
34 TwelveHour,
35}
36
37impl TimeFormat {
38 pub fn toggle(&self) -> Self {
40 match self {
41 TimeFormat::TwentyFourHour => TimeFormat::TwelveHour,
42 TimeFormat::TwelveHour => TimeFormat::TwentyFourHour,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
49pub enum AnimationStyle {
50 #[default]
51 None,
52 Shifting,
53 Pulsing,
54 Wave,
55 Reactive,
56}
57
58const ALL_ANIMATION_STYLES: &[AnimationStyle] = &[
60 AnimationStyle::None,
61 AnimationStyle::Shifting,
62 AnimationStyle::Pulsing,
63 AnimationStyle::Wave,
64 AnimationStyle::Reactive,
65];
66
67impl AnimationStyle {
68 pub fn next(&self) -> Self {
70 let current_idx = ALL_ANIMATION_STYLES
71 .iter()
72 .position(|s| s == self)
73 .unwrap_or(0);
74 let next_idx = (current_idx + 1) % ALL_ANIMATION_STYLES.len();
75 ALL_ANIMATION_STYLES[next_idx]
76 }
77
78 pub fn prev(&self) -> Self {
80 let current_idx = ALL_ANIMATION_STYLES
81 .iter()
82 .position(|s| s == self)
83 .unwrap_or(0);
84 let prev_idx = if current_idx == 0 {
85 ALL_ANIMATION_STYLES.len() - 1
86 } else {
87 current_idx - 1
88 };
89 ALL_ANIMATION_STYLES[prev_idx]
90 }
91
92 pub fn display_name(self) -> &'static str {
94 match self {
95 AnimationStyle::None => "None",
96 AnimationStyle::Shifting => "Shifting",
97 AnimationStyle::Pulsing => "Pulsing",
98 AnimationStyle::Wave => "Wave",
99 AnimationStyle::Reactive => "Reactive",
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
106pub enum BackgroundStyle {
107 #[default]
108 None,
109 Starfield,
110 MatrixRain,
111 GradientWave,
112 Snowfall,
114 Frost,
115 Aurora,
116 Sunny,
118 Rainy,
119 Stormy,
120 Windy,
121 Cloudy,
122 Foggy,
123 SystemPulse,
125 ResourceWave,
126 DataFlow,
127 HeatMap,
128}
129
130const ALL_BACKGROUND_STYLES: &[BackgroundStyle] = &[
132 BackgroundStyle::None,
133 BackgroundStyle::Starfield,
134 BackgroundStyle::MatrixRain,
135 BackgroundStyle::GradientWave,
136 BackgroundStyle::Snowfall,
137 BackgroundStyle::Frost,
138 BackgroundStyle::Aurora,
139 BackgroundStyle::Sunny,
140 BackgroundStyle::Rainy,
141 BackgroundStyle::Stormy,
142 BackgroundStyle::Windy,
143 BackgroundStyle::Cloudy,
144 BackgroundStyle::Foggy,
145 BackgroundStyle::SystemPulse,
146 BackgroundStyle::ResourceWave,
147 BackgroundStyle::DataFlow,
148 BackgroundStyle::HeatMap,
149];
150
151impl BackgroundStyle {
152 pub fn next(&self) -> Self {
154 let current_idx = ALL_BACKGROUND_STYLES
155 .iter()
156 .position(|s| s == self)
157 .unwrap_or(0);
158 let next_idx = (current_idx + 1) % ALL_BACKGROUND_STYLES.len();
159 ALL_BACKGROUND_STYLES[next_idx]
160 }
161
162 pub fn prev(&self) -> Self {
164 let current_idx = ALL_BACKGROUND_STYLES
165 .iter()
166 .position(|s| s == self)
167 .unwrap_or(0);
168 let prev_idx = if current_idx == 0 {
169 ALL_BACKGROUND_STYLES.len() - 1
170 } else {
171 current_idx - 1
172 };
173 ALL_BACKGROUND_STYLES[prev_idx]
174 }
175
176 pub fn display_name(self) -> &'static str {
178 match self {
179 BackgroundStyle::None => "None",
180 BackgroundStyle::Starfield => "Starfield",
181 BackgroundStyle::MatrixRain => "Matrix",
182 BackgroundStyle::GradientWave => "Gradient",
183 BackgroundStyle::Snowfall => "Snowfall",
184 BackgroundStyle::Frost => "Frost",
185 BackgroundStyle::Aurora => "Aurora",
186 BackgroundStyle::Sunny => "Sunny",
187 BackgroundStyle::Rainy => "Rainy",
188 BackgroundStyle::Stormy => "Stormy",
189 BackgroundStyle::Windy => "Windy",
190 BackgroundStyle::Cloudy => "Cloudy",
191 BackgroundStyle::Foggy => "Foggy",
192 BackgroundStyle::SystemPulse => "Sys Pulse",
193 BackgroundStyle::ResourceWave => "Resource",
194 BackgroundStyle::DataFlow => "Data Flow",
195 BackgroundStyle::HeatMap => "Heat Map",
196 }
197 }
198
199 pub fn is_reactive(self) -> bool {
201 matches!(
202 self,
203 BackgroundStyle::SystemPulse
204 | BackgroundStyle::ResourceWave
205 | BackgroundStyle::DataFlow
206 | BackgroundStyle::HeatMap
207 )
208 }
209}
210
211#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
213pub enum AnimationSpeed {
214 Slow,
215 #[default]
216 Medium,
217 Fast,
218}
219
220const ALL_ANIMATION_SPEEDS: &[AnimationSpeed] = &[
222 AnimationSpeed::Slow,
223 AnimationSpeed::Medium,
224 AnimationSpeed::Fast,
225];
226
227impl AnimationSpeed {
228 pub fn next(&self) -> Self {
230 let current_idx = ALL_ANIMATION_SPEEDS
231 .iter()
232 .position(|s| s == self)
233 .unwrap_or(0);
234 let next_idx = (current_idx + 1) % ALL_ANIMATION_SPEEDS.len();
235 ALL_ANIMATION_SPEEDS[next_idx]
236 }
237
238 pub fn prev(&self) -> Self {
240 let current_idx = ALL_ANIMATION_SPEEDS
241 .iter()
242 .position(|s| s == self)
243 .unwrap_or(0);
244 let prev_idx = if current_idx == 0 {
245 ALL_ANIMATION_SPEEDS.len() - 1
246 } else {
247 current_idx - 1
248 };
249 ALL_ANIMATION_SPEEDS[prev_idx]
250 }
251
252 pub fn display_name(self) -> &'static str {
254 match self {
255 AnimationSpeed::Slow => "Slow",
256 AnimationSpeed::Medium => "Medium",
257 AnimationSpeed::Fast => "Fast",
258 }
259 }
260
261 pub fn shift_cycle_ms(self) -> u64 {
263 match self {
264 AnimationSpeed::Slow => 30_000,
265 AnimationSpeed::Medium => 15_000,
266 AnimationSpeed::Fast => 5_000,
267 }
268 }
269
270 pub fn pulse_period_ms(self) -> u64 {
272 match self {
273 AnimationSpeed::Slow => 3_000,
274 AnimationSpeed::Medium => 1_500,
275 AnimationSpeed::Fast => 750,
276 }
277 }
278
279 pub fn wave_period_ms(self) -> u64 {
281 match self {
282 AnimationSpeed::Slow => 4_000,
283 AnimationSpeed::Medium => 2_000,
284 AnimationSpeed::Fast => 1_000,
285 }
286 }
287
288 pub fn flash_decay_ms(self) -> u64 {
290 match self {
291 AnimationSpeed::Slow => 800,
292 AnimationSpeed::Medium => 400,
293 AnimationSpeed::Fast => 200,
294 }
295 }
296
297 pub fn star_twinkle_period_ms(self) -> u64 {
299 match self {
300 AnimationSpeed::Slow => 500,
301 AnimationSpeed::Medium => 300,
302 AnimationSpeed::Fast => 150,
303 }
304 }
305
306 pub fn matrix_fall_speed(self) -> f32 {
308 match self {
309 AnimationSpeed::Slow => 0.5,
310 AnimationSpeed::Medium => 1.0,
311 AnimationSpeed::Fast => 2.0,
312 }
313 }
314
315 pub fn gradient_scroll_period_ms(self) -> u64 {
317 match self {
318 AnimationSpeed::Slow => 5000,
319 AnimationSpeed::Medium => 3000,
320 AnimationSpeed::Fast => 1500,
321 }
322 }
323
324 pub fn snow_fall_speed(self) -> f32 {
326 match self {
327 AnimationSpeed::Slow => 0.3,
328 AnimationSpeed::Medium => 0.6,
329 AnimationSpeed::Fast => 1.0,
330 }
331 }
332
333 pub fn frost_growth_period_ms(self) -> u64 {
335 match self {
336 AnimationSpeed::Slow => 8000,
337 AnimationSpeed::Medium => 5000,
338 AnimationSpeed::Fast => 3000,
339 }
340 }
341
342 pub fn aurora_wave_period_ms(self) -> u64 {
344 match self {
345 AnimationSpeed::Slow => 6000,
346 AnimationSpeed::Medium => 4000,
347 AnimationSpeed::Fast => 2000,
348 }
349 }
350
351 pub fn rain_fall_speed(self) -> f32 {
355 match self {
356 AnimationSpeed::Slow => 0.8,
357 AnimationSpeed::Medium => 1.5,
358 AnimationSpeed::Fast => 2.5,
359 }
360 }
361
362 pub fn lightning_interval_ms(self) -> (u64, u64) {
364 match self {
365 AnimationSpeed::Slow => (6000, 12000),
366 AnimationSpeed::Medium => (4000, 8000),
367 AnimationSpeed::Fast => (2000, 5000),
368 }
369 }
370
371 pub fn wind_streak_speed(self) -> f32 {
373 match self {
374 AnimationSpeed::Slow => 0.5,
375 AnimationSpeed::Medium => 1.0,
376 AnimationSpeed::Fast => 2.0,
377 }
378 }
379
380 pub fn cloud_drift_period_ms(self) -> u64 {
382 match self {
383 AnimationSpeed::Slow => 8000,
384 AnimationSpeed::Medium => 5000,
385 AnimationSpeed::Fast => 3000,
386 }
387 }
388
389 pub fn sun_shimmer_period_ms(self) -> u64 {
391 match self {
392 AnimationSpeed::Slow => 2000,
393 AnimationSpeed::Medium => 1200,
394 AnimationSpeed::Fast => 600,
395 }
396 }
397
398 pub fn fog_pulse_period_ms(self) -> u64 {
400 match self {
401 AnimationSpeed::Slow => 6000,
402 AnimationSpeed::Medium => 4000,
403 AnimationSpeed::Fast => 2500,
404 }
405 }
406}
407
408#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
410pub enum ColorTheme {
411 #[default]
412 Cyan,
413 Green,
414 White,
415 Magenta,
416 Yellow,
417 Red,
418 Blue,
419 Rainbow,
421 RainbowVertical,
422 GradientWarm,
423 GradientCool,
424 GradientOcean,
425 GradientNeon,
426 GradientFire,
427 GradientFrost,
429 GradientAurora,
430 GradientWinter,
431}
432
433const ALL_THEMES: &[ColorTheme] = &[
435 ColorTheme::Cyan,
436 ColorTheme::Green,
437 ColorTheme::Magenta,
438 ColorTheme::Yellow,
439 ColorTheme::Red,
440 ColorTheme::Blue,
441 ColorTheme::White,
442 ColorTheme::Rainbow,
443 ColorTheme::RainbowVertical,
444 ColorTheme::GradientWarm,
445 ColorTheme::GradientCool,
446 ColorTheme::GradientOcean,
447 ColorTheme::GradientNeon,
448 ColorTheme::GradientFire,
449 ColorTheme::GradientFrost,
450 ColorTheme::GradientAurora,
451 ColorTheme::GradientWinter,
452];
453
454impl ColorTheme {
455 pub fn next(&self) -> Self {
457 let current_idx = ALL_THEMES.iter().position(|t| t == self).unwrap_or(0);
458 let next_idx = (current_idx + 1) % ALL_THEMES.len();
459 ALL_THEMES[next_idx]
460 }
461
462 pub fn prev(&self) -> Self {
464 let current_idx = ALL_THEMES.iter().position(|t| t == self).unwrap_or(0);
465 let prev_idx = if current_idx == 0 {
466 ALL_THEMES.len() - 1
467 } else {
468 current_idx - 1
469 };
470 ALL_THEMES[prev_idx]
471 }
472
473 pub fn color(self) -> Color {
475 match self {
476 ColorTheme::Cyan => Color::Cyan,
477 ColorTheme::Green => Color::Green,
478 ColorTheme::White => Color::White,
479 ColorTheme::Magenta => Color::Magenta,
480 ColorTheme::Yellow => Color::Yellow,
481 ColorTheme::Red => Color::Red,
482 ColorTheme::Blue => Color::Blue,
483 ColorTheme::Rainbow | ColorTheme::RainbowVertical | ColorTheme::GradientNeon => {
485 Color::Magenta
486 }
487 ColorTheme::GradientWarm | ColorTheme::GradientFire => Color::Red,
488 ColorTheme::GradientCool | ColorTheme::GradientOcean => Color::Cyan,
489 ColorTheme::GradientFrost | ColorTheme::GradientWinter => Color::Cyan,
490 ColorTheme::GradientAurora => Color::Green,
491 }
492 }
493
494 pub fn is_dynamic(self) -> bool {
496 matches!(
497 self,
498 ColorTheme::Rainbow
499 | ColorTheme::RainbowVertical
500 | ColorTheme::GradientWarm
501 | ColorTheme::GradientCool
502 | ColorTheme::GradientOcean
503 | ColorTheme::GradientNeon
504 | ColorTheme::GradientFire
505 | ColorTheme::GradientFrost
506 | ColorTheme::GradientAurora
507 | ColorTheme::GradientWinter
508 )
509 }
510
511 pub fn color_at_position(self, x: usize, y: usize, width: usize, height: usize) -> Color {
515 match self {
516 ColorTheme::Rainbow => {
517 let colors = [
518 Color::Red,
519 Color::Rgb(255, 127, 0), Color::Yellow,
521 Color::Green,
522 Color::Cyan,
523 Color::Blue,
524 Color::Magenta,
525 ];
526 let idx = if width > 0 {
527 (x * colors.len() / width.max(1)) % colors.len()
528 } else {
529 0
530 };
531 colors[idx]
532 }
533 ColorTheme::RainbowVertical => {
534 let colors = [
535 Color::Red,
536 Color::Rgb(255, 127, 0), Color::Yellow,
538 Color::Green,
539 Color::Cyan,
540 Color::Blue,
541 Color::Magenta,
542 ];
543 let idx = if height > 0 {
544 (y * colors.len() / height.max(1)) % colors.len()
545 } else {
546 0
547 };
548 colors[idx]
549 }
550 ColorTheme::GradientWarm => {
551 let progress = if width > 0 {
553 (x as f32) / (width.max(1) as f32)
554 } else {
555 0.0
556 };
557 if progress < 0.5 {
558 let g = (127.0 * (progress * 2.0)) as u8;
560 Color::Rgb(255, g, 0)
561 } else {
562 let g = 127 + ((128.0 * ((progress - 0.5) * 2.0)) as u8);
564 Color::Rgb(255, g, 0)
565 }
566 }
567 ColorTheme::GradientCool => {
568 let progress = if width > 0 {
570 (x as f32) / (width.max(1) as f32)
571 } else {
572 0.0
573 };
574 if progress < 0.5 {
575 let g = (255.0 * (progress * 2.0)) as u8;
577 Color::Rgb(0, g, 255)
578 } else {
579 let b = 255 - ((255.0 * ((progress - 0.5) * 2.0)) as u8);
581 Color::Rgb(0, 255, b)
582 }
583 }
584 ColorTheme::GradientOcean => {
585 let progress = if width > 0 {
587 (x as f32) / (width.max(1) as f32)
588 } else {
589 0.0
590 };
591 if progress < 0.5 {
592 let r = (100.0 * (progress * 2.0)) as u8;
594 let g = (150.0 + 105.0 * (progress * 2.0)) as u8;
595 Color::Rgb(r, g, 255)
596 } else {
597 let b = 255 - ((127.0 * ((progress - 0.5) * 2.0)) as u8);
599 Color::Rgb(100, 255, b)
600 }
601 }
602 ColorTheme::GradientNeon => {
603 let progress = if width > 0 {
605 (x as f32) / (width.max(1) as f32)
606 } else {
607 0.0
608 };
609 let r = 255 - ((255.0 * progress) as u8);
610 let g = (255.0 * progress) as u8;
611 let b = 255;
612 Color::Rgb(r, g, b)
613 }
614 ColorTheme::GradientFire => {
615 let progress = if width > 0 {
617 (x as f32) / (width.max(1) as f32)
618 } else {
619 0.0
620 };
621 if progress < 0.33 {
622 let r = 128 + ((127.0 * (progress * 3.0)) as u8);
624 Color::Rgb(r, 0, 0)
625 } else if progress < 0.66 {
626 let g = (165.0 * ((progress - 0.33) * 3.0)) as u8;
628 Color::Rgb(255, g, 0)
629 } else {
630 let g = 165 + ((90.0 * ((progress - 0.66) * 3.0)) as u8);
632 Color::Rgb(255, g, 0)
633 }
634 }
635 ColorTheme::GradientFrost => {
636 let progress = if width > 0 {
638 (x as f32) / (width.max(1) as f32)
639 } else {
640 0.0
641 };
642 if progress < 0.5 {
643 let t = progress * 2.0;
645 let r = 255 - ((255 - 176) as f32 * t) as u8;
646 let g = 255 - ((255 - 224) as f32 * t) as u8;
647 let b = 255 - ((255 - 230) as f32 * t) as u8;
648 Color::Rgb(r, g, b)
649 } else {
650 let t = (progress - 0.5) * 2.0;
652 let r = 176 - ((176 - 70) as f32 * t) as u8;
653 let g = 224 - ((224 - 130) as f32 * t) as u8;
654 let b = 230 - ((230 - 180) as f32 * t) as u8;
655 Color::Rgb(r, g, b)
656 }
657 }
658 ColorTheme::GradientAurora => {
659 let progress = if width > 0 {
661 (x as f32) / (width.max(1) as f32)
662 } else {
663 0.0
664 };
665 if progress < 0.33 {
666 let t = progress * 3.0;
668 let r = (0.0 + 0.0 * t) as u8;
669 let g = (255.0 - 128.0 * t) as u8;
670 let b = (127.0 + 128.0 * t) as u8;
671 Color::Rgb(r, g, b)
672 } else if progress < 0.66 {
673 let t = (progress - 0.33) * 3.0;
675 let r = (0.0 + 65.0 * t) as u8;
676 let g = (127.0 - 22.0 * t) as u8;
677 let b = (255.0 - 30.0 * t) as u8;
678 Color::Rgb(r, g, b)
679 } else {
680 let t = (progress - 0.66) * 3.0;
682 let r = (65.0 + 73.0 * t) as u8;
683 let g = (105.0 - 62.0 * t) as u8;
684 let b = (225.0 + 1.0 * t) as u8;
685 Color::Rgb(r, g, b)
686 }
687 }
688 ColorTheme::GradientWinter => {
689 let progress = if width > 0 {
691 (x as f32) / (width.max(1) as f32)
692 } else {
693 0.0
694 };
695 if progress < 0.5 {
696 let t = progress * 2.0;
698 let r = (25.0 + 40.0 * t) as u8;
699 let g = (25.0 + 80.0 * t) as u8;
700 let b = (112.0 + 113.0 * t) as u8;
701 Color::Rgb(r, g, b)
702 } else {
703 let t = (progress - 0.5) * 2.0;
705 let r = (65.0 + 70.0 * t) as u8;
706 let g = (105.0 + 101.0 * t) as u8;
707 let b = (225.0 + 25.0 * t) as u8;
708 Color::Rgb(r, g, b)
709 }
710 }
711 _ => self.color(),
713 }
714 }
715
716 pub fn display_name(self) -> &'static str {
718 match self {
719 ColorTheme::Cyan => "Cyan",
720 ColorTheme::Green => "Green",
721 ColorTheme::White => "White",
722 ColorTheme::Magenta => "Magenta",
723 ColorTheme::Yellow => "Yellow",
724 ColorTheme::Red => "Red",
725 ColorTheme::Blue => "Blue",
726 ColorTheme::Rainbow => "Rainbow",
727 ColorTheme::RainbowVertical => "Rainbow V",
728 ColorTheme::GradientWarm => "Warm",
729 ColorTheme::GradientCool => "Cool",
730 ColorTheme::GradientOcean => "Ocean",
731 ColorTheme::GradientNeon => "Neon",
732 ColorTheme::GradientFire => "Fire",
733 ColorTheme::GradientFrost => "Frost",
734 ColorTheme::GradientAurora => "Aurora",
735 ColorTheme::GradientWinter => "Winter",
736 }
737 }
738}
739
740pub fn apply_animation(
742 base_color: Color,
743 animation_style: AnimationStyle,
744 speed: AnimationSpeed,
745 elapsed_ms: u64,
746 x: usize,
747 width: usize,
748 flash_intensity: f32,
749) -> Color {
750 match animation_style {
751 AnimationStyle::None => base_color,
752 AnimationStyle::Shifting => apply_shifting(base_color, elapsed_ms, speed),
753 AnimationStyle::Pulsing => apply_pulsing(base_color, elapsed_ms, speed),
754 AnimationStyle::Wave => apply_wave(base_color, elapsed_ms, speed, x, width),
755 AnimationStyle::Reactive => apply_reactive(base_color, flash_intensity),
756 }
757}
758
759fn apply_shifting(color: Color, elapsed_ms: u64, speed: AnimationSpeed) -> Color {
761 let (r, g, b) = color_to_rgb(color);
762 let (h, s, l) = rgb_to_hsl(r, g, b);
763
764 let cycle_ms = speed.shift_cycle_ms();
765 let hue_offset = ((elapsed_ms % cycle_ms) as f32 / cycle_ms as f32) * 360.0;
766 let new_h = (h + hue_offset) % 360.0;
767
768 let (nr, ng, nb) = hsl_to_rgb(new_h, s, l);
769 Color::Rgb(nr, ng, nb)
770}
771
772fn apply_pulsing(color: Color, elapsed_ms: u64, speed: AnimationSpeed) -> Color {
774 let (r, g, b) = color_to_rgb(color);
775
776 let period_ms = speed.pulse_period_ms();
777 let phase = (elapsed_ms % period_ms) as f32 / period_ms as f32;
778 let brightness = 0.5 + 0.5 * (phase * 2.0 * std::f32::consts::PI).sin();
779
780 let factor = 0.3 + 0.7 * brightness;
782 Color::Rgb(
783 (r as f32 * factor) as u8,
784 (g as f32 * factor) as u8,
785 (b as f32 * factor) as u8,
786 )
787}
788
789fn apply_wave(
791 color: Color,
792 elapsed_ms: u64,
793 speed: AnimationSpeed,
794 x: usize,
795 width: usize,
796) -> Color {
797 let (r, g, b) = color_to_rgb(color);
798
799 let period_ms = speed.wave_period_ms();
800 let time_phase = (elapsed_ms % period_ms) as f32 / period_ms as f32;
801 let x_phase = if width > 0 {
802 x as f32 / width as f32
803 } else {
804 0.0
805 };
806
807 let wave = ((x_phase + time_phase) * 2.0 * std::f32::consts::PI).sin();
808 let brightness = 0.6 + 0.4 * wave;
809
810 Color::Rgb(
811 (r as f32 * brightness) as u8,
812 (g as f32 * brightness) as u8,
813 (b as f32 * brightness) as u8,
814 )
815}
816
817fn apply_reactive(color: Color, flash_intensity: f32) -> Color {
819 let (r, g, b) = color_to_rgb(color);
820
821 let factor = 1.0 + flash_intensity;
823 Color::Rgb(
824 (r as f32 * factor).min(255.0) as u8,
825 (g as f32 * factor).min(255.0) as u8,
826 (b as f32 * factor).min(255.0) as u8,
827 )
828}
829
830fn color_to_rgb(color: Color) -> (u8, u8, u8) {
832 match color {
833 Color::Rgb(r, g, b) => (r, g, b),
834 Color::Red => (255, 0, 0),
835 Color::Green => (0, 255, 0),
836 Color::Blue => (0, 0, 255),
837 Color::Yellow => (255, 255, 0),
838 Color::Magenta => (255, 0, 255),
839 Color::Cyan => (0, 255, 255),
840 Color::White => (255, 255, 255),
841 _ => (128, 128, 128),
842 }
843}
844
845fn rgb_to_hsl(r: u8, g: u8, b: u8) -> (f32, f32, f32) {
847 let r = r as f32 / 255.0;
848 let g = g as f32 / 255.0;
849 let b = b as f32 / 255.0;
850
851 let max = r.max(g).max(b);
852 let min = r.min(g).min(b);
853 let l = (max + min) / 2.0;
854
855 if max == min {
856 return (0.0, 0.0, l);
857 }
858
859 let d = max - min;
860 let s = if l > 0.5 {
861 d / (2.0 - max - min)
862 } else {
863 d / (max + min)
864 };
865
866 let h = if max == r {
867 ((g - b) / d + if g < b { 6.0 } else { 0.0 }) * 60.0
868 } else if max == g {
869 ((b - r) / d + 2.0) * 60.0
870 } else {
871 ((r - g) / d + 4.0) * 60.0
872 };
873
874 (h, s, l)
875}
876
877fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
879 if s == 0.0 {
880 let v = (l * 255.0) as u8;
881 return (v, v, v);
882 }
883
884 let q = if l < 0.5 {
885 l * (1.0 + s)
886 } else {
887 l + s - l * s
888 };
889 let p = 2.0 * l - q;
890
891 let h = h / 360.0;
892
893 let r = hue_to_rgb(p, q, h + 1.0 / 3.0);
894 let g = hue_to_rgb(p, q, h);
895 let b = hue_to_rgb(p, q, h - 1.0 / 3.0);
896
897 ((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
898}
899
900fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
901 if t < 0.0 {
902 t += 1.0;
903 }
904 if t > 1.0 {
905 t -= 1.0;
906 }
907
908 if t < 1.0 / 6.0 {
909 p + (q - p) * 6.0 * t
910 } else if t < 1.0 / 2.0 {
911 q
912 } else if t < 2.0 / 3.0 {
913 p + (q - p) * (2.0 / 3.0 - t) * 6.0
914 } else {
915 p
916 }
917}
918
919pub fn is_colon_visible(elapsed_ms: u64) -> bool {
922 let phase = (elapsed_ms % 1000) as f32 / 1000.0;
923 phase < 0.5
924}