1#[derive(Clone, Debug)]
5pub enum WidgetDef {
6 Knob { param_id: u32, label: &'static str },
8 Slider { param_id: u32, label: &'static str },
10 Toggle { param_id: u32, label: &'static str },
12}
13
14impl WidgetDef {
15 pub fn param_id(&self) -> u32 {
16 match self {
17 WidgetDef::Knob { param_id, .. } => *param_id,
18 WidgetDef::Slider { param_id, .. } => *param_id,
19 WidgetDef::Toggle { param_id, .. } => *param_id,
20 }
21 }
22
23 pub fn label(&self) -> &'static str {
24 match self {
25 WidgetDef::Knob { label, .. } => label,
26 WidgetDef::Slider { label, .. } => label,
27 WidgetDef::Toggle { label, .. } => label,
28 }
29 }
30}
31
32#[derive(Clone, Debug)]
34pub struct KnobDef {
35 pub param_id: u32,
36 pub label: &'static str,
37 pub widget: Option<WidgetKind>,
39 pub span: u32,
41 pub param_id_y: Option<u32>,
43 pub meter_ids: Option<Vec<u32>>,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum WidgetKind {
50 Knob,
51 Slider,
52 Toggle,
53 Selector,
54 Meter,
56 XYPad,
58}
59
60impl KnobDef {
61 pub fn knob(param_id: impl Into<u32>, label: &'static str) -> Self {
63 Self { param_id: param_id.into(), label, widget: Some(WidgetKind::Knob), span: 1, param_id_y: None, meter_ids: None }
64 }
65
66 pub fn slider(param_id: impl Into<u32>, label: &'static str) -> Self {
68 Self { param_id: param_id.into(), label, widget: Some(WidgetKind::Slider), span: 1, param_id_y: None, meter_ids: None }
69 }
70
71 pub fn toggle(param_id: impl Into<u32>, label: &'static str) -> Self {
73 Self { param_id: param_id.into(), label, widget: Some(WidgetKind::Toggle), span: 1, param_id_y: None, meter_ids: None }
74 }
75
76 pub fn selector(param_id: impl Into<u32>, label: &'static str) -> Self {
78 Self { param_id: param_id.into(), label, widget: Some(WidgetKind::Selector), span: 1, param_id_y: None, meter_ids: None }
79 }
80
81 pub fn meter(ids: &[u32], label: &'static str) -> Self {
83 Self {
84 param_id: ids.first().copied().unwrap_or(0),
85 label,
86 widget: Some(WidgetKind::Meter),
87 span: 1,
88 param_id_y: None,
89 meter_ids: Some(ids.to_vec()),
90 }
91 }
92
93 pub fn xy_pad(param_x: impl Into<u32>, param_y: impl Into<u32>, label: &'static str) -> Self {
95 Self { param_id: param_x.into(), label, widget: Some(WidgetKind::XYPad), span: 2, param_id_y: Some(param_y.into()), meter_ids: None }
96 }
97
98 pub fn with_span(mut self, span: u32) -> Self {
100 self.span = span;
101 self
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct KnobRow {
108 pub label: Option<&'static str>,
109 pub knobs: Vec<KnobDef>,
110}
111
112#[derive(Clone, Debug)]
114pub struct PluginLayout {
115 pub title: &'static str,
116 pub version: &'static str,
117 pub rows: Vec<KnobRow>,
118 pub width: u32,
119 pub height: u32,
120 pub knob_size: f32,
121}
122
123impl PluginLayout {
124 pub fn compute_size(rows: &[KnobRow], knob_size: f32) -> (u32, u32) {
126 let header_h = 30.0;
127 let row_h = knob_size + 30.0;
128 let section_label_h = 18.0;
129 let padding = 10.0;
130
131 let max_knobs = rows.iter()
132 .map(|r| r.knobs.iter().map(|k| k.span.max(1) as usize).sum::<usize>())
133 .max().unwrap_or(1);
134 let w = (max_knobs as f32 * (knob_size + 10.0) + 20.0).max(300.0);
135
136 let mut h = header_h + padding;
137 for row in rows {
138 if row.label.is_some() {
139 h += section_label_h;
140 }
141 h += row_h + padding;
142 }
143
144 (w as u32, h as u32)
145 }
146
147 pub fn build(
149 title: &'static str,
150 version: &'static str,
151 rows: Vec<KnobRow>,
152 knob_size: f32,
153 ) -> Self {
154 let (w, h) = Self::compute_size(&rows, knob_size);
155 Self {
156 title,
157 version,
158 rows,
159 width: w,
160 height: h,
161 knob_size,
162 }
163 }
164}
165
166pub const AUTO: u32 = u32::MAX;
172
173pub const GRID_GAP: f32 = 30.0;
175pub const GRID_PADDING: f32 = 15.0;
176pub const GRID_HEADER_H: f32 = 30.0;
177pub const GRID_SECTION_H: f32 = 18.0;
178
179#[derive(Clone, Debug)]
181pub struct GridWidget {
182 pub col: u32,
184 pub row: u32,
186 pub col_span: u32,
188 pub row_span: u32,
190 pub param_id: u32,
192 pub label: &'static str,
194 pub widget: Option<WidgetKind>,
196 pub param_id_y: Option<u32>,
198 pub meter_ids: Option<Vec<u32>>,
200}
201
202impl GridWidget {
203 pub fn knob(param_id: impl Into<u32>, label: &'static str) -> Self {
204 Self {
205 col: AUTO, row: AUTO, col_span: 1, row_span: 1,
206 param_id: param_id.into(), label, widget: Some(WidgetKind::Knob),
207 param_id_y: None, meter_ids: None,
208 }
209 }
210
211 pub fn slider(param_id: impl Into<u32>, label: &'static str) -> Self {
212 Self {
213 col: AUTO, row: AUTO, col_span: 1, row_span: 1,
214 param_id: param_id.into(), label, widget: Some(WidgetKind::Slider),
215 param_id_y: None, meter_ids: None,
216 }
217 }
218
219 pub fn toggle(param_id: impl Into<u32>, label: &'static str) -> Self {
220 Self {
221 col: AUTO, row: AUTO, col_span: 1, row_span: 1,
222 param_id: param_id.into(), label, widget: Some(WidgetKind::Toggle),
223 param_id_y: None, meter_ids: None,
224 }
225 }
226
227 pub fn selector(param_id: impl Into<u32>, label: &'static str) -> Self {
228 Self {
229 col: AUTO, row: AUTO, col_span: 1, row_span: 1,
230 param_id: param_id.into(), label, widget: Some(WidgetKind::Selector),
231 param_id_y: None, meter_ids: None,
232 }
233 }
234
235 pub fn meter(ids: &[u32], label: &'static str) -> Self {
236 Self {
237 col: AUTO, row: AUTO, col_span: 1, row_span: 1,
238 param_id: ids.first().copied().unwrap_or(0), label,
239 widget: Some(WidgetKind::Meter),
240 param_id_y: None, meter_ids: Some(ids.to_vec()),
241 }
242 }
243
244 pub fn xy_pad(param_x: impl Into<u32>, param_y: impl Into<u32>, label: &'static str) -> Self {
245 Self {
246 col: AUTO, row: AUTO, col_span: 2, row_span: 2,
247 param_id: param_x.into(), label, widget: Some(WidgetKind::XYPad),
248 param_id_y: Some(param_y.into()), meter_ids: None,
249 }
250 }
251
252 pub fn cols(mut self, n: u32) -> Self {
254 self.col_span = n;
255 self
256 }
257
258 pub fn rows(mut self, n: u32) -> Self {
260 self.row_span = n;
261 self
262 }
263
264 pub fn at(mut self, col: u32, row: u32) -> Self {
266 self.col = col;
267 self.row = row;
268 self
269 }
270}
271
272#[derive(Clone, Debug)]
274pub struct GridLayout {
275 pub title: &'static str,
276 pub version: &'static str,
277 pub cols: u32,
279 pub sections: Vec<(u32, &'static str)>,
281 pub widgets: Vec<GridWidget>,
283 pub cell_size: f32,
285 pub width: u32,
287 pub height: u32,
289}
290
291impl GridLayout {
292 pub fn build(
297 title: &'static str,
298 version: &'static str,
299 cols: u32,
300 cell_size: f32,
301 widgets: Vec<GridWidget>,
302 breaks: Vec<(usize, &'static str)>,
303 ) -> Self {
304 let mut layout = Self {
305 title, version, cols,
306 sections: Vec::new(),
307 widgets, cell_size,
308 width: 0, height: 0,
309 };
310 layout.auto_flow_with_breaks(&breaks);
311 let (w, h) = layout.compute_size();
312 layout.width = w;
313 layout.height = h;
314 layout
315 }
316
317 pub fn compute_size(&self) -> (u32, u32) {
319 let max_row = self.widgets.iter()
320 .map(|w| w.row + w.row_span)
321 .max().unwrap_or(1);
322 let section_count = self.sections.len() as f32;
323
324 let w = GRID_PADDING * 2.0 + self.cols as f32 * (self.cell_size + GRID_GAP) - GRID_GAP;
325 let bottom_label_h = 28.0; let h = GRID_HEADER_H + GRID_PADDING
327 + max_row as f32 * (self.cell_size + GRID_GAP) - GRID_GAP
328 + section_count * GRID_SECTION_H
329 + bottom_label_h + GRID_PADDING;
330
331 (w.max(300.0) as u32, h as u32)
332 }
333
334 pub fn auto_flow(&mut self) {
336 self.auto_flow_with_breaks(&[]);
337 }
338
339 pub fn auto_flow_with_breaks(&mut self, breaks: &[(usize, &'static str)]) {
344 let mut occupied = std::collections::HashSet::new();
345 let mut cursor_col: u32 = 0;
346 let mut cursor_row: u32 = 0;
347 let mut any_emitted = false;
348
349 for w in &self.widgets {
351 if w.col != AUTO && w.row != AUTO {
352 for c in w.col..w.col + w.col_span {
353 for r in w.row..w.row + w.row_span {
354 occupied.insert((c, r));
355 }
356 }
357 }
358 }
359
360 for (i, w) in self.widgets.iter_mut().enumerate() {
362 for &(break_idx, label) in breaks {
364 if break_idx == i {
365 if any_emitted || cursor_col > 0 {
366 cursor_row += 1;
367 cursor_col = 0;
368 }
369 self.sections.push((cursor_row, label));
370 any_emitted = true;
371 }
372 }
373
374 if w.col != AUTO && w.row != AUTO {
375 any_emitted = true;
377 continue;
378 }
379
380 loop {
382 if cursor_col + w.col_span > self.cols {
383 cursor_col = 0;
384 cursor_row += 1;
385 }
386 let fits = (0..w.col_span).all(|dc|
387 (0..w.row_span).all(|dr|
388 !occupied.contains(&(cursor_col + dc, cursor_row + dr))
389 )
390 );
391 if fits { break; }
392 cursor_col += 1;
393 }
394
395 w.col = cursor_col;
396 w.row = cursor_row;
397
398 for c in w.col..w.col + w.col_span {
399 for r in w.row..w.row + w.row_span {
400 occupied.insert((c, r));
401 }
402 }
403
404 cursor_col += w.col_span;
405 any_emitted = true;
406 }
407 }
408}
409
410pub fn compute_section_offsets(layout: &GridLayout) -> Vec<f32> {
414 let max_row = layout.widgets.iter()
415 .map(|w| w.row + w.row_span)
416 .max().unwrap_or(1);
417 let mut offsets = vec![0.0f32; max_row as usize + 1];
418 let mut cumulative = 0.0;
419
420 for row in 0..=max_row {
421 if layout.sections.iter().any(|(r, _)| *r == row) {
422 cumulative += GRID_SECTION_H;
423 }
424 if (row as usize) < offsets.len() {
425 offsets[row as usize] = cumulative;
426 }
427 }
428 offsets
429}
430
431impl From<PluginLayout> for GridLayout {
432 fn from(pl: PluginLayout) -> Self {
433 let cols = pl.rows.iter()
434 .map(|r| r.knobs.iter().map(|k| k.span.max(1)).sum::<u32>())
435 .max().unwrap_or(1);
436
437 let mut widgets = Vec::new();
438 let mut sections = Vec::new();
439 let mut grid_row = 0u32;
440
441 for row in &pl.rows {
442 if let Some(label) = row.label {
443 sections.push((grid_row, label));
444 }
445 let mut col = 0u32;
446 for knob in &row.knobs {
447 widgets.push(GridWidget {
448 col, row: grid_row,
449 col_span: knob.span.max(1), row_span: 1,
450 param_id: knob.param_id,
451 label: knob.label,
452 widget: knob.widget,
453 param_id_y: knob.param_id_y,
454 meter_ids: knob.meter_ids.clone(),
455 });
456 col += knob.span.max(1);
457 }
458 grid_row += 1;
459 }
460
461 let mut gl = GridLayout {
462 title: pl.title, version: pl.version,
463 cols, sections, widgets, cell_size: pl.knob_size,
464 width: 0, height: 0,
465 };
466 let (w, h) = gl.compute_size();
467 gl.width = w;
468 gl.height = h;
469 gl
470 }
471}
472
473#[derive(Clone, Debug)]
475pub enum Layout {
476 Rows(PluginLayout),
477 Grid(GridLayout),
478}
479
480impl Layout {
481 pub fn width(&self) -> u32 {
482 match self { Layout::Rows(l) => l.width, Layout::Grid(g) => g.width }
483 }
484 pub fn height(&self) -> u32 {
485 match self { Layout::Rows(l) => l.height, Layout::Grid(g) => g.height }
486 }
487 pub fn title(&self) -> &str {
488 match self { Layout::Rows(l) => l.title, Layout::Grid(g) => g.title }
489 }
490 pub fn version(&self) -> &str {
491 match self { Layout::Rows(l) => l.version, Layout::Grid(g) => g.version }
492 }
493}