1use 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
8pub 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 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 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 canvas.draw_text("Statistics", Point::new(2.0, 10.0), &label_style);
72
73 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 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 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 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 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); }
237}