1use super::Brick;
39use std::collections::VecDeque;
40use std::time::Duration;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum CollectorError {
49 NotAvailable,
51 Failed(String),
53 Disabled,
55 Timeout,
57}
58
59impl std::fmt::Display for CollectorError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Self::NotAvailable => write!(f, "Feature not available on this platform"),
63 Self::Failed(msg) => write!(f, "Collection failed: {msg}"),
64 Self::Disabled => write!(f, "Collector is disabled"),
65 Self::Timeout => write!(f, "Collection timed out"),
66 }
67 }
68}
69
70impl std::error::Error for CollectorError {}
71
72pub trait CollectorBrick: Brick + Send + Sync {
98 type Metrics;
100
101 fn is_available(&self) -> bool;
106
107 fn collect(&mut self) -> Result<Self::Metrics, CollectorError>;
113
114 fn feature_gate(&self) -> Option<&'static str> {
118 None
119 }
120
121 fn collection_interval(&self) -> Duration {
123 Duration::from_millis(100)
124 }
125}
126
127pub trait AnalyzerBrick: Brick + Send + Sync {
151 type Input;
153 type Output;
155
156 fn analyze(&self, input: &Self::Input) -> Self::Output;
158
159 fn is_stale(&self, _age: Duration) -> bool {
161 false
162 }
163}
164
165pub trait PanelBrick: Brick + Send + Sync {
173 fn render(&self, width: u16, height: u16) -> Vec<String>;
177
178 fn title(&self) -> &str;
180
181 fn focusable(&self) -> bool {
183 true
184 }
185
186 fn explodable(&self) -> bool {
188 true
189 }
190
191 fn min_height(&self) -> u16 {
193 3
194 }
195
196 fn preferred_height_fraction(&self) -> f32 {
198 0.25
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208pub enum PanelId {
209 Waveform,
211 Spectrogram,
213 Transcription,
215 Metrics,
217 VuMeter,
219 Status,
221 Custom(u32),
223}
224
225#[derive(Debug, Clone)]
229pub struct PanelState {
230 pub focused: Option<PanelId>,
232 pub exploded: Option<PanelId>,
234 pub visible: Vec<PanelId>,
236}
237
238impl Default for PanelState {
239 fn default() -> Self {
240 Self {
241 focused: None,
242 exploded: None,
243 visible: vec![
244 PanelId::Waveform,
245 PanelId::Transcription,
246 PanelId::Metrics,
247 PanelId::Status,
248 ],
249 }
250 }
251}
252
253impl PanelState {
254 #[must_use]
256 pub fn with_panels(panels: Vec<PanelId>) -> Self {
257 Self {
258 focused: panels.first().copied(),
259 exploded: None,
260 visible: panels,
261 }
262 }
263
264 pub fn focus_next(&mut self) {
266 if self.visible.is_empty() {
267 self.focused = None;
268 return;
269 }
270
271 let current_idx = self
272 .focused
273 .and_then(|f| self.visible.iter().position(|p| *p == f));
274
275 let next_idx = current_idx
276 .map(|i| (i + 1) % self.visible.len())
277 .unwrap_or(0);
278
279 self.focused = self.visible.get(next_idx).copied();
280 }
281
282 pub fn focus_prev(&mut self) {
284 if self.visible.is_empty() {
285 self.focused = None;
286 return;
287 }
288
289 let current_idx = self
290 .focused
291 .and_then(|f| self.visible.iter().position(|p| *p == f));
292
293 let prev_idx = current_idx
294 .map(|i| {
295 if i == 0 {
296 self.visible.len() - 1
297 } else {
298 i - 1
299 }
300 })
301 .unwrap_or(0);
302
303 self.focused = self.visible.get(prev_idx).copied();
304 }
305
306 pub fn toggle_explode(&mut self) {
308 if self.exploded.is_some() {
309 self.exploded = None;
310 } else {
311 self.exploded = self.focused;
312 }
313 }
314
315 #[must_use]
317 pub fn is_focused(&self, panel: PanelId) -> bool {
318 self.focused == Some(panel)
319 }
320
321 #[must_use]
323 pub fn is_exploded(&self, panel: PanelId) -> bool {
324 self.exploded == Some(panel)
325 }
326
327 #[must_use]
329 pub fn has_exploded(&self) -> bool {
330 self.exploded.is_some()
331 }
332
333 pub fn focus(&mut self, panel: PanelId) {
335 if self.visible.contains(&panel) {
336 self.focused = Some(panel);
337 }
338 }
339
340 pub fn add_panel(&mut self, panel: PanelId) {
342 if !self.visible.contains(&panel) {
343 self.visible.push(panel);
344 }
345 }
346
347 pub fn remove_panel(&mut self, panel: PanelId) {
349 self.visible.retain(|p| *p != panel);
350 if self.focused == Some(panel) {
351 self.focused = self.visible.first().copied();
352 }
353 if self.exploded == Some(panel) {
354 self.exploded = None;
355 }
356 }
357}
358
359#[derive(Debug, Clone)]
381pub struct RingBuffer<T> {
382 data: VecDeque<T>,
383 capacity: usize,
384}
385
386impl<T> RingBuffer<T> {
387 #[must_use]
389 pub fn new(capacity: usize) -> Self {
390 Self {
391 data: VecDeque::with_capacity(capacity),
392 capacity,
393 }
394 }
395
396 pub fn push(&mut self, value: T) {
398 if self.data.len() >= self.capacity {
399 self.data.pop_front();
400 }
401 self.data.push_back(value);
402 }
403
404 pub fn iter(&self) -> impl Iterator<Item = &T> {
406 self.data.iter()
407 }
408
409 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
411 self.data.iter_mut()
412 }
413
414 #[must_use]
416 pub fn len(&self) -> usize {
417 self.data.len()
418 }
419
420 #[must_use]
422 pub fn is_empty(&self) -> bool {
423 self.data.is_empty()
424 }
425
426 #[must_use]
428 pub fn capacity(&self) -> usize {
429 self.capacity
430 }
431
432 #[must_use]
434 pub fn last(&self) -> Option<&T> {
435 self.data.back()
436 }
437
438 #[must_use]
440 pub fn first(&self) -> Option<&T> {
441 self.data.front()
442 }
443
444 #[must_use]
446 pub fn get(&self, index: usize) -> Option<&T> {
447 self.data.get(index)
448 }
449
450 pub fn clear(&mut self) {
452 self.data.clear();
453 }
454
455 #[must_use]
457 pub fn is_full(&self) -> bool {
458 self.data.len() >= self.capacity
459 }
460}
461
462impl<T: Clone> RingBuffer<T> {
463 #[must_use]
465 pub fn to_vec(&self) -> Vec<T> {
466 self.data.iter().cloned().collect()
467 }
468}
469
470impl<T: Copy + Default> RingBuffer<T> {
471 pub fn fill_default(&mut self) {
473 while self.data.len() < self.capacity {
474 self.data.push_back(T::default());
475 }
476 }
477}
478
479impl<T: Copy + Into<f64>> RingBuffer<T> {
480 #[must_use]
482 pub fn average(&self) -> f64 {
483 if self.is_empty() {
484 return 0.0;
485 }
486 let sum: f64 = self.data.iter().map(|v| (*v).into()).sum();
487 sum / self.data.len() as f64
488 }
489
490 #[must_use]
492 pub fn min(&self) -> Option<f64> {
493 self.data
494 .iter()
495 .map(|v| (*v).into())
496 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
497 }
498
499 #[must_use]
501 pub fn max(&self) -> Option<f64> {
502 self.data
503 .iter()
504 .map(|v| (*v).into())
505 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
506 }
507}
508
509#[derive(Debug, Clone, Copy, PartialEq)]
531pub struct CielabColor {
532 pub l: f32,
534 pub a: f32,
536 pub b: f32,
538}
539
540impl CielabColor {
541 #[must_use]
543 pub const fn new(l: f32, a: f32, b: f32) -> Self {
544 Self { l, a, b }
545 }
546
547 #[must_use]
549 pub fn lerp(&self, other: &Self, t: f32) -> Self {
550 let t = t.clamp(0.0, 1.0);
551 Self {
552 l: self.l + (other.l - self.l) * t,
553 a: self.a + (other.a - self.a) * t,
554 b: self.b + (other.b - self.b) * t,
555 }
556 }
557
558 #[must_use]
563 #[allow(
564 clippy::cast_possible_truncation,
565 clippy::cast_sign_loss,
566 clippy::many_single_char_names )]
568 pub fn to_rgb(&self) -> (u8, u8, u8) {
569 let fy = (self.l + 16.0) / 116.0;
571 let fx = self.a / 500.0 + fy;
572 let fz = fy - self.b / 200.0;
573
574 let xr = if fx.powi(3) > 0.008856 {
575 fx.powi(3)
576 } else {
577 (116.0 * fx - 16.0) / 903.3
578 };
579 let yr = if self.l > 7.9996 {
580 fy.powi(3)
581 } else {
582 self.l / 903.3
583 };
584 let zr = if fz.powi(3) > 0.008856 {
585 fz.powi(3)
586 } else {
587 (116.0 * fz - 16.0) / 903.3
588 };
589
590 let x = xr * 0.95047;
592 let y = yr * 1.0;
593 let z = zr * 1.08883;
594
595 let r = x * 3.2406 - y * 1.5372 - z * 0.4986;
597 let g = -x * 0.9689 + y * 1.8758 + z * 0.0415;
598 let b = x * 0.0557 - y * 0.2040 + z * 1.0570;
599
600 let gamma = |c: f32| -> f32 {
602 if c > 0.0031308 {
603 1.055 * c.powf(1.0 / 2.4) - 0.055
604 } else {
605 12.92 * c
606 }
607 };
608
609 let r = (gamma(r) * 255.0).clamp(0.0, 255.0) as u8;
610 let g = (gamma(g) * 255.0).clamp(0.0, 255.0) as u8;
611 let b = (gamma(b) * 255.0).clamp(0.0, 255.0) as u8;
612
613 (r, g, b)
614 }
615
616 #[must_use]
618 pub fn to_hex(&self) -> String {
619 let (r, g, b) = self.to_rgb();
620 format!("#{r:02x}{g:02x}{b:02x}")
621 }
622
623 #[must_use]
627 pub fn percent_gradient(percent: f32) -> Self {
628 let percent = percent.clamp(0.0, 1.0);
629
630 let green = Self::new(87.0, -86.0, 83.0);
632 let yellow = Self::new(97.0, -21.0, 94.0);
633 let red = Self::new(53.0, 80.0, 67.0);
634
635 if percent < 0.5 {
636 green.lerp(&yellow, percent * 2.0)
637 } else {
638 yellow.lerp(&red, (percent - 0.5) * 2.0)
639 }
640 }
641
642 #[must_use]
644 pub fn meter_gradient(level: f32) -> Self {
645 let level = level.clamp(0.0, 1.0);
646
647 let blue = Self::new(50.0, -10.0, -50.0);
648 let green = Self::new(87.0, -86.0, 83.0);
649 let yellow = Self::new(97.0, -21.0, 94.0);
650 let red = Self::new(53.0, 80.0, 67.0);
651
652 if level < 0.33 {
653 blue.lerp(&green, level * 3.0)
654 } else if level < 0.66 {
655 green.lerp(&yellow, (level - 0.33) * 3.0)
656 } else {
657 yellow.lerp(&red, (level - 0.66) * 3.0)
658 }
659 }
660}
661
662impl Default for CielabColor {
663 fn default() -> Self {
664 Self::new(50.0, 0.0, 0.0) }
666}
667
668#[cfg(test)]
673#[allow(clippy::unwrap_used, clippy::expect_used)]
674mod tests {
675 use super::*;
676
677 #[test]
679 fn test_ring_buffer_basic() {
680 let mut buf: RingBuffer<i32> = RingBuffer::new(3);
681 buf.push(1);
682 buf.push(2);
683 buf.push(3);
684 buf.push(4); let values: Vec<_> = buf.iter().copied().collect();
687 assert_eq!(values, vec![2, 3, 4]);
688 }
689
690 #[test]
691 fn test_ring_buffer_capacity() {
692 let mut buf: RingBuffer<i32> = RingBuffer::new(5);
693 for i in 0..10 {
694 buf.push(i);
695 }
696 assert_eq!(buf.len(), 5);
697 assert_eq!(*buf.last().unwrap(), 9);
698 assert_eq!(*buf.first().unwrap(), 5);
699 }
700
701 #[test]
702 fn test_ring_buffer_to_vec() {
703 let mut buf: RingBuffer<i32> = RingBuffer::new(3);
704 buf.push(1);
705 buf.push(2);
706 buf.push(3);
707 buf.push(4);
708
709 assert_eq!(buf.to_vec(), vec![2, 3, 4]);
710 }
711
712 #[test]
713 fn test_ring_buffer_average() {
714 let mut buf: RingBuffer<f32> = RingBuffer::new(4);
715 buf.push(1.0);
716 buf.push(2.0);
717 buf.push(3.0);
718 buf.push(4.0);
719
720 assert!((buf.average() - 2.5).abs() < 0.001);
721 }
722
723 #[test]
724 fn test_ring_buffer_min_max() {
725 let mut buf: RingBuffer<f32> = RingBuffer::new(5);
726 buf.push(3.0);
727 buf.push(1.0);
728 buf.push(4.0);
729 buf.push(1.5);
730 buf.push(9.0);
731
732 assert!((buf.min().unwrap() - 1.0).abs() < 0.001);
733 assert!((buf.max().unwrap() - 9.0).abs() < 0.001);
734 }
735
736 #[test]
738 fn test_panel_state_focus() {
739 let mut state = PanelState::default();
740 state.focused = Some(PanelId::Waveform);
741
742 state.focus_next();
743 assert_eq!(state.focused, Some(PanelId::Transcription));
744
745 state.focus_next();
746 assert_eq!(state.focused, Some(PanelId::Metrics));
747
748 state.focus_prev();
749 assert_eq!(state.focused, Some(PanelId::Transcription));
750 }
751
752 #[test]
753 fn test_panel_state_focus_wrap() {
754 let mut state = PanelState::with_panels(vec![PanelId::Waveform, PanelId::Metrics]);
755 state.focused = Some(PanelId::Metrics);
756
757 state.focus_next();
758 assert_eq!(state.focused, Some(PanelId::Waveform));
759
760 state.focus_prev();
761 assert_eq!(state.focused, Some(PanelId::Metrics));
762 }
763
764 #[test]
765 fn test_panel_state_explode() {
766 let mut state = PanelState::default();
767 state.focused = Some(PanelId::Transcription);
768
769 assert!(!state.has_exploded());
770
771 state.toggle_explode();
772 assert!(state.is_exploded(PanelId::Transcription));
773 assert!(state.has_exploded());
774
775 state.toggle_explode();
776 assert!(!state.has_exploded());
777 }
778
779 #[test]
780 fn test_panel_state_add_remove() {
781 let mut state = PanelState::default();
782 let custom = PanelId::Custom(42);
783
784 state.add_panel(custom);
785 assert!(state.visible.contains(&custom));
786
787 state.focus(custom);
788 assert_eq!(state.focused, Some(custom));
789
790 state.remove_panel(custom);
791 assert!(!state.visible.contains(&custom));
792 assert_ne!(state.focused, Some(custom));
793 }
794
795 #[test]
797 fn test_cielab_lerp() {
798 let green = CielabColor::new(87.0, -86.0, 83.0);
799 let red = CielabColor::new(53.0, 80.0, 67.0);
800
801 let mid = green.lerp(&red, 0.5);
802 assert!((mid.l - 70.0).abs() < 0.1);
803 assert!((mid.a - (-3.0)).abs() < 0.1);
804 }
805
806 #[test]
807 fn test_cielab_gradient() {
808 let start = CielabColor::percent_gradient(0.0);
809 let end = CielabColor::percent_gradient(1.0);
810
811 assert!(start.a < 0.0);
813 assert!(end.a > 0.0);
815 }
816
817 #[test]
818 fn test_cielab_to_rgb() {
819 let white = CielabColor::new(100.0, 0.0, 0.0);
820 let (r, g, b) = white.to_rgb();
821 assert!(r > 250);
823 assert!(g > 250);
824 assert!(b > 250);
825
826 let black = CielabColor::new(0.0, 0.0, 0.0);
827 let (r, g, b) = black.to_rgb();
828 assert!(r < 5);
830 assert!(g < 5);
831 assert!(b < 5);
832 }
833
834 #[test]
835 fn test_cielab_to_hex() {
836 let color = CielabColor::new(50.0, 0.0, 0.0);
837 let hex = color.to_hex();
838 assert!(hex.starts_with('#'));
839 assert_eq!(hex.len(), 7);
840 }
841
842 #[test]
843 fn test_cielab_meter_gradient() {
844 let low = CielabColor::meter_gradient(0.0);
845 let mid = CielabColor::meter_gradient(0.5);
846 let high = CielabColor::meter_gradient(1.0);
847
848 assert!(low.b < 0.0);
850 assert!(high.a > 0.0);
852 assert!(mid.l > 80.0);
854 }
855
856 #[test]
858 fn test_collector_error_display() {
859 assert_eq!(
860 CollectorError::NotAvailable.to_string(),
861 "Feature not available on this platform"
862 );
863 assert_eq!(
864 CollectorError::Failed("test".into()).to_string(),
865 "Collection failed: test"
866 );
867 }
868
869 #[test]
874 fn test_collector_error_disabled() {
875 assert_eq!(
876 CollectorError::Disabled.to_string(),
877 "Collector is disabled"
878 );
879 }
880
881 #[test]
882 fn test_collector_error_timeout() {
883 assert_eq!(CollectorError::Timeout.to_string(), "Collection timed out");
884 }
885
886 #[test]
887 fn test_collector_error_eq() {
888 assert_eq!(CollectorError::NotAvailable, CollectorError::NotAvailable);
889 assert_eq!(CollectorError::Disabled, CollectorError::Disabled);
890 assert_eq!(CollectorError::Timeout, CollectorError::Timeout);
891 assert_eq!(
892 CollectorError::Failed("a".into()),
893 CollectorError::Failed("a".into())
894 );
895 assert_ne!(
896 CollectorError::Failed("a".into()),
897 CollectorError::Failed("b".into())
898 );
899 }
900
901 #[test]
902 fn test_collector_error_clone() {
903 let err = CollectorError::Failed("test".into());
904 let cloned = err.clone();
905 assert_eq!(err, cloned);
906 }
907
908 #[test]
909 fn test_collector_error_is_error() {
910 let err = CollectorError::Timeout;
911 let _: &dyn std::error::Error = &err;
912 }
913
914 #[test]
915 fn test_ring_buffer_is_empty() {
916 let buf: RingBuffer<i32> = RingBuffer::new(5);
917 assert!(buf.is_empty());
918
919 let mut buf2: RingBuffer<i32> = RingBuffer::new(5);
920 buf2.push(1);
921 assert!(!buf2.is_empty());
922 }
923
924 #[test]
925 fn test_ring_buffer_capacity_getter() {
926 let buf: RingBuffer<i32> = RingBuffer::new(10);
927 assert_eq!(buf.capacity(), 10);
928 }
929
930 #[test]
931 fn test_ring_buffer_first_last_empty() {
932 let buf: RingBuffer<i32> = RingBuffer::new(5);
933 assert!(buf.first().is_none());
934 assert!(buf.last().is_none());
935 }
936
937 #[test]
938 fn test_ring_buffer_get() {
939 let mut buf: RingBuffer<i32> = RingBuffer::new(5);
940 buf.push(10);
941 buf.push(20);
942 buf.push(30);
943
944 assert_eq!(buf.get(0), Some(&10));
945 assert_eq!(buf.get(1), Some(&20));
946 assert_eq!(buf.get(2), Some(&30));
947 assert_eq!(buf.get(3), None);
948 }
949
950 #[test]
951 fn test_ring_buffer_clear() {
952 let mut buf: RingBuffer<i32> = RingBuffer::new(5);
953 buf.push(1);
954 buf.push(2);
955 buf.push(3);
956
957 assert_eq!(buf.len(), 3);
958 buf.clear();
959 assert_eq!(buf.len(), 0);
960 assert!(buf.is_empty());
961 }
962
963 #[test]
964 fn test_ring_buffer_is_full() {
965 let mut buf: RingBuffer<i32> = RingBuffer::new(3);
966 assert!(!buf.is_full());
967
968 buf.push(1);
969 assert!(!buf.is_full());
970
971 buf.push(2);
972 assert!(!buf.is_full());
973
974 buf.push(3);
975 assert!(buf.is_full());
976
977 buf.push(4); assert!(buf.is_full());
979 }
980
981 #[test]
982 fn test_ring_buffer_iter_mut() {
983 let mut buf: RingBuffer<i32> = RingBuffer::new(5);
984 buf.push(1);
985 buf.push(2);
986 buf.push(3);
987
988 for val in buf.iter_mut() {
989 *val *= 2;
990 }
991
992 let values: Vec<_> = buf.iter().copied().collect();
993 assert_eq!(values, vec![2, 4, 6]);
994 }
995
996 #[test]
997 fn test_ring_buffer_fill_default() {
998 let mut buf: RingBuffer<i32> = RingBuffer::new(5);
999 buf.push(1);
1000 buf.push(2);
1001
1002 assert_eq!(buf.len(), 2);
1003 buf.fill_default();
1004 assert_eq!(buf.len(), 5);
1005 assert!(buf.is_full());
1006 }
1007
1008 #[test]
1009 fn test_ring_buffer_average_empty() {
1010 let buf: RingBuffer<f32> = RingBuffer::new(5);
1011 assert!((buf.average() - 0.0).abs() < 0.001);
1012 }
1013
1014 #[test]
1015 fn test_ring_buffer_min_max_empty() {
1016 let buf: RingBuffer<f32> = RingBuffer::new(5);
1017 assert!(buf.min().is_none());
1018 assert!(buf.max().is_none());
1019 }
1020
1021 #[test]
1022 fn test_ring_buffer_clone() {
1023 let mut buf: RingBuffer<i32> = RingBuffer::new(3);
1024 buf.push(1);
1025 buf.push(2);
1026
1027 let cloned = buf.clone();
1028 assert_eq!(buf.len(), cloned.len());
1029 assert_eq!(buf.to_vec(), cloned.to_vec());
1030 }
1031
1032 #[test]
1033 fn test_panel_id_eq() {
1034 assert_eq!(PanelId::Waveform, PanelId::Waveform);
1035 assert_eq!(PanelId::Custom(1), PanelId::Custom(1));
1036 assert_ne!(PanelId::Custom(1), PanelId::Custom(2));
1037 assert_ne!(PanelId::Waveform, PanelId::Spectrogram);
1038 }
1039
1040 #[test]
1041 fn test_panel_id_hash() {
1042 use std::collections::HashSet;
1043 let mut set = HashSet::new();
1044 set.insert(PanelId::Waveform);
1045 set.insert(PanelId::Spectrogram);
1046 set.insert(PanelId::Waveform); assert_eq!(set.len(), 2);
1048 }
1049
1050 #[test]
1051 fn test_panel_id_all_variants() {
1052 let ids = [
1053 PanelId::Waveform,
1054 PanelId::Spectrogram,
1055 PanelId::Transcription,
1056 PanelId::Metrics,
1057 PanelId::VuMeter,
1058 PanelId::Status,
1059 PanelId::Custom(0),
1060 ];
1061 for id in ids {
1062 assert_eq!(id, id);
1064 }
1065 }
1066
1067 #[test]
1068 fn test_panel_state_default() {
1069 let state = PanelState::default();
1070 assert!(state.focused.is_none());
1071 assert!(state.exploded.is_none());
1072 assert_eq!(state.visible.len(), 4);
1073 assert!(state.visible.contains(&PanelId::Waveform));
1074 assert!(state.visible.contains(&PanelId::Transcription));
1075 assert!(state.visible.contains(&PanelId::Metrics));
1076 assert!(state.visible.contains(&PanelId::Status));
1077 }
1078
1079 #[test]
1080 fn test_panel_state_with_panels() {
1081 let panels = vec![PanelId::Spectrogram, PanelId::VuMeter];
1082 let state = PanelState::with_panels(panels);
1083 assert_eq!(state.visible.len(), 2);
1084 assert_eq!(state.focused, Some(PanelId::Spectrogram));
1085 }
1086
1087 #[test]
1088 fn test_panel_state_with_panels_empty() {
1089 let state = PanelState::with_panels(vec![]);
1090 assert!(state.visible.is_empty());
1091 assert!(state.focused.is_none());
1092 }
1093
1094 #[test]
1095 fn test_panel_state_focus_next_empty() {
1096 let mut state = PanelState::with_panels(vec![]);
1097 state.focus_next();
1098 assert!(state.focused.is_none());
1099 }
1100
1101 #[test]
1102 fn test_panel_state_focus_prev_empty() {
1103 let mut state = PanelState::with_panels(vec![]);
1104 state.focus_prev();
1105 assert!(state.focused.is_none());
1106 }
1107
1108 #[test]
1109 fn test_panel_state_focus_next_no_current_focus() {
1110 let mut state = PanelState::with_panels(vec![PanelId::Waveform, PanelId::Metrics]);
1111 state.focused = None;
1112
1113 state.focus_next();
1114 assert_eq!(state.focused, Some(PanelId::Waveform));
1115 }
1116
1117 #[test]
1118 fn test_panel_state_focus_prev_no_current_focus() {
1119 let mut state = PanelState::with_panels(vec![PanelId::Waveform, PanelId::Metrics]);
1120 state.focused = None;
1121
1122 state.focus_prev();
1123 assert_eq!(state.focused, Some(PanelId::Waveform));
1124 }
1125
1126 #[test]
1127 fn test_panel_state_focus_prev_at_start() {
1128 let mut state = PanelState::with_panels(vec![PanelId::Waveform, PanelId::Metrics]);
1129 state.focused = Some(PanelId::Waveform);
1130
1131 state.focus_prev();
1132 assert_eq!(state.focused, Some(PanelId::Metrics)); }
1134
1135 #[test]
1136 fn test_panel_state_toggle_explode_no_focus() {
1137 let mut state = PanelState::default();
1138 state.focused = None;
1139
1140 state.toggle_explode();
1141 assert!(state.exploded.is_none());
1143 }
1144
1145 #[test]
1146 fn test_panel_state_is_focused() {
1147 let mut state = PanelState::default();
1148 state.focused = Some(PanelId::Waveform);
1149
1150 assert!(state.is_focused(PanelId::Waveform));
1151 assert!(!state.is_focused(PanelId::Metrics));
1152 }
1153
1154 #[test]
1155 fn test_panel_state_is_exploded() {
1156 let mut state = PanelState::default();
1157 state.exploded = Some(PanelId::Transcription);
1158
1159 assert!(state.is_exploded(PanelId::Transcription));
1160 assert!(!state.is_exploded(PanelId::Waveform));
1161 }
1162
1163 #[test]
1164 fn test_panel_state_focus_invalid_panel() {
1165 let mut state = PanelState::default();
1166 state.focused = Some(PanelId::Waveform);
1167
1168 state.focus(PanelId::VuMeter);
1170 assert_eq!(state.focused, Some(PanelId::Waveform));
1172 }
1173
1174 #[test]
1175 fn test_panel_state_add_panel_duplicate() {
1176 let mut state = PanelState::default();
1177 let initial_len = state.visible.len();
1178
1179 state.add_panel(PanelId::Waveform); assert_eq!(state.visible.len(), initial_len);
1181 }
1182
1183 #[test]
1184 fn test_panel_state_remove_panel_updates_focus() {
1185 let mut state = PanelState::default();
1186 state.focused = Some(PanelId::Metrics);
1187
1188 state.remove_panel(PanelId::Metrics);
1189 assert_eq!(state.focused, Some(PanelId::Waveform));
1191 }
1192
1193 #[test]
1194 fn test_panel_state_remove_panel_clears_exploded() {
1195 let mut state = PanelState::default();
1196 state.exploded = Some(PanelId::Metrics);
1197
1198 state.remove_panel(PanelId::Metrics);
1199 assert!(state.exploded.is_none());
1200 }
1201
1202 #[test]
1203 fn test_panel_state_clone() {
1204 let state = PanelState::default();
1205 let cloned = state.clone();
1206 assert_eq!(state.visible.len(), cloned.visible.len());
1207 }
1208
1209 #[test]
1210 fn test_cielab_default() {
1211 let color = CielabColor::default();
1212 assert!((color.l - 50.0).abs() < 0.001);
1213 assert!((color.a - 0.0).abs() < 0.001);
1214 assert!((color.b - 0.0).abs() < 0.001);
1215 }
1216
1217 #[test]
1218 fn test_cielab_lerp_clamp() {
1219 let c1 = CielabColor::new(0.0, 0.0, 0.0);
1220 let c2 = CielabColor::new(100.0, 100.0, 100.0);
1221
1222 let result = c1.lerp(&c2, -0.5);
1224 assert!((result.l - 0.0).abs() < 0.001);
1225
1226 let result = c1.lerp(&c2, 1.5);
1228 assert!((result.l - 100.0).abs() < 0.001);
1229 }
1230
1231 #[test]
1232 fn test_cielab_percent_gradient_clamp() {
1233 let color = CielabColor::percent_gradient(-0.5);
1235 assert!(color.a < 0.0); let color = CielabColor::percent_gradient(1.5);
1239 assert!(color.a > 0.0); }
1241
1242 #[test]
1243 fn test_cielab_percent_gradient_midpoint() {
1244 let color = CielabColor::percent_gradient(0.5);
1246 assert!(color.l > 90.0);
1248 }
1249
1250 #[test]
1251 fn test_cielab_meter_gradient_clamp() {
1252 let low = CielabColor::meter_gradient(-1.0);
1253 let high = CielabColor::meter_gradient(2.0);
1254 assert!(low.b < 0.0); assert!(high.a > 0.0); }
1258
1259 #[test]
1260 fn test_cielab_meter_gradient_transitions() {
1261 let at_033 = CielabColor::meter_gradient(0.33);
1263 let at_066 = CielabColor::meter_gradient(0.66);
1264
1265 assert!(at_033.l > 0.0);
1267 assert!(at_066.l > 0.0);
1268 }
1269
1270 #[test]
1271 fn test_cielab_to_rgb_edge_cases() {
1272 let dark = CielabColor::new(5.0, 0.0, 0.0);
1274 let (r, g, b) = dark.to_rgb();
1275 assert!(r < 20);
1277 assert!(g < 20);
1278 assert!(b < 20);
1279 }
1280
1281 #[test]
1282 fn test_cielab_eq() {
1283 let c1 = CielabColor::new(50.0, 10.0, -20.0);
1284 let c2 = CielabColor::new(50.0, 10.0, -20.0);
1285 let c3 = CielabColor::new(51.0, 10.0, -20.0);
1286
1287 assert_eq!(c1, c2);
1288 assert_ne!(c1, c3);
1289 }
1290
1291 #[test]
1292 fn test_cielab_clone() {
1293 let c1 = CielabColor::new(75.0, 25.0, -50.0);
1294 let c2 = c1;
1295 assert_eq!(c1, c2);
1296 }
1297
1298 #[test]
1299 fn test_ring_buffer_f64() {
1300 let mut buf: RingBuffer<f64> = RingBuffer::new(3);
1301 buf.push(1.5);
1302 buf.push(2.5);
1303 buf.push(3.5);
1304
1305 assert!((buf.average() - 2.5).abs() < 0.001);
1306 assert!((buf.min().unwrap() - 1.5).abs() < 0.001);
1307 assert!((buf.max().unwrap() - 3.5).abs() < 0.001);
1308 }
1309}