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