1use std::f32::consts::PI;
4
5use crate::render::RenderBackend;
6use crate::theme::Theme;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum WidgetType {
11 Knob,
12 Slider,
13 Toggle,
14 Selector,
15 Meter,
16 XYPad,
17}
18
19pub fn draw_knob(
25 ctx: &mut dyn RenderBackend,
26 x: f32,
27 y: f32,
28 size: f32,
29 value: f32,
30 label: &str,
31 value_text: &str,
32 theme: &Theme,
33 highlighted: bool,
34) {
35 let cx = x + size / 2.0;
36 let cy = y + size / 2.0 - 8.0; let radius = size / 2.0 - 6.0;
38
39 let start_angle = 0.75 * PI; let end_angle = 2.25 * PI; let arc_start = start_angle;
44 let arc_end = end_angle;
45
46 ctx.stroke_arc(cx, cy, radius, arc_start, arc_end, theme.knob_track, 3.0);
48
49 let value_angle = arc_start + value * (arc_end - arc_start);
51 if value > 0.01 {
52 ctx.stroke_arc(cx, cy, radius, arc_start, value_angle, theme.knob_fill, 3.0);
53 }
54
55 let pointer_len = radius * 0.6;
57 let px = cx + pointer_len * value_angle.cos();
58 let py = cy + pointer_len * value_angle.sin();
59 ctx.draw_line(cx, cy, px, py, theme.knob_pointer, 2.0);
60
61 ctx.fill_circle(cx, cy, 3.0, theme.surface);
63
64 if highlighted {
66 ctx.stroke_arc(cx, cy, radius + 3.0, arc_start, arc_end, theme.accent, 1.5);
67 }
68
69 let val_size = 10.0;
71 let val_w = ctx.text_width(value_text, val_size);
72 ctx.draw_text(value_text, cx - val_w / 2.0, y + size - 2.0, val_size, theme.text);
73
74 let label_size = 9.0;
76 let label_w = ctx.text_width(label, label_size);
77 ctx.draw_text(label, cx - label_w / 2.0, y + size + 10.0, label_size, theme.text_dim);
78}
79
80pub fn draw_header(
82 ctx: &mut dyn RenderBackend,
83 x: f32,
84 y: f32,
85 w: f32,
86 h: f32,
87 title: &str,
88 version: &str,
89 theme: &Theme,
90) {
91 ctx.fill_rect(x, y, w, h, theme.header_bg);
92
93 let title_size = 12.0;
94 ctx.draw_text(
95 title,
96 x + 10.0,
97 y + (h - title_size) / 2.0,
98 title_size,
99 theme.header_text,
100 );
101
102 let ver_size = 9.0;
103 let ver_w = ctx.text_width(version, ver_size);
104 ctx.draw_text(
105 version,
106 x + w - ver_w - 10.0,
107 y + (h - ver_size) / 2.0,
108 ver_size,
109 theme.text_dim,
110 );
111}
112
113pub fn draw_slider(
117 ctx: &mut dyn RenderBackend,
118 x: f32,
119 y: f32,
120 width: f32,
121 height: f32,
122 value: f32,
123 label: &str,
124 value_text: &str,
125 theme: &Theme,
126 highlighted: bool,
127) {
128 let track_y = y + height / 2.0 - 8.0;
129 let track_h = 4.0;
130 let margin = 6.0;
131 let track_w = width - margin * 2.0;
132
133 ctx.fill_rect(x + margin, track_y, track_w, track_h, theme.knob_track);
135
136 let fill_w = track_w * value;
138 if fill_w > 0.5 {
139 ctx.fill_rect(x + margin, track_y, fill_w, track_h, theme.knob_fill);
140 }
141
142 let thumb_x = x + margin + fill_w;
144 let thumb_r = 6.0;
145 ctx.fill_circle(thumb_x, track_y + track_h / 2.0, thumb_r, theme.knob_pointer);
146 if highlighted {
147 ctx.fill_circle(thumb_x, track_y + track_h / 2.0, thumb_r + 2.0, theme.accent);
148 ctx.fill_circle(thumb_x, track_y + track_h / 2.0, thumb_r, theme.knob_pointer);
149 }
150
151 let val_size = 10.0;
153 let cx = x + width / 2.0;
154 let val_w = ctx.text_width(value_text, val_size);
155 ctx.draw_text(value_text, cx - val_w / 2.0, y + height - 2.0, val_size, theme.text);
156
157 let label_size = 9.0;
159 let label_w = ctx.text_width(label, label_size);
160 ctx.draw_text(label, cx - label_w / 2.0, y + height + 10.0, label_size, theme.text_dim);
161}
162
163pub fn draw_toggle(
167 ctx: &mut dyn RenderBackend,
168 x: f32,
169 y: f32,
170 width: f32,
171 height: f32,
172 value: f32,
173 label: &str,
174 value_text: &str,
175 theme: &Theme,
176 highlighted: bool,
177) {
178 let is_on = value > 0.5;
179 let cx = x + width / 2.0;
180 let cy = y + height / 2.0 - 8.0;
181
182 let track_w = 32.0;
184 let track_h = 16.0;
185 let track_x = cx - track_w / 2.0;
186 let track_y = cy - track_h / 2.0;
187 let bg = if is_on { theme.knob_fill } else { theme.knob_track };
188 ctx.fill_rect(track_x, track_y, track_w, track_h, bg);
189
190 let thumb_x = if is_on {
192 track_x + track_w - track_h / 2.0
193 } else {
194 track_x + track_h / 2.0
195 };
196 ctx.fill_circle(thumb_x, cy, track_h / 2.0 - 2.0, theme.knob_pointer);
197
198 if highlighted {
199 ctx.fill_rect(track_x - 2.0, track_y - 2.0, track_w + 4.0, track_h + 4.0, theme.accent);
200 ctx.fill_rect(track_x, track_y, track_w, track_h, bg);
201 ctx.fill_circle(thumb_x, cy, track_h / 2.0 - 2.0, theme.knob_pointer);
202 }
203
204 let val_size = 10.0;
206 let val_w = ctx.text_width(value_text, val_size);
207 ctx.draw_text(value_text, cx - val_w / 2.0, y + height - 2.0, val_size, theme.text);
208
209 let label_size = 9.0;
211 let label_w = ctx.text_width(label, label_size);
212 ctx.draw_text(label, cx - label_w / 2.0, y + height + 10.0, label_size, theme.text_dim);
213}
214
215pub fn draw_selector(
219 ctx: &mut dyn RenderBackend,
220 x: f32,
221 y: f32,
222 width: f32,
223 height: f32,
224 _value: f32,
225 label: &str,
226 value_text: &str,
227 theme: &Theme,
228 highlighted: bool,
229) {
230 let cx = x + width / 2.0;
231 let cy = y + height / 2.0 - 8.0;
232
233 let val_size = 10.0;
235 let arrow_size = 8.0;
236 let arrow_pad = 14.0; let val_w = ctx.text_width(value_text, val_size);
238 let box_w = (val_w + arrow_pad * 2.0 + 8.0).max(width - 12.0);
239 let box_h = 20.0;
240 let box_x = cx - box_w / 2.0;
241 let box_y = cy - box_h / 2.0;
242 let bg = if highlighted { theme.accent } else { theme.knob_track };
243 ctx.fill_rect(box_x, box_y, box_w, box_h, bg);
244
245 ctx.draw_text(
247 value_text,
248 cx - val_w / 2.0,
249 cy - val_size / 2.0,
250 val_size,
251 theme.text,
252 );
253
254 ctx.draw_text("<", box_x + 4.0, cy - arrow_size / 2.0, arrow_size, theme.text_dim);
256 let gt_w = ctx.text_width(">", arrow_size);
257 ctx.draw_text(">", box_x + box_w - gt_w - 4.0, cy - arrow_size / 2.0, arrow_size, theme.text_dim);
258
259 let label_size = 9.0;
261 let label_w = ctx.text_width(label, label_size);
262 ctx.draw_text(label, cx - label_w / 2.0, y + height + 10.0, label_size, theme.text_dim);
263}
264
265pub fn draw_meter(
269 ctx: &mut dyn RenderBackend,
270 x: f32,
271 y: f32,
272 width: f32,
273 height: f32,
274 levels: &[f32],
275 label: &str,
276 theme: &Theme,
277) {
278 let cx = x + width / 2.0;
279 let num = levels.len().max(1);
280 let bar_w = 6.0f32;
281 let gap = 2.0f32;
282 let total_bar_w = num as f32 * bar_w + (num as f32 - 1.0).max(0.0) * gap;
283 let bar_h = height - 4.0; let bar_start_x = cx - total_bar_w / 2.0;
285 let bar_y = y + 2.0;
286
287 for (i, &level) in levels.iter().enumerate() {
288 let bx = bar_start_x + i as f32 * (bar_w + gap);
289
290 ctx.fill_rect(bx, bar_y, bar_w, bar_h, theme.knob_track);
292
293 let fill_h = bar_h * level.clamp(0.0, 1.0);
295 if fill_h > 0.5 {
296 let color = if level > 0.9 { theme.accent } else { theme.knob_fill };
297 ctx.fill_rect(bx, bar_y + bar_h - fill_h, bar_w, fill_h, color);
298 }
299 }
300
301 let label_size = 8.0;
303 let label_w = ctx.text_width(label, label_size);
304 ctx.draw_text(label, cx - label_w / 2.0, y + height + 4.0, label_size, theme.text_dim);
305}
306
307pub fn draw_xy_pad(
311 ctx: &mut dyn RenderBackend,
312 x: f32,
313 y: f32,
314 width: f32,
315 height: f32,
316 value_x: f32,
317 value_y: f32,
318 label_x: &str,
319 label_y: &str,
320 theme: &Theme,
321 highlighted: bool,
322) {
323 let pad_margin = 4.0;
324 let pad_x = x + pad_margin;
325 let pad_y = y + pad_margin;
326 let pad_w = width - pad_margin * 2.0;
327 let pad_h = height - pad_margin * 2.0;
328
329 ctx.fill_rect(pad_x, pad_y, pad_w, pad_h, theme.knob_track);
331
332 let dot_x = pad_x + value_x.clamp(0.0, 1.0) * pad_w;
334 let dot_y = pad_y + (1.0 - value_y.clamp(0.0, 1.0)) * pad_h; let line_color = theme.text_dim;
336 ctx.draw_line(dot_x, pad_y, dot_x, pad_y + pad_h, line_color, 1.0);
337 ctx.draw_line(pad_x, dot_y, pad_x + pad_w, dot_y, line_color, 1.0);
338
339 let dot_color = if highlighted { theme.accent } else { theme.knob_fill };
341 ctx.fill_circle(dot_x, dot_y, 5.0, dot_color);
342 ctx.fill_circle(dot_x, dot_y, 3.0, theme.knob_pointer);
343
344 if highlighted {
346 ctx.draw_line(pad_x, pad_y, pad_x + pad_w, pad_y, theme.accent, 1.5);
347 ctx.draw_line(pad_x + pad_w, pad_y, pad_x + pad_w, pad_y + pad_h, theme.accent, 1.5);
348 ctx.draw_line(pad_x + pad_w, pad_y + pad_h, pad_x, pad_y + pad_h, theme.accent, 1.5);
349 ctx.draw_line(pad_x, pad_y + pad_h, pad_x, pad_y, theme.accent, 1.5);
350 }
351
352 let label_size = 8.0;
354 let x_label_w = ctx.text_width(label_x, label_size);
355 let cx = x + width / 2.0;
356 ctx.draw_text(label_x, cx - x_label_w / 2.0, y + height + 4.0, label_size, theme.text_dim);
357
358 if !label_y.is_empty() {
359 ctx.draw_text(label_y, pad_x + 3.0, pad_y + 2.0, label_size, theme.text_dim);
360 }
361}
362
363pub fn draw_section_label(
365 ctx: &mut dyn RenderBackend,
366 x: f32,
367 y: f32,
368 w: f32,
369 label: &str,
370 theme: &Theme,
371) {
372 let size = 9.0;
373 let label_w = ctx.text_width(label, size);
374 ctx.draw_text(label, x + (w - label_w) / 2.0, y, size, theme.text_dim);
375}