1use presentar_core::{
6 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Constraints, Event,
7 LayoutResult, Point, Rect, Size, TextStyle, TypeId, Widget,
8};
9use std::any::Any;
10use std::time::Duration;
11
12#[derive(Debug, Clone)]
14pub struct Segment {
15 pub value: f64,
17 pub color: Color,
19 pub label: Option<String>,
21}
22
23impl Segment {
24 #[must_use]
26 pub fn new(value: f64, color: Color) -> Self {
27 Self {
28 value,
29 color,
30 label: None,
31 }
32 }
33
34 #[must_use]
36 pub fn with_label(mut self, label: impl Into<String>) -> Self {
37 self.label = Some(label.into());
38 self
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct SegmentedMeter {
45 segments: Vec<Segment>,
47 max: f64,
49 background: Color,
51 show_percentages: bool,
53 bounds: Rect,
55}
56
57impl SegmentedMeter {
58 #[must_use]
60 pub fn new(segments: Vec<Segment>, max: f64) -> Self {
61 Self {
62 segments,
63 max,
64 background: Color::rgb(0.2, 0.2, 0.2),
65 show_percentages: false,
66 bounds: Rect::new(0.0, 0.0, 0.0, 0.0),
67 }
68 }
69
70 #[must_use]
72 pub fn memory(used: f64, cached: f64, total: f64) -> Self {
73 let free = (total - used - cached).max(0.0);
74 Self::new(
75 vec![
76 Segment::new(used, Color::rgb(1.0, 0.7, 0.2)).with_label("Used"),
77 Segment::new(cached, Color::rgb(0.2, 0.6, 1.0)).with_label("Cached"),
78 Segment::new(free, Color::rgb(0.3, 0.3, 0.3)).with_label("Free"),
79 ],
80 total,
81 )
82 }
83
84 #[must_use]
86 pub fn with_background(mut self, color: Color) -> Self {
87 self.background = color;
88 self
89 }
90
91 #[must_use]
93 pub fn with_percentages(mut self, show: bool) -> Self {
94 self.show_percentages = show;
95 self
96 }
97
98 pub fn set_segments(&mut self, segments: Vec<Segment>) {
100 self.segments = segments;
101 }
102
103 pub fn set_max(&mut self, max: f64) {
105 self.max = max;
106 }
107
108 fn render(&self, canvas: &mut dyn Canvas) {
109 let width = self.bounds.width as usize;
110 let height = self.bounds.height as usize;
111 if width == 0 || height == 0 {
112 return;
113 }
114
115 let total: f64 = self.segments.iter().map(|s| s.value).sum();
117 let scale = if self.max > 0.0 {
118 width as f64 / self.max
119 } else if total > 0.0 {
120 width as f64 / total
121 } else {
122 0.0
123 };
124
125 let mut x_offset = 0usize;
126
127 for segment in &self.segments {
129 let segment_width = (segment.value * scale).round() as usize;
130 if segment_width == 0 {
131 continue;
132 }
133
134 let style = TextStyle {
135 color: segment.color,
136 ..Default::default()
137 };
138
139 for row in 0..height {
141 for col in 0..segment_width {
142 let x = x_offset + col;
143 if x >= width {
144 break;
145 }
146 canvas.draw_text(
147 "█",
148 Point::new(self.bounds.x + x as f32, self.bounds.y + row as f32),
149 &style,
150 );
151 }
152 }
153
154 x_offset += segment_width;
155 }
156
157 if x_offset < width {
159 let bg_style = TextStyle {
160 color: self.background,
161 ..Default::default()
162 };
163
164 for row in 0..height {
165 for col in x_offset..width {
166 canvas.draw_text(
167 "░",
168 Point::new(self.bounds.x + col as f32, self.bounds.y + row as f32),
169 &bg_style,
170 );
171 }
172 }
173 }
174 }
175}
176
177impl Brick for SegmentedMeter {
178 fn brick_name(&self) -> &'static str {
179 "segmented_meter"
180 }
181
182 fn assertions(&self) -> &[BrickAssertion] {
183 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(8)];
184 ASSERTIONS
185 }
186
187 fn budget(&self) -> BrickBudget {
188 BrickBudget::uniform(8)
189 }
190
191 fn verify(&self) -> BrickVerification {
192 BrickVerification {
193 passed: vec![BrickAssertion::max_latency_ms(8)],
194 failed: vec![],
195 verification_time: Duration::from_micros(5),
196 }
197 }
198
199 fn to_html(&self) -> String {
200 String::new()
201 }
202
203 fn to_css(&self) -> String {
204 String::new()
205 }
206}
207
208impl Widget for SegmentedMeter {
209 fn type_id(&self) -> TypeId {
210 TypeId::of::<Self>()
211 }
212
213 fn measure(&self, constraints: Constraints) -> Size {
214 let width = constraints.max_width.max(10.0);
215 let height = constraints.max_height.clamp(1.0, 2.0);
216 constraints.constrain(Size::new(width, height))
217 }
218
219 fn layout(&mut self, bounds: Rect) -> LayoutResult {
220 self.bounds = bounds;
221 LayoutResult {
222 size: Size::new(bounds.width, bounds.height),
223 }
224 }
225
226 fn paint(&self, canvas: &mut dyn Canvas) {
227 self.render(canvas);
228 }
229
230 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
231 None
232 }
233
234 fn children(&self) -> &[Box<dyn Widget>] {
235 &[]
236 }
237
238 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
239 &mut []
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 struct MockCanvas {
248 texts: Vec<(String, Point, Color)>,
249 }
250
251 impl MockCanvas {
252 fn new() -> Self {
253 Self { texts: vec![] }
254 }
255 }
256
257 impl Canvas for MockCanvas {
258 fn fill_rect(&mut self, _rect: Rect, _color: Color) {}
259 fn stroke_rect(&mut self, _rect: Rect, _color: Color, _width: f32) {}
260 fn draw_text(&mut self, text: &str, position: Point, style: &TextStyle) {
261 self.texts.push((text.to_string(), position, style.color));
262 }
263 fn draw_line(&mut self, _from: Point, _to: Point, _color: Color, _width: f32) {}
264 fn fill_circle(&mut self, _center: Point, _radius: f32, _color: Color) {}
265 fn stroke_circle(&mut self, _center: Point, _radius: f32, _color: Color, _width: f32) {}
266 fn fill_arc(
267 &mut self,
268 _center: Point,
269 _radius: f32,
270 _start: f32,
271 _end: f32,
272 _color: Color,
273 ) {
274 }
275 fn draw_path(&mut self, _points: &[Point], _color: Color, _width: f32) {}
276 fn fill_polygon(&mut self, _points: &[Point], _color: Color) {}
277 fn push_clip(&mut self, _rect: Rect) {}
278 fn pop_clip(&mut self) {}
279 fn push_transform(&mut self, _transform: presentar_core::Transform2D) {}
280 fn pop_transform(&mut self) {}
281 }
282
283 #[test]
284 fn test_segment_creation() {
285 let segment = Segment::new(50.0, Color::RED);
286 assert_eq!(segment.value, 50.0);
287 assert_eq!(segment.color, Color::RED);
288 assert!(segment.label.is_none());
289 }
290
291 #[test]
292 fn test_segment_with_label() {
293 let segment = Segment::new(50.0, Color::RED).with_label("Used");
294 assert_eq!(segment.label, Some("Used".to_string()));
295 }
296
297 #[test]
298 fn test_segmented_meter_creation() {
299 let meter = SegmentedMeter::new(
300 vec![
301 Segment::new(30.0, Color::RED),
302 Segment::new(20.0, Color::BLUE),
303 ],
304 100.0,
305 );
306 assert_eq!(meter.segments.len(), 2);
307 assert_eq!(meter.max, 100.0);
308 }
309
310 #[test]
311 fn test_segmented_meter_memory() {
312 let meter = SegmentedMeter::memory(60.0, 20.0, 100.0);
313 assert_eq!(meter.segments.len(), 3);
314 assert_eq!(meter.max, 100.0);
315 }
316
317 #[test]
318 fn test_segmented_meter_with_background() {
319 let meter = SegmentedMeter::new(vec![], 100.0).with_background(Color::BLACK);
320 assert_eq!(meter.background, Color::BLACK);
321 }
322
323 #[test]
324 fn test_segmented_meter_with_percentages() {
325 let meter = SegmentedMeter::new(vec![], 100.0).with_percentages(true);
326 assert!(meter.show_percentages);
327 }
328
329 #[test]
330 fn test_segmented_meter_set_segments() {
331 let mut meter = SegmentedMeter::new(vec![], 100.0);
332 meter.set_segments(vec![Segment::new(50.0, Color::GREEN)]);
333 assert_eq!(meter.segments.len(), 1);
334 }
335
336 #[test]
337 fn test_segmented_meter_set_max() {
338 let mut meter = SegmentedMeter::new(vec![], 100.0);
339 meter.set_max(200.0);
340 assert_eq!(meter.max, 200.0);
341 }
342
343 #[test]
344 fn test_segmented_meter_paint() {
345 let mut meter = SegmentedMeter::new(
346 vec![
347 Segment::new(50.0, Color::RED),
348 Segment::new(30.0, Color::BLUE),
349 ],
350 100.0,
351 );
352 meter.bounds = Rect::new(0.0, 0.0, 20.0, 1.0);
353 let mut canvas = MockCanvas::new();
354 meter.paint(&mut canvas);
355 assert!(!canvas.texts.is_empty());
356 }
357
358 #[test]
359 fn test_segmented_meter_paint_empty() {
360 let mut meter = SegmentedMeter::new(vec![], 100.0);
361 meter.bounds = Rect::new(0.0, 0.0, 20.0, 1.0);
362 let mut canvas = MockCanvas::new();
363 meter.paint(&mut canvas);
364 assert!(!canvas.texts.is_empty());
366 }
367
368 #[test]
369 fn test_segmented_meter_paint_zero_bounds() {
370 let mut meter = SegmentedMeter::new(vec![Segment::new(50.0, Color::RED)], 100.0);
371 meter.bounds = Rect::new(0.0, 0.0, 0.0, 0.0);
372 let mut canvas = MockCanvas::new();
373 meter.paint(&mut canvas);
374 assert!(canvas.texts.is_empty());
375 }
376
377 #[test]
378 fn test_segmented_meter_brick_name() {
379 let meter = SegmentedMeter::new(vec![], 100.0);
380 assert_eq!(meter.brick_name(), "segmented_meter");
381 }
382
383 #[test]
384 fn test_segmented_meter_assertions_not_empty() {
385 let meter = SegmentedMeter::new(vec![], 100.0);
386 assert!(!meter.assertions().is_empty());
387 }
388
389 #[test]
390 fn test_segmented_meter_verify() {
391 let meter = SegmentedMeter::new(vec![], 100.0);
392 assert!(meter.verify().is_valid());
393 }
394
395 #[test]
396 fn test_segmented_meter_measure() {
397 let meter = SegmentedMeter::new(vec![], 100.0);
398 let constraints = Constraints::new(0.0, 50.0, 0.0, 10.0);
399 let size = meter.measure(constraints);
400 assert!(size.width >= 10.0);
401 assert!(size.height >= 1.0);
402 }
403
404 #[test]
405 fn test_segmented_meter_colors() {
406 let mut meter = SegmentedMeter::new(
407 vec![
408 Segment::new(25.0, Color::RED),
409 Segment::new(25.0, Color::GREEN),
410 Segment::new(25.0, Color::BLUE),
411 ],
412 100.0,
413 );
414 meter.bounds = Rect::new(0.0, 0.0, 12.0, 1.0);
415 let mut canvas = MockCanvas::new();
416 meter.paint(&mut canvas);
417
418 let colors: std::collections::HashSet<_> = canvas
420 .texts
421 .iter()
422 .map(|(_, _, c)| format!("{:?}", c))
423 .collect();
424 assert!(colors.len() >= 3); }
426
427 #[test]
428 fn test_segmented_meter_layout() {
429 let mut meter = SegmentedMeter::new(vec![Segment::new(50.0, Color::RED)], 100.0);
430 let result = meter.layout(Rect::new(0.0, 0.0, 80.0, 2.0));
431 assert_eq!(result.size.width, 80.0);
432 assert_eq!(result.size.height, 2.0);
433 }
434
435 #[test]
436 fn test_segmented_meter_event() {
437 let mut meter = SegmentedMeter::new(vec![], 100.0);
438 let event = Event::Resize {
439 width: 80.0,
440 height: 24.0,
441 };
442 assert!(meter.event(&event).is_none());
443 }
444
445 #[test]
446 fn test_segmented_meter_children() {
447 let meter = SegmentedMeter::new(vec![], 100.0);
448 assert!(meter.children().is_empty());
449 }
450
451 #[test]
452 fn test_segmented_meter_children_mut() {
453 let mut meter = SegmentedMeter::new(vec![], 100.0);
454 assert!(meter.children_mut().is_empty());
455 }
456
457 #[test]
458 fn test_segmented_meter_type_id() {
459 let meter = SegmentedMeter::new(vec![], 100.0);
460 let tid = Widget::type_id(&meter);
461 assert_eq!(tid, TypeId::of::<SegmentedMeter>());
462 }
463
464 #[test]
465 fn test_segmented_meter_budget() {
466 let meter = SegmentedMeter::new(vec![], 100.0);
467 let budget = meter.budget();
468 assert!(budget.layout_ms > 0);
469 }
470
471 #[test]
472 fn test_segmented_meter_to_html() {
473 let meter = SegmentedMeter::new(vec![], 100.0);
474 assert!(meter.to_html().is_empty());
475 }
476
477 #[test]
478 fn test_segmented_meter_to_css() {
479 let meter = SegmentedMeter::new(vec![], 100.0);
480 assert!(meter.to_css().is_empty());
481 }
482
483 #[test]
484 fn test_segment_clone() {
485 let seg = Segment::new(50.0, Color::RED).with_label("Test");
486 let cloned = seg.clone();
487 assert_eq!(cloned.value, seg.value);
488 assert_eq!(cloned.label, seg.label);
489 }
490
491 #[test]
492 fn test_segmented_meter_clone() {
493 let meter = SegmentedMeter::new(vec![Segment::new(50.0, Color::RED)], 100.0);
494 let cloned = meter.clone();
495 assert_eq!(cloned.max, meter.max);
496 assert_eq!(cloned.segments.len(), meter.segments.len());
497 }
498
499 #[test]
500 fn test_segment_debug() {
501 let seg = Segment::new(50.0, Color::RED);
502 let debug = format!("{seg:?}");
503 assert!(debug.contains("50"));
504 }
505
506 #[test]
507 fn test_segmented_meter_debug() {
508 let meter = SegmentedMeter::new(vec![], 100.0);
509 let debug = format!("{meter:?}");
510 assert!(debug.contains("100"));
511 }
512
513 #[test]
514 fn test_segmented_meter_paint_zero_max() {
515 let mut meter = SegmentedMeter::new(vec![Segment::new(50.0, Color::RED)], 0.0);
516 meter.bounds = Rect::new(0.0, 0.0, 20.0, 1.0);
517 let mut canvas = MockCanvas::new();
518 meter.paint(&mut canvas);
519 }
521
522 #[test]
523 fn test_segmented_meter_paint_multi_row() {
524 let mut meter = SegmentedMeter::new(
525 vec![
526 Segment::new(50.0, Color::RED),
527 Segment::new(30.0, Color::BLUE),
528 ],
529 100.0,
530 );
531 meter.bounds = Rect::new(0.0, 0.0, 20.0, 3.0);
532 let mut canvas = MockCanvas::new();
533 meter.paint(&mut canvas);
534 assert!(!canvas.texts.is_empty());
536 }
537
538 #[test]
539 fn test_segmented_meter_paint_zero_segment() {
540 let mut meter = SegmentedMeter::new(
541 vec![
542 Segment::new(50.0, Color::RED),
543 Segment::new(0.0, Color::BLUE), Segment::new(30.0, Color::GREEN),
545 ],
546 100.0,
547 );
548 meter.bounds = Rect::new(0.0, 0.0, 20.0, 1.0);
549 let mut canvas = MockCanvas::new();
550 meter.paint(&mut canvas);
551 assert!(!canvas.texts.is_empty());
553 }
554
555 #[test]
556 fn test_segmented_meter_paint_overflow() {
557 let mut meter = SegmentedMeter::new(
558 vec![
559 Segment::new(100.0, Color::RED),
560 Segment::new(100.0, Color::BLUE),
561 ],
562 100.0, );
564 meter.bounds = Rect::new(0.0, 0.0, 20.0, 1.0);
565 let mut canvas = MockCanvas::new();
566 meter.paint(&mut canvas);
567 assert!(!canvas.texts.is_empty());
569 }
570
571 #[test]
572 fn test_segmented_meter_memory_overflow() {
573 let meter = SegmentedMeter::memory(80.0, 30.0, 100.0);
575 assert_eq!(meter.segments.len(), 3);
576 assert_eq!(meter.segments[2].value, 0.0);
578 }
579}