zest_widget/widget/
arc.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
18pub struct Arc<'a, C: PixelColor, M: Clone> {
20 rect: Rectangle,
21 value: f32,
23 min: f32,
24 max: f32,
25 start_deg: i32,
27 sweep_deg: i32,
30 width: u32,
32 track_color: Option<C>,
33 value_color: Option<C>,
34 w: Length,
35 h: Length,
36 _phantom: PhantomData<&'a M>,
37}
38
39impl<'a, C: PixelColor, M: Clone> Arc<'a, C, M> {
40 pub fn new(value: f32, min: f32, max: f32) -> Self {
44 Self {
45 rect: Rectangle::zero(),
46 value,
47 min,
48 max,
49 start_deg: 225,
50 sweep_deg: -270,
51 width: 6,
52 track_color: None,
53 value_color: None,
54 w: Length::Fill,
55 h: Length::Fill,
56 _phantom: PhantomData,
57 }
58 }
59
60 #[must_use]
62 pub fn value(mut self, value: f32) -> Self {
63 self.value = value;
64 self
65 }
66
67 #[must_use]
69 pub fn range(mut self, min: f32, max: f32) -> Self {
70 if min <= max {
71 self.min = min;
72 self.max = max;
73 } else {
74 self.min = max;
75 self.max = min;
76 }
77 self
78 }
79
80 #[must_use]
82 pub fn start_deg(mut self, start_deg: i32) -> Self {
83 self.start_deg = start_deg;
84 self
85 }
86
87 #[must_use]
90 pub fn sweep_deg(mut self, sweep_deg: i32) -> Self {
91 self.sweep_deg = sweep_deg;
92 self
93 }
94
95 #[must_use]
97 pub fn width_px(mut self, width: u32) -> Self {
98 self.width = width;
99 self
100 }
101
102 #[must_use]
105 pub fn track_color(mut self, color: C) -> Self {
106 self.track_color = Some(color);
107 self
108 }
109
110 #[must_use]
112 pub fn value_color(mut self, color: C) -> Self {
113 self.value_color = Some(color);
114 self
115 }
116
117 #[must_use]
119 pub fn width(mut self, width: impl Into<Length>) -> Self {
120 self.w = width.into();
121 self
122 }
123
124 #[must_use]
126 pub fn height(mut self, height: impl Into<Length>) -> Self {
127 self.h = height.into();
128 self
129 }
130
131 fn fraction(&self) -> f32 {
133 let span = self.max - self.min;
134 if span <= 0.0 {
135 return 0.0;
136 }
137 ((self.value - self.min) / span).clamp(0.0, 1.0)
138 }
139
140 fn center(&self) -> Point {
142 Point::new(
143 self.rect.top_left.x + self.rect.size.width as i32 / 2,
144 self.rect.top_left.y + self.rect.size.height as i32 / 2,
145 )
146 }
147
148 fn radius(&self) -> u32 {
150 let smaller = self.rect.size.width.min(self.rect.size.height);
151 (smaller / 2).saturating_sub(self.width.div_ceil(2))
152 }
153}
154
155impl<'a, C: PixelColor, M: Clone> Widget<C, M> for Arc<'a, C, M> {
156 fn measure(&mut self, constraints: Constraints) -> Size {
157 let w = self.w.resolve(constraints.max.width, constraints.max.width);
158 let h = self
159 .h
160 .resolve(constraints.max.height, constraints.max.height);
161 constraints.clamp(Size::new(w, h))
162 }
163
164 fn preferred_size(&self) -> (Length, Length) {
165 (self.w, self.h)
166 }
167
168 fn arrange(&mut self, rect: Rectangle) {
169 self.rect = rect;
170 }
171
172 fn rect(&self) -> Rectangle {
173 self.rect
174 }
175
176 fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
177 None
178 }
179
180 fn draw<'t>(
181 &self,
182 renderer: &mut dyn Renderer<C>,
183 theme: &Theme<'t, C>,
184 ) -> Result<(), RenderError> {
185 let center = self.center();
186 let radius = self.radius();
187 if radius == 0 {
188 return Ok(());
189 }
190
191 let track = self.track_color.unwrap_or(theme.background.divider);
192 let value = self.value_color.unwrap_or(theme.accent.base);
193
194 renderer.stroke_arc(
196 center,
197 radius,
198 self.start_deg,
199 self.sweep_deg,
200 self.width,
201 track,
202 )?;
203
204 let value_sweep = (self.sweep_deg as f32 * self.fraction()) as i32;
206 if value_sweep != 0 {
207 renderer.stroke_arc(
208 center,
209 radius,
210 self.start_deg,
211 value_sweep,
212 self.width,
213 value,
214 )?;
215 }
216
217 Ok(())
218 }
219}