presentar_widgets/
progress_bar.rs

1//! Progress bar widget.
2
3use presentar_core::{
4    widget::{AccessibleRole, LayoutResult},
5    Canvas, Color, Constraints, Event, Rect, Size, TypeId, Widget,
6};
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9
10/// Mode of the progress bar.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
12pub enum ProgressMode {
13    /// Determinate progress (known percentage).
14    #[default]
15    Determinate,
16    /// Indeterminate progress (unknown percentage, animated).
17    Indeterminate,
18}
19
20/// Progress bar widget.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ProgressBar {
23    /// Current progress value (0.0 to 1.0)
24    value: f32,
25    /// Progress mode
26    mode: ProgressMode,
27    /// Minimum width
28    min_width: f32,
29    /// Height of the bar
30    height: f32,
31    /// Corner radius
32    corner_radius: f32,
33    /// Track color (background)
34    track_color: Color,
35    /// Fill color (progress)
36    fill_color: Color,
37    /// Show percentage label
38    show_label: bool,
39    /// Label color
40    label_color: Color,
41    /// Accessible name
42    accessible_name_value: Option<String>,
43    /// Test ID
44    test_id_value: Option<String>,
45    /// Current layout bounds
46    #[serde(skip)]
47    bounds: Rect,
48}
49
50impl Default for ProgressBar {
51    fn default() -> Self {
52        Self {
53            value: 0.0,
54            mode: ProgressMode::Determinate,
55            min_width: 100.0,
56            height: 8.0,
57            corner_radius: 4.0,
58            track_color: Color::new(0.88, 0.88, 0.88, 1.0), // #E0E0E0
59            fill_color: Color::new(0.13, 0.59, 0.95, 1.0),  // #2196F3
60            show_label: false,
61            label_color: Color::BLACK,
62            accessible_name_value: None,
63            test_id_value: None,
64            bounds: Rect::default(),
65        }
66    }
67}
68
69impl ProgressBar {
70    /// Create a new progress bar.
71    #[must_use]
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    /// Create a progress bar with the given value.
77    #[must_use]
78    pub fn with_value(value: f32) -> Self {
79        Self::default().value(value)
80    }
81
82    /// Set the progress value (clamped to 0.0..=1.0).
83    #[must_use]
84    pub fn value(mut self, value: f32) -> Self {
85        self.value = value.clamp(0.0, 1.0);
86        self
87    }
88
89    /// Set the progress mode.
90    #[must_use]
91    pub const fn mode(mut self, mode: ProgressMode) -> Self {
92        self.mode = mode;
93        self
94    }
95
96    /// Set indeterminate mode.
97    #[must_use]
98    pub const fn indeterminate(self) -> Self {
99        self.mode(ProgressMode::Indeterminate)
100    }
101
102    /// Set the minimum width.
103    #[must_use]
104    pub fn min_width(mut self, width: f32) -> Self {
105        self.min_width = width.max(20.0);
106        self
107    }
108
109    /// Set the height.
110    #[must_use]
111    pub fn height(mut self, height: f32) -> Self {
112        self.height = height.max(4.0);
113        self
114    }
115
116    /// Set the corner radius.
117    #[must_use]
118    pub fn corner_radius(mut self, radius: f32) -> Self {
119        self.corner_radius = radius.max(0.0);
120        self
121    }
122
123    /// Set the track color (background).
124    #[must_use]
125    pub const fn track_color(mut self, color: Color) -> Self {
126        self.track_color = color;
127        self
128    }
129
130    /// Set the fill color (progress).
131    #[must_use]
132    pub const fn fill_color(mut self, color: Color) -> Self {
133        self.fill_color = color;
134        self
135    }
136
137    /// Show percentage label.
138    #[must_use]
139    pub const fn with_label(mut self) -> Self {
140        self.show_label = true;
141        self
142    }
143
144    /// Set whether to show the label.
145    #[must_use]
146    pub const fn show_label(mut self, show: bool) -> Self {
147        self.show_label = show;
148        self
149    }
150
151    /// Set the label color.
152    #[must_use]
153    pub const fn label_color(mut self, color: Color) -> Self {
154        self.label_color = color;
155        self
156    }
157
158    /// Set the accessible name.
159    #[must_use]
160    pub fn accessible_name(mut self, name: impl Into<String>) -> Self {
161        self.accessible_name_value = Some(name.into());
162        self
163    }
164
165    /// Set the test ID.
166    #[must_use]
167    pub fn test_id(mut self, id: impl Into<String>) -> Self {
168        self.test_id_value = Some(id.into());
169        self
170    }
171
172    /// Get the current value.
173    #[must_use]
174    pub const fn get_value(&self) -> f32 {
175        self.value
176    }
177
178    /// Get the current mode.
179    #[must_use]
180    pub const fn get_mode(&self) -> ProgressMode {
181        self.mode
182    }
183
184    /// Get the percentage (0-100).
185    #[must_use]
186    pub fn percentage(&self) -> u8 {
187        (self.value * 100.0).round() as u8
188    }
189
190    /// Check if progress is complete.
191    #[must_use]
192    pub fn is_complete(&self) -> bool {
193        self.mode == ProgressMode::Determinate && self.value >= 1.0
194    }
195
196    /// Check if indeterminate.
197    #[must_use]
198    pub fn is_indeterminate(&self) -> bool {
199        self.mode == ProgressMode::Indeterminate
200    }
201
202    /// Set the value directly (mutable).
203    pub fn set_value(&mut self, value: f32) {
204        self.value = value.clamp(0.0, 1.0);
205    }
206
207    /// Increment the value by a delta.
208    pub fn increment(&mut self, delta: f32) {
209        self.value = (self.value + delta).clamp(0.0, 1.0);
210    }
211
212    /// Calculate the fill width.
213    fn fill_width(&self, total_width: f32) -> f32 {
214        total_width * self.value
215    }
216
217    /// Get the track color.
218    #[must_use]
219    pub const fn get_track_color(&self) -> Color {
220        self.track_color
221    }
222
223    /// Get the fill color.
224    #[must_use]
225    pub const fn get_fill_color(&self) -> Color {
226        self.fill_color
227    }
228
229    /// Get the label color.
230    #[must_use]
231    pub const fn get_label_color(&self) -> Color {
232        self.label_color
233    }
234
235    /// Get whether label is shown.
236    #[must_use]
237    pub const fn is_label_shown(&self) -> bool {
238        self.show_label
239    }
240
241    /// Get the minimum width.
242    #[must_use]
243    pub const fn get_min_width(&self) -> f32 {
244        self.min_width
245    }
246
247    /// Get the height.
248    #[must_use]
249    pub const fn get_height(&self) -> f32 {
250        self.height
251    }
252
253    /// Get the corner radius.
254    #[must_use]
255    pub const fn get_corner_radius(&self) -> f32 {
256        self.corner_radius
257    }
258}
259
260impl Widget for ProgressBar {
261    fn type_id(&self) -> TypeId {
262        TypeId::of::<Self>()
263    }
264
265    fn measure(&self, constraints: Constraints) -> Size {
266        let preferred_height = if self.show_label {
267            self.height + 20.0
268        } else {
269            self.height
270        };
271        let preferred = Size::new(self.min_width, preferred_height);
272        constraints.constrain(preferred)
273    }
274
275    fn layout(&mut self, bounds: Rect) -> LayoutResult {
276        self.bounds = bounds;
277        LayoutResult {
278            size: bounds.size(),
279        }
280    }
281
282    fn paint(&self, canvas: &mut dyn Canvas) {
283        // Draw track (background)
284        let track_rect = Rect::new(self.bounds.x, self.bounds.y, self.bounds.width, self.height);
285        canvas.fill_rect(track_rect, self.track_color);
286
287        // Draw fill (progress) - only for determinate mode
288        if self.mode == ProgressMode::Determinate && self.value > 0.0 {
289            let fill_width = self.fill_width(track_rect.width);
290            let fill_rect = Rect::new(track_rect.x, track_rect.y, fill_width, self.height);
291            canvas.fill_rect(fill_rect, self.fill_color);
292        }
293    }
294
295    fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
296        // Progress bars don't handle events
297        None
298    }
299
300    fn children(&self) -> &[Box<dyn Widget>] {
301        &[]
302    }
303
304    fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
305        &mut []
306    }
307
308    fn is_interactive(&self) -> bool {
309        false
310    }
311
312    fn is_focusable(&self) -> bool {
313        false
314    }
315
316    fn accessible_name(&self) -> Option<&str> {
317        self.accessible_name_value.as_deref()
318    }
319
320    fn accessible_role(&self) -> AccessibleRole {
321        AccessibleRole::ProgressBar
322    }
323
324    fn test_id(&self) -> Option<&str> {
325        self.test_id_value.as_deref()
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    // ===== ProgressMode Tests =====
334
335    #[test]
336    fn test_progress_mode_default() {
337        assert_eq!(ProgressMode::default(), ProgressMode::Determinate);
338    }
339
340    #[test]
341    fn test_progress_mode_equality() {
342        assert_eq!(ProgressMode::Determinate, ProgressMode::Determinate);
343        assert_eq!(ProgressMode::Indeterminate, ProgressMode::Indeterminate);
344        assert_ne!(ProgressMode::Determinate, ProgressMode::Indeterminate);
345    }
346
347    // ===== ProgressBar Construction Tests =====
348
349    #[test]
350    fn test_progress_bar_new() {
351        let pb = ProgressBar::new();
352        assert_eq!(pb.get_value(), 0.0);
353        assert_eq!(pb.get_mode(), ProgressMode::Determinate);
354    }
355
356    #[test]
357    fn test_progress_bar_with_value() {
358        let pb = ProgressBar::with_value(0.5);
359        assert_eq!(pb.get_value(), 0.5);
360    }
361
362    #[test]
363    fn test_progress_bar_default() {
364        let pb = ProgressBar::default();
365        assert_eq!(pb.get_value(), 0.0);
366        assert_eq!(pb.get_mode(), ProgressMode::Determinate);
367        assert!(!pb.is_label_shown());
368    }
369
370    #[test]
371    fn test_progress_bar_builder() {
372        let pb = ProgressBar::new()
373            .value(0.75)
374            .min_width(200.0)
375            .height(12.0)
376            .corner_radius(6.0)
377            .track_color(Color::WHITE)
378            .fill_color(Color::new(0.0, 1.0, 0.0, 1.0))
379            .with_label()
380            .label_color(Color::BLACK)
381            .accessible_name("Loading progress")
382            .test_id("main-progress");
383
384        assert_eq!(pb.get_value(), 0.75);
385        assert_eq!(pb.get_min_width(), 200.0);
386        assert_eq!(pb.get_height(), 12.0);
387        assert_eq!(pb.get_corner_radius(), 6.0);
388        assert_eq!(pb.get_track_color(), Color::WHITE);
389        assert_eq!(pb.get_fill_color(), Color::new(0.0, 1.0, 0.0, 1.0));
390        assert!(pb.is_label_shown());
391        assert_eq!(pb.get_label_color(), Color::BLACK);
392        assert_eq!(Widget::accessible_name(&pb), Some("Loading progress"));
393        assert_eq!(Widget::test_id(&pb), Some("main-progress"));
394    }
395
396    // ===== Value Tests =====
397
398    #[test]
399    fn test_progress_bar_value_clamped_min() {
400        let pb = ProgressBar::new().value(-0.5);
401        assert_eq!(pb.get_value(), 0.0);
402    }
403
404    #[test]
405    fn test_progress_bar_value_clamped_max() {
406        let pb = ProgressBar::new().value(1.5);
407        assert_eq!(pb.get_value(), 1.0);
408    }
409
410    #[test]
411    fn test_progress_bar_set_value() {
412        let mut pb = ProgressBar::new();
413        pb.set_value(0.6);
414        assert_eq!(pb.get_value(), 0.6);
415    }
416
417    #[test]
418    fn test_progress_bar_set_value_clamped() {
419        let mut pb = ProgressBar::new();
420        pb.set_value(2.0);
421        assert_eq!(pb.get_value(), 1.0);
422        pb.set_value(-1.0);
423        assert_eq!(pb.get_value(), 0.0);
424    }
425
426    #[test]
427    fn test_progress_bar_increment() {
428        let mut pb = ProgressBar::with_value(0.3);
429        pb.increment(0.2);
430        assert!((pb.get_value() - 0.5).abs() < 0.001);
431    }
432
433    #[test]
434    fn test_progress_bar_increment_clamped() {
435        let mut pb = ProgressBar::with_value(0.9);
436        pb.increment(0.5);
437        assert_eq!(pb.get_value(), 1.0);
438    }
439
440    #[test]
441    fn test_progress_bar_percentage() {
442        let pb = ProgressBar::with_value(0.0);
443        assert_eq!(pb.percentage(), 0);
444
445        let pb = ProgressBar::with_value(0.5);
446        assert_eq!(pb.percentage(), 50);
447
448        let pb = ProgressBar::with_value(1.0);
449        assert_eq!(pb.percentage(), 100);
450
451        let pb = ProgressBar::with_value(0.333);
452        assert_eq!(pb.percentage(), 33);
453    }
454
455    // ===== Mode Tests =====
456
457    #[test]
458    fn test_progress_bar_mode() {
459        let pb = ProgressBar::new().mode(ProgressMode::Indeterminate);
460        assert_eq!(pb.get_mode(), ProgressMode::Indeterminate);
461    }
462
463    #[test]
464    fn test_progress_bar_indeterminate() {
465        let pb = ProgressBar::new().indeterminate();
466        assert!(pb.is_indeterminate());
467    }
468
469    #[test]
470    fn test_progress_bar_is_complete() {
471        let pb = ProgressBar::with_value(1.0);
472        assert!(pb.is_complete());
473
474        let pb = ProgressBar::with_value(0.99);
475        assert!(!pb.is_complete());
476
477        let pb = ProgressBar::with_value(1.0).indeterminate();
478        assert!(!pb.is_complete());
479    }
480
481    // ===== Dimension Tests =====
482
483    #[test]
484    fn test_progress_bar_min_width_min() {
485        let pb = ProgressBar::new().min_width(5.0);
486        assert_eq!(pb.get_min_width(), 20.0);
487    }
488
489    #[test]
490    fn test_progress_bar_height_min() {
491        let pb = ProgressBar::new().height(1.0);
492        assert_eq!(pb.get_height(), 4.0);
493    }
494
495    #[test]
496    fn test_progress_bar_corner_radius() {
497        let pb = ProgressBar::new().corner_radius(10.0);
498        assert_eq!(pb.get_corner_radius(), 10.0);
499    }
500
501    #[test]
502    fn test_progress_bar_corner_radius_min() {
503        let pb = ProgressBar::new().corner_radius(-5.0);
504        assert_eq!(pb.get_corner_radius(), 0.0);
505    }
506
507    // ===== Color Tests =====
508
509    #[test]
510    fn test_progress_bar_colors() {
511        let track = Color::new(0.78, 0.78, 0.78, 1.0);
512        let fill = Color::new(0.0, 0.5, 1.0, 1.0);
513        let label = Color::new(0.2, 0.2, 0.2, 1.0);
514
515        let pb = ProgressBar::new()
516            .track_color(track)
517            .fill_color(fill)
518            .label_color(label);
519
520        assert_eq!(pb.get_track_color(), track);
521        assert_eq!(pb.get_fill_color(), fill);
522        assert_eq!(pb.get_label_color(), label);
523    }
524
525    // ===== Label Tests =====
526
527    #[test]
528    fn test_progress_bar_show_label() {
529        let pb = ProgressBar::new().show_label(true);
530        assert!(pb.is_label_shown());
531
532        let pb = ProgressBar::new().show_label(false);
533        assert!(!pb.is_label_shown());
534    }
535
536    #[test]
537    fn test_progress_bar_with_label() {
538        let pb = ProgressBar::new().with_label();
539        assert!(pb.is_label_shown());
540    }
541
542    // ===== Fill Width Tests =====
543
544    #[test]
545    fn test_progress_bar_fill_width() {
546        let pb = ProgressBar::with_value(0.5);
547        assert_eq!(pb.fill_width(100.0), 50.0);
548
549        let pb = ProgressBar::with_value(0.0);
550        assert_eq!(pb.fill_width(100.0), 0.0);
551
552        let pb = ProgressBar::with_value(1.0);
553        assert_eq!(pb.fill_width(100.0), 100.0);
554    }
555
556    // ===== Widget Trait Tests =====
557
558    #[test]
559    fn test_progress_bar_type_id() {
560        let pb = ProgressBar::new();
561        assert_eq!(Widget::type_id(&pb), TypeId::of::<ProgressBar>());
562    }
563
564    #[test]
565    fn test_progress_bar_measure() {
566        let pb = ProgressBar::new().min_width(150.0).height(10.0);
567        let size = pb.measure(Constraints::loose(Size::new(300.0, 100.0)));
568        assert_eq!(size.width, 150.0);
569        assert_eq!(size.height, 10.0);
570    }
571
572    #[test]
573    fn test_progress_bar_measure_with_label() {
574        let pb = ProgressBar::new()
575            .min_width(150.0)
576            .height(10.0)
577            .with_label();
578        let size = pb.measure(Constraints::loose(Size::new(300.0, 100.0)));
579        assert_eq!(size.width, 150.0);
580        assert_eq!(size.height, 30.0); // height + 20 for label
581    }
582
583    #[test]
584    fn test_progress_bar_layout() {
585        let mut pb = ProgressBar::new();
586        let bounds = Rect::new(10.0, 20.0, 200.0, 8.0);
587        let result = pb.layout(bounds);
588        assert_eq!(result.size, Size::new(200.0, 8.0));
589        assert_eq!(pb.bounds, bounds);
590    }
591
592    #[test]
593    fn test_progress_bar_children() {
594        let pb = ProgressBar::new();
595        assert!(pb.children().is_empty());
596    }
597
598    #[test]
599    fn test_progress_bar_is_interactive() {
600        let pb = ProgressBar::new();
601        assert!(!pb.is_interactive());
602    }
603
604    #[test]
605    fn test_progress_bar_is_focusable() {
606        let pb = ProgressBar::new();
607        assert!(!pb.is_focusable());
608    }
609
610    #[test]
611    fn test_progress_bar_accessible_role() {
612        let pb = ProgressBar::new();
613        assert_eq!(pb.accessible_role(), AccessibleRole::ProgressBar);
614    }
615
616    #[test]
617    fn test_progress_bar_accessible_name() {
618        let pb = ProgressBar::new().accessible_name("Download progress");
619        assert_eq!(Widget::accessible_name(&pb), Some("Download progress"));
620    }
621
622    #[test]
623    fn test_progress_bar_accessible_name_none() {
624        let pb = ProgressBar::new();
625        assert_eq!(Widget::accessible_name(&pb), None);
626    }
627
628    #[test]
629    fn test_progress_bar_test_id() {
630        let pb = ProgressBar::new().test_id("upload-progress");
631        assert_eq!(Widget::test_id(&pb), Some("upload-progress"));
632    }
633
634    // ===== Paint Tests =====
635
636    use presentar_core::draw::DrawCommand;
637    use presentar_core::RecordingCanvas;
638
639    #[test]
640    fn test_progress_bar_paint_draws_track() {
641        let mut pb = ProgressBar::new();
642        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
643
644        let mut canvas = RecordingCanvas::new();
645        pb.paint(&mut canvas);
646
647        // Should draw at least track
648        assert!(canvas.command_count() >= 1);
649
650        match &canvas.commands()[0] {
651            DrawCommand::Rect { bounds, style, .. } => {
652                assert_eq!(bounds.width, 200.0);
653                assert_eq!(bounds.height, 8.0);
654                assert!(style.fill.is_some());
655            }
656            _ => panic!("Expected Rect command for track"),
657        }
658    }
659
660    #[test]
661    fn test_progress_bar_paint_zero_percent() {
662        let mut pb = ProgressBar::with_value(0.0);
663        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
664
665        let mut canvas = RecordingCanvas::new();
666        pb.paint(&mut canvas);
667
668        // Only track, no fill when value is 0
669        assert_eq!(canvas.command_count(), 1);
670    }
671
672    #[test]
673    fn test_progress_bar_paint_50_percent() {
674        let mut pb = ProgressBar::with_value(0.5);
675        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
676
677        let mut canvas = RecordingCanvas::new();
678        pb.paint(&mut canvas);
679
680        // Track + fill
681        assert_eq!(canvas.command_count(), 2);
682
683        // Check fill width is 50%
684        match &canvas.commands()[1] {
685            DrawCommand::Rect { bounds, .. } => {
686                assert_eq!(bounds.width, 100.0);
687            }
688            _ => panic!("Expected Rect command for fill"),
689        }
690    }
691
692    #[test]
693    fn test_progress_bar_paint_100_percent() {
694        let mut pb = ProgressBar::with_value(1.0);
695        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
696
697        let mut canvas = RecordingCanvas::new();
698        pb.paint(&mut canvas);
699
700        // Track + fill
701        assert_eq!(canvas.command_count(), 2);
702
703        // Check fill width is 100%
704        match &canvas.commands()[1] {
705            DrawCommand::Rect { bounds, .. } => {
706                assert_eq!(bounds.width, 200.0);
707            }
708            _ => panic!("Expected Rect command for fill"),
709        }
710    }
711
712    #[test]
713    fn test_progress_bar_paint_25_percent() {
714        let mut pb = ProgressBar::with_value(0.25);
715        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
716
717        let mut canvas = RecordingCanvas::new();
718        pb.paint(&mut canvas);
719
720        match &canvas.commands()[1] {
721            DrawCommand::Rect { bounds, .. } => {
722                assert_eq!(bounds.width, 50.0);
723            }
724            _ => panic!("Expected Rect command for fill"),
725        }
726    }
727
728    #[test]
729    fn test_progress_bar_paint_indeterminate_no_fill() {
730        let mut pb = ProgressBar::with_value(0.5).indeterminate();
731        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
732
733        let mut canvas = RecordingCanvas::new();
734        pb.paint(&mut canvas);
735
736        // Indeterminate mode: only track, no fill
737        assert_eq!(canvas.command_count(), 1);
738    }
739
740    #[test]
741    fn test_progress_bar_paint_uses_track_color() {
742        let track_color = Color::new(0.9, 0.9, 0.9, 1.0);
743        let mut pb = ProgressBar::new().track_color(track_color);
744        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
745
746        let mut canvas = RecordingCanvas::new();
747        pb.paint(&mut canvas);
748
749        match &canvas.commands()[0] {
750            DrawCommand::Rect { style, .. } => {
751                assert_eq!(style.fill, Some(track_color));
752            }
753            _ => panic!("Expected Rect command"),
754        }
755    }
756
757    #[test]
758    fn test_progress_bar_paint_uses_fill_color() {
759        let fill_color = Color::new(0.0, 0.8, 0.0, 1.0);
760        let mut pb = ProgressBar::with_value(0.5).fill_color(fill_color);
761        pb.layout(Rect::new(0.0, 0.0, 200.0, 8.0));
762
763        let mut canvas = RecordingCanvas::new();
764        pb.paint(&mut canvas);
765
766        match &canvas.commands()[1] {
767            DrawCommand::Rect { style, .. } => {
768                assert_eq!(style.fill, Some(fill_color));
769            }
770            _ => panic!("Expected Rect command"),
771        }
772    }
773
774    #[test]
775    fn test_progress_bar_paint_position_from_layout() {
776        let mut pb = ProgressBar::with_value(0.5);
777        pb.layout(Rect::new(50.0, 100.0, 200.0, 8.0));
778
779        let mut canvas = RecordingCanvas::new();
780        pb.paint(&mut canvas);
781
782        // Track position
783        match &canvas.commands()[0] {
784            DrawCommand::Rect { bounds, .. } => {
785                assert_eq!(bounds.x, 50.0);
786                assert_eq!(bounds.y, 100.0);
787            }
788            _ => panic!("Expected Rect command"),
789        }
790
791        // Fill position
792        match &canvas.commands()[1] {
793            DrawCommand::Rect { bounds, .. } => {
794                assert_eq!(bounds.x, 50.0);
795                assert_eq!(bounds.y, 100.0);
796            }
797            _ => panic!("Expected Rect command"),
798        }
799    }
800
801    #[test]
802    fn test_progress_bar_paint_uses_height() {
803        let mut pb = ProgressBar::new().height(16.0);
804        pb.layout(Rect::new(0.0, 0.0, 200.0, 16.0));
805
806        let mut canvas = RecordingCanvas::new();
807        pb.paint(&mut canvas);
808
809        match &canvas.commands()[0] {
810            DrawCommand::Rect { bounds, .. } => {
811                assert_eq!(bounds.height, 16.0);
812            }
813            _ => panic!("Expected Rect command"),
814        }
815    }
816}