Skip to main content

cbtop/bricks/panels/
overview.rs

1//! Overview panel brick (Layer 3)
2
3use crate::brick::{Brick, BrickAssertion, BrickBudget, BrickVerification};
4use presentar_core::{Canvas, Point, Rect, TextStyle, Widget};
5use presentar_terminal::{BrailleGraph, GraphMode, Theme};
6use std::any::Any;
7
8/// Overview panel brick
9pub struct OverviewPanelBrick {
10    pub cpu_data: Vec<f64>,
11    pub gpu_data: Vec<f64>,
12    pub cpu_avg: f64,
13    pub gpu_avg: f64,
14    pub frame_count: u64,
15    pub problem_size: usize,
16    pub theme: Theme,
17}
18
19impl OverviewPanelBrick {
20    pub fn new() -> Self {
21        Self {
22            cpu_data: Vec::new(),
23            gpu_data: Vec::new(),
24            cpu_avg: 0.0,
25            gpu_avg: 0.0,
26            frame_count: 0,
27            problem_size: 0,
28            theme: Theme::tokyo_night(),
29        }
30    }
31
32    pub fn paint(&self, canvas: &mut dyn Canvas, width: f32, _height: f32) {
33        let label_style = TextStyle {
34            color: self.theme.foreground,
35            ..Default::default()
36        };
37        let dim_style = TextStyle {
38            color: self.theme.dim,
39            ..Default::default()
40        };
41
42        // CPU section
43        canvas.draw_text("CPU Utilization", Point::new(2.0, 2.0), &label_style);
44        if !self.cpu_data.is_empty() {
45            let cpu_usage = self.cpu_data.last().copied().unwrap_or(0.0);
46            let mut graph = BrailleGraph::new(self.cpu_data.clone())
47                .with_color(self.theme.cpu_color(cpu_usage))
48                .with_range(0.0, 100.0)
49                .with_mode(GraphMode::Braille);
50            graph.layout(Rect::new(2.0, 3.0, width / 2.0 - 4.0, 6.0));
51            graph.paint(canvas);
52        }
53
54        // GPU section
55        canvas.draw_text(
56            "GPU Utilization",
57            Point::new(width / 2.0 + 2.0, 2.0),
58            &label_style,
59        );
60        if !self.gpu_data.is_empty() {
61            let gpu_usage = self.gpu_data.last().copied().unwrap_or(0.0);
62            let mut graph = BrailleGraph::new(self.gpu_data.clone())
63                .with_color(self.theme.gpu_color(gpu_usage))
64                .with_range(0.0, 100.0)
65                .with_mode(GraphMode::Braille);
66            graph.layout(Rect::new(width / 2.0 + 2.0, 3.0, width / 2.0 - 4.0, 6.0));
67            graph.paint(canvas);
68        }
69
70        // Statistics
71        canvas.draw_text("Statistics", Point::new(2.0, 10.0), &label_style);
72
73        // Color the CPU/GPU avg values according to their usage
74        let cpu_color_style = TextStyle {
75            color: self.theme.cpu_color(self.cpu_avg),
76            ..Default::default()
77        };
78        let gpu_color_style = TextStyle {
79            color: self.theme.gpu_color(self.gpu_avg),
80            ..Default::default()
81        };
82
83        canvas.draw_text("CPU Avg: ", Point::new(2.0, 11.0), &dim_style);
84        canvas.draw_text(
85            &format!("{:.1}%", self.cpu_avg),
86            Point::new(12.0, 11.0),
87            &cpu_color_style,
88        );
89        canvas.draw_text("  GPU Avg: ", Point::new(18.0, 11.0), &dim_style);
90        canvas.draw_text(
91            &format!("{:.1}%", self.gpu_avg),
92            Point::new(30.0, 11.0),
93            &gpu_color_style,
94        );
95
96        canvas.draw_text(
97            &format!(
98                "Samples: {}  Problem Size: {}",
99                self.frame_count, self.problem_size
100            ),
101            Point::new(2.0, 12.0),
102            &dim_style,
103        );
104    }
105}
106
107impl Brick for OverviewPanelBrick {
108    fn brick_name(&self) -> &'static str {
109        "overview_panel"
110    }
111
112    fn assertions(&self) -> Vec<BrickAssertion> {
113        vec![
114            BrickAssertion::MinWidth(40),
115            BrickAssertion::MinHeight(15),
116            BrickAssertion::max_latency_ms(8),
117        ]
118    }
119
120    fn budget(&self) -> BrickBudget {
121        BrickBudget::FRAME_60FPS
122    }
123
124    fn verify(&self) -> BrickVerification {
125        let mut v = BrickVerification::new();
126        for assertion in self.assertions() {
127            v.check(&assertion);
128        }
129        v
130    }
131
132    fn as_any(&self) -> &dyn Any {
133        self
134    }
135}
136
137impl Default for OverviewPanelBrick {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use presentar_core::RecordingCanvas;
147
148    #[test]
149    fn test_overview_panel_brick_name() {
150        let panel = OverviewPanelBrick::new();
151        assert_eq!(panel.brick_name(), "overview_panel");
152    }
153
154    #[test]
155    fn test_overview_panel_has_assertions() {
156        let panel = OverviewPanelBrick::new();
157        assert!(!panel.assertions().is_empty());
158    }
159
160    #[test]
161    fn test_overview_panel_paint_empty() {
162        let panel = OverviewPanelBrick::new();
163        let mut canvas = RecordingCanvas::new();
164
165        panel.paint(&mut canvas, 80.0, 24.0);
166
167        // Should draw header and stats even with no data
168        assert!(!canvas.is_empty());
169        assert!(canvas.command_count() >= 5);
170    }
171
172    #[test]
173    fn test_overview_panel_paint_with_cpu_data() {
174        let mut panel = OverviewPanelBrick::new();
175        panel.cpu_data = vec![10.0, 20.0, 30.0, 40.0, 50.0];
176        panel.cpu_avg = 30.0;
177
178        let mut canvas = RecordingCanvas::new();
179        panel.paint(&mut canvas, 80.0, 24.0);
180
181        // Should draw CPU graph
182        assert!(canvas.command_count() >= 5);
183    }
184
185    #[test]
186    fn test_overview_panel_paint_with_gpu_data() {
187        let mut panel = OverviewPanelBrick::new();
188        panel.gpu_data = vec![15.0, 25.0, 35.0, 45.0, 55.0];
189        panel.gpu_avg = 35.0;
190
191        let mut canvas = RecordingCanvas::new();
192        panel.paint(&mut canvas, 80.0, 24.0);
193
194        // Should draw GPU graph
195        assert!(canvas.command_count() >= 5);
196    }
197
198    #[test]
199    fn test_overview_panel_paint_with_both_graphs() {
200        let mut panel = OverviewPanelBrick::new();
201        panel.cpu_data = vec![10.0, 20.0, 30.0, 40.0];
202        panel.gpu_data = vec![15.0, 25.0, 35.0, 45.0];
203        panel.cpu_avg = 25.0;
204        panel.gpu_avg = 30.0;
205        panel.frame_count = 100;
206        panel.problem_size = 1024;
207
208        let mut canvas = RecordingCanvas::new();
209        panel.paint(&mut canvas, 80.0, 24.0);
210
211        // Should draw both graphs and stats
212        assert!(canvas.command_count() >= 8);
213    }
214
215    #[test]
216    fn test_overview_panel_default() {
217        let panel = OverviewPanelBrick::default();
218        assert!(panel.cpu_data.is_empty());
219        assert!(panel.gpu_data.is_empty());
220        assert_eq!(panel.cpu_avg, 0.0);
221        assert_eq!(panel.gpu_avg, 0.0);
222    }
223
224    #[test]
225    fn test_overview_panel_verify() {
226        let panel = OverviewPanelBrick::new();
227        let verification = panel.verify();
228        assert!(verification.is_valid());
229    }
230
231    #[test]
232    fn test_overview_panel_budget() {
233        let panel = OverviewPanelBrick::new();
234        let budget = panel.budget();
235        assert_eq!(budget.total_ms(), 16); // FRAME_60FPS
236    }
237}