zest_widget/widget/
progress_bar.rs1use super::Widget;
13use core::marker::PhantomData;
14use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
15use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
16use zest_theme::Theme;
17
18const BAR_H: u32 = 10;
20const INTRINSIC_W: u32 = 160;
22
23pub struct ProgressBar<C: PixelColor, M: Clone> {
25 rect: Rectangle,
26 value: f32,
27 min: f32,
28 max: f32,
29 color: Option<C>,
30 width: Length,
31 height: Length,
32 _phantom: PhantomData<M>,
33}
34
35impl<C: PixelColor, M: Clone> ProgressBar<C, M> {
36 pub fn new(value: f32) -> Self {
38 Self {
39 rect: Rectangle::zero(),
40 value,
41 min: 0.0,
42 max: 1.0,
43 color: None,
44 width: Length::Fill,
45 height: Length::Fixed(BAR_H),
46 _phantom: PhantomData,
47 }
48 }
49
50 #[must_use]
52 pub fn range(mut self, min: f32, max: f32) -> Self {
53 self.min = min;
54 self.max = max;
55 self
56 }
57
58 #[must_use]
60 pub fn color(mut self, color: C) -> Self {
61 self.color = Some(color);
62 self
63 }
64
65 #[must_use]
67 pub fn width(mut self, width: impl Into<Length>) -> Self {
68 self.width = width.into();
69 self
70 }
71
72 #[must_use]
74 pub fn height(mut self, height: impl Into<Length>) -> Self {
75 self.height = height.into();
76 self
77 }
78
79 fn fraction(&self) -> f32 {
81 if self.max <= self.min {
82 0.0
83 } else {
84 ((self.value - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
85 }
86 }
87}
88
89impl<C: PixelColor, M: Clone> Widget<C, M> for ProgressBar<C, M> {
90 fn measure(&mut self, constraints: Constraints) -> Size {
91 let w = self.width.resolve(INTRINSIC_W, constraints.max.width);
92 let h = self.height.resolve(BAR_H, constraints.max.height);
93 constraints.clamp(Size::new(w, h))
94 }
95
96 fn preferred_size(&self) -> (Length, Length) {
97 (self.width, self.height)
98 }
99
100 fn arrange(&mut self, rect: Rectangle) {
101 self.rect = rect;
102 }
103
104 fn rect(&self) -> Rectangle {
105 self.rect
106 }
107
108 fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
109 None
110 }
111
112 fn draw<'t>(
113 &self,
114 renderer: &mut dyn Renderer<C>,
115 theme: &Theme<'t, C>,
116 ) -> Result<(), RenderError> {
117 renderer.fill_rect(self.rect, theme.background.divider)?;
119
120 let fill_w = (self.rect.size.width as f32 * self.fraction()) as u32;
122 if fill_w > 0 {
123 let fill_color = self.color.unwrap_or(theme.accent.base);
124 let filled =
125 Rectangle::new(self.rect.top_left, Size::new(fill_w, self.rect.size.height));
126 renderer.fill_rect(filled, fill_color)?;
127 }
128
129 Ok(())
130 }
131}