1#[cfg(not(feature = "std"))]
10use crate::math::F32Ext as _;
11
12use crate::{
13 geometry::Rect,
14 render::{PixelRead, RenderCtx},
15};
16use embedded_graphics_core::{
17 draw_target::DrawTarget,
18 pixelcolor::{Rgb565, RgbColor, WebColors},
19};
20
21#[derive(Debug, Clone, Copy)]
25pub struct BusyWheel {
26 pub center_x: i32,
27 pub center_y: i32,
28 pub radius: u32,
29 pub dot_count: u8,
30 pub dot_radius: u32,
31 pub phase: f32,
32 pub color: Rgb565,
33 pub opacity: u8,
34}
35
36impl BusyWheel {
37 pub fn new(center_x: i32, center_y: i32, radius: u32) -> Self {
38 Self {
39 center_x,
40 center_y,
41 radius,
42 dot_count: 8,
43 dot_radius: 3,
44 phase: 0.0,
45 color: Rgb565::CSS_CYAN,
46 opacity: 255,
47 }
48 }
49
50 pub fn draw<D, C>(&self, ctx: &mut RenderCtx<D, C>) -> Result<(), D::Error>
51 where
52 D: DrawTarget<Color = Rgb565> + PixelRead,
53 C: crate::render::Compositor<D>,
54 {
55 if self.opacity == 0 || self.dot_count == 0 {
56 return Ok(());
57 }
58
59 let step_angle = 2.0 * core::f32::consts::PI / self.dot_count as f32;
60 let dr = self.dot_radius as i32;
61
62 for i in 0..self.dot_count {
63 let angle = self.phase + (i as f32 * step_angle);
64 let dx = (angle.cos() * self.radius as f32) as i32;
65 let dy = (angle.sin() * self.radius as f32) as i32;
66 let px = self.center_x + dx;
67 let py = self.center_y + dy;
68
69 let dot_rect = Rect::new(px - dr, py - dr, (dr * 2 + 1) as u32, (dr * 2 + 1) as u32);
70 ctx.fill_rounded_rect(dot_rect, dr as u8, self.color)?;
71 }
72
73 Ok(())
74 }
75}
76
77#[derive(Debug, Clone, Copy)]
81pub struct GaugeWidget {
82 pub bounds: Rect,
83 pub min_val: f32,
84 pub max_val: f32,
85 pub current_val: f32,
86 pub needle_color: Rgb565,
87 pub dial_color: Rgb565,
88 pub arc_color: Rgb565,
89}
90
91impl GaugeWidget {
92 pub fn new(bounds: Rect, min_val: f32, max_val: f32) -> Self {
93 Self {
94 bounds,
95 min_val,
96 max_val,
97 current_val: min_val,
98 needle_color: Rgb565::RED,
99 dial_color: Rgb565::new(4, 8, 4),
100 arc_color: Rgb565::GREEN,
101 }
102 }
103
104 pub fn draw<D, C>(&self, ctx: &mut RenderCtx<D, C>) -> Result<(), D::Error>
105 where
106 D: DrawTarget<Color = Rgb565> + PixelRead,
107 C: crate::render::Compositor<D>,
108 {
109 ctx.fill_rounded_rect(self.bounds, 6, self.dial_color)?;
111
112 let center_x = self.bounds.x + (self.bounds.w as i32 / 2);
113 let center_y = self.bounds.y + (self.bounds.h as i32 / 2);
114 let radius = (self.bounds.w.min(self.bounds.h) as f32 * 0.4) as i32;
115
116 let range = (self.max_val - self.min_val).max(0.001);
118 let norm_val = ((self.current_val - self.min_val) / range).clamp(0.0, 1.0);
119
120 let angle_deg = -135.0 + norm_val * 270.0;
122 let angle_rad = angle_deg * core::f32::consts::PI / 180.0;
123
124 let nx = center_x + (angle_rad.cos() * radius as f32) as i32;
125 let ny = center_y + (angle_rad.sin() * radius as f32) as i32;
126
127 ctx.draw_line(center_x, center_y, nx, ny, self.needle_color)?;
129
130 let pivot_rect = Rect::new(center_x - 2, center_y - 2, 5, 5);
132 ctx.fill_rounded_rect(pivot_rect, 2, Rgb565::WHITE)?;
133
134 Ok(())
135 }
136}
137
138#[cfg(feature = "embedded-dsp")]
140pub struct TouchInputFilter {
141 coeffs: [f32; 5],
142 state_x: [f32; 4],
143 state_y: [f32; 4],
144 initialized: bool,
145}
146
147#[cfg(feature = "embedded-dsp")]
148impl TouchInputFilter {
149 pub fn new(_cutoff_freq_ratio: f32) -> Self {
151 let coeffs = [0.0675, 0.1349, 0.0675, 1.1430, -0.4128];
152 Self {
153 coeffs,
154 state_x: [0.0; 4],
155 state_y: [0.0; 4],
156 initialized: false,
157 }
158 }
159
160 pub fn filter(&mut self, raw_x: f32, raw_y: f32) -> (f32, f32) {
162 if !self.initialized {
163 self.state_x.fill(raw_x);
164 self.state_y.fill(raw_y);
165 self.initialized = true;
166 }
167
168 let mut inst_x = embedded_dsp::filtering::BiquadCascadeInstanceF32 {
169 num_stages: 1,
170 coeffs: &self.coeffs,
171 state: &mut self.state_x,
172 };
173 let mut out_x = 0.0f32;
174 embedded_dsp::filtering::biquad_cascade_df1_f32(
175 &mut inst_x,
176 &[raw_x],
177 core::slice::from_mut(&mut out_x),
178 );
179
180 let mut inst_y = embedded_dsp::filtering::BiquadCascadeInstanceF32 {
181 num_stages: 1,
182 coeffs: &self.coeffs,
183 state: &mut self.state_y,
184 };
185 let mut out_y = 0.0f32;
186 embedded_dsp::filtering::biquad_cascade_df1_f32(
187 &mut inst_y,
188 &[raw_y],
189 core::slice::from_mut(&mut out_y),
190 );
191
192 (out_x, out_y)
193 }
194
195 pub fn reset(&mut self) {
197 self.state_x.fill(0.0);
198 self.state_y.fill(0.0);
199 self.initialized = false;
200 }
201}
202
203#[cfg(feature = "embedded-dsp")]
205pub struct SpectrumAnalyzerWidget<'a> {
206 pub bounds: Rect,
207 pub signal_samples: &'a [f32],
208 pub bar_color: Rgb565,
209 pub bg_color: Rgb565,
210}
211
212#[cfg(feature = "embedded-dsp")]
213impl<'a> SpectrumAnalyzerWidget<'a> {
214 pub fn new(bounds: Rect, signal_samples: &'a [f32]) -> Self {
215 Self {
216 bounds,
217 signal_samples,
218 bar_color: Rgb565::CSS_LIME_GREEN,
219 bg_color: Rgb565::new(2, 4, 2),
220 }
221 }
222
223 pub fn draw<D, C>(&self, ctx: &mut RenderCtx<D, C>) -> Result<(), D::Error>
224 where
225 D: DrawTarget<Color = Rgb565> + PixelRead,
226 C: crate::render::Compositor<D>,
227 {
228 ctx.fill_rounded_rect(self.bounds, 4, self.bg_color)?;
229
230 if self.signal_samples.is_empty() {
231 return Ok(());
232 }
233
234 let mut rms = 0.0f32;
236 let _ = embedded_dsp::statistics::rms_f32(self.signal_samples, &mut rms);
237
238 let mut max_val = 0.0f32;
239 let mut idx = 0;
240 let _ = embedded_dsp::statistics::max_f32(self.signal_samples, &mut max_val, &mut idx);
241
242 let bar_count = (self.bounds.w / 6).max(1) as usize;
243 let chunk_size = (self.signal_samples.len() / bar_count).max(1);
244
245 for i in 0..bar_count {
246 let start = i * chunk_size;
247 let end = (start + chunk_size).min(self.signal_samples.len());
248 let chunk = &self.signal_samples[start..end];
249 let mut chunk_rms = 0.0f32;
250 if !chunk.is_empty() {
251 let _ = embedded_dsp::statistics::rms_f32(chunk, &mut chunk_rms);
252 }
253
254 let norm_h = (chunk_rms / (max_val.max(rms).max(0.001))).clamp(0.05, 1.0);
255 let bar_h = (self.bounds.h as f32 * norm_h) as u32;
256
257 let bx = self.bounds.x + (i as i32 * 6);
258 let by = self.bounds.bottom() - bar_h as i32;
259
260 let bar_rect = Rect::new(bx + 1, by, 4, bar_h);
261 ctx.fill_rect(bar_rect, self.bar_color)?;
262 }
263
264 Ok(())
265 }
266}