zest_widget/widget/
spinner.rs1use super::Widget;
11use core::marker::PhantomData;
12use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
13use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
14use zest_theme::Theme;
15
16pub struct Spinner<'a, C: PixelColor, M: Clone> {
18 rect: Rectangle,
19 angle: i32,
21 arc_deg: i32,
23 width: u32,
25 track: bool,
27 track_color: Option<C>,
28 arc_color: Option<C>,
29 w: Length,
30 h: Length,
31 _phantom: PhantomData<&'a M>,
32}
33
34impl<'a, C: PixelColor, M: Clone> Spinner<'a, C, M> {
35 pub fn new(angle: i32) -> Self {
38 Self {
39 rect: Rectangle::zero(),
40 angle,
41 arc_deg: 90,
42 width: 5,
43 track: true,
44 track_color: None,
45 arc_color: None,
46 w: Length::Fill,
47 h: Length::Fill,
48 _phantom: PhantomData,
49 }
50 }
51
52 #[must_use]
54 pub fn angle(mut self, angle: i32) -> Self {
55 self.angle = angle;
56 self
57 }
58
59 #[must_use]
61 pub fn arc_deg(mut self, arc_deg: i32) -> Self {
62 self.arc_deg = arc_deg;
63 self
64 }
65
66 #[must_use]
68 pub fn width_px(mut self, width: u32) -> Self {
69 self.width = width;
70 self
71 }
72
73 #[must_use]
75 pub fn track(mut self, on: bool) -> Self {
76 self.track = on;
77 self
78 }
79
80 #[must_use]
83 pub fn track_color(mut self, color: C) -> Self {
84 self.track_color = Some(color);
85 self
86 }
87
88 #[must_use]
91 pub fn arc_color(mut self, color: C) -> Self {
92 self.arc_color = Some(color);
93 self
94 }
95
96 #[must_use]
98 pub fn width(mut self, width: impl Into<Length>) -> Self {
99 self.w = width.into();
100 self
101 }
102
103 #[must_use]
105 pub fn height(mut self, height: impl Into<Length>) -> Self {
106 self.h = height.into();
107 self
108 }
109
110 fn center(&self) -> Point {
111 Point::new(
112 self.rect.top_left.x + self.rect.size.width as i32 / 2,
113 self.rect.top_left.y + self.rect.size.height as i32 / 2,
114 )
115 }
116
117 fn radius(&self) -> u32 {
118 let smaller = self.rect.size.width.min(self.rect.size.height);
119 (smaller / 2).saturating_sub(self.width.div_ceil(2))
120 }
121}
122
123impl<'a, C: PixelColor, M: Clone> Widget<C, M> for Spinner<'a, C, M> {
124 fn measure(&mut self, constraints: Constraints) -> Size {
125 let w = self.w.resolve(constraints.max.width, constraints.max.width);
126 let h = self
127 .h
128 .resolve(constraints.max.height, constraints.max.height);
129 constraints.clamp(Size::new(w, h))
130 }
131
132 fn preferred_size(&self) -> (Length, Length) {
133 (self.w, self.h)
134 }
135
136 fn arrange(&mut self, rect: Rectangle) {
137 self.rect = rect;
138 }
139
140 fn rect(&self) -> Rectangle {
141 self.rect
142 }
143
144 fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
145 None
146 }
147
148 fn draw<'t>(
149 &self,
150 renderer: &mut dyn Renderer<C>,
151 theme: &Theme<'t, C>,
152 ) -> Result<(), RenderError> {
153 let center = self.center();
154 let radius = self.radius();
155 if radius == 0 {
156 return Ok(());
157 }
158 let track = self.track_color.unwrap_or(theme.background.divider);
159 let arc = self.arc_color.unwrap_or(theme.accent.base);
160
161 if self.track {
162 renderer.stroke_arc(center, radius, 0, 360, self.width, track)?;
163 }
164 renderer.stroke_arc(center, radius, self.angle, self.arc_deg, self.width, arc)?;
165 Ok(())
166 }
167}