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