1use std::time::Instant;
2
3use eframe::egui;
4use eframe::egui::CentralPanel;
5use eframe::egui::Color32;
6use eframe::egui::ComboBox;
7use eframe::egui::Context;
8use eframe::egui::DragValue;
9use eframe::egui::Grid;
10use eframe::egui::TextEdit;
11use eframe::egui::Ui;
12use eframe::egui::Vec2;
13use eframe::egui::vec2;
14use midilab::IntoEnumIterator;
15use midilab::manufacturer::akai::mpd226::ColorPattern;
16use midilab::manufacturer::akai::mpd226::ColorSequence;
17use midilab::manufacturer::akai::mpd226::NotePattern;
18use midilab::manufacturer::akai::mpd226::Preset;
19use midilab::manufacturer::akai::mpd226::control::Dial;
20use midilab::manufacturer::akai::mpd226::control::Fader;
21use midilab::manufacturer::akai::mpd226::control::Pad;
22use midilab::manufacturer::akai::mpd226::control::Switch;
23use midilab::manufacturer::akai::mpd226::control::value_kind::ActiveState;
24use midilab::manufacturer::akai::mpd226::control::value_kind::AfterTouchKind;
25use midilab::manufacturer::akai::mpd226::control::value_kind::DialKind;
26use midilab::manufacturer::akai::mpd226::control::value_kind::FaderKind;
27use midilab::manufacturer::akai::mpd226::control::value_kind::GateValue;
28use midilab::manufacturer::akai::mpd226::control::value_kind::MidiChannel;
29use midilab::manufacturer::akai::mpd226::control::value_kind::PadColor;
30use midilab::manufacturer::akai::mpd226::control::value_kind::PadKind;
31use midilab::manufacturer::akai::mpd226::control::value_kind::PresetName;
32use midilab::manufacturer::akai::mpd226::control::value_kind::PresetSlot;
33use midilab::manufacturer::akai::mpd226::control::value_kind::SwingKind;
34use midilab::manufacturer::akai::mpd226::control::value_kind::SwitchKind;
35use midilab::manufacturer::akai::mpd226::control::value_kind::Tempo;
36use midilab::manufacturer::akai::mpd226::control::value_kind::TimeDivision;
37use midilab::manufacturer::akai::mpd226::control::value_kind::TransportKind;
38use midilab::manufacturer::akai::mpd226::control::value_kind::TriggerKind;
39use midilab::manufacturer::akai::mpd226::repository::DialRepository;
40use midilab::manufacturer::akai::mpd226::repository::FaderRepository;
41use midilab::manufacturer::akai::mpd226::repository::PadRepository;
42use midilab::manufacturer::akai::mpd226::repository::SwitchRepository;
43use midilab::message::AppMsg;
44use midilab::message::UiEffect;
45use midilab::message::UiMsg;
46use midilab::message::UserMsg;
47use midilab::midi::Note;
48use midilab::scale::Octave;
49use midilab::scale::PitchClass;
50use midilab::scale::ScaleKind;
51use midilab::scale::ScaleSequence;
52use midilab::scale::SequenceDirection;
53use tokio::sync::mpsc::UnboundedReceiver;
54use tokio::sync::mpsc::UnboundedSender;
55
56#[allow(unused)]
57pub struct AkaiMpd226Editor {
58 ui_state: UiState,
59 outbox: Vec<AppMsg>,
60 app_tx: UnboundedSender<AppMsg>,
61 ui_rx: UnboundedReceiver<UiMsg>,
62}
63
64impl AkaiMpd226Editor {
65 pub fn new(app_tx: UnboundedSender<AppMsg>, ui_rx: UnboundedReceiver<UiMsg>) -> Self {
66 Self {
67 ui_state: UiState::default(),
68 outbox: Vec::new(),
69 app_tx,
70 ui_rx,
71 }
72 }
73
74 fn poll_ui_msgs(&mut self) {
75 while let Ok(msg) = self.ui_rx.try_recv() {
76 match msg {
77 UiMsg::UpdatePreset(preset) => {
78 self.ui_state.preset = Some(*preset);
79 }
80
81 UiMsg::UserMsg(e) => {
82 self.ui_state.user_error = Some(e);
83 }
84 }
85 }
86 }
87}
88
89impl eframe::App for AkaiMpd226Editor {
90 fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
91 self.poll_ui_msgs();
92
93 CentralPanel::default().show(ctx, |ui| {
94 ui.vertical(|ui| {
95 render_header(ui, &mut self.ui_state, &mut self.outbox);
96 ui.add_space(16.0);
97 render_editor(ui, &mut self.ui_state);
98 ui.add_space(16.0);
99 render_pad_patterns(ui, &mut self.ui_state);
100 });
101 });
102
103 for msg in self.outbox.drain(..) {
104 let _ = self.app_tx.send(msg);
105 }
106
107 ctx.request_repaint_after(std::time::Duration::from_millis(17));
108 }
109}
110
111const APP_X: f32 = 1295.;
112const APP_Y: f32 = 1024.;
113pub const APP_DIMENSIONS: Vec2 = Vec2 { x: APP_X, y: APP_Y };
114
115const PAD_X: f32 = 64.;
116const PAD_Y: f32 = 64.;
117const PAD_DIMENSIONS: Vec2 = Vec2 { x: PAD_X, y: PAD_Y };
118
119const DIAL_X: f32 = 48.;
120const DIAL_Y: f32 = 48.;
121const DIAL_DIMENSIONS: Vec2 = Vec2 {
122 x: DIAL_X,
123 y: DIAL_Y,
124};
125
126const FADER_X: f32 = 48.;
127const FADER_Y: f32 = 80.;
128const FADER_DIMENSIONS: Vec2 = Vec2 {
129 x: FADER_X,
130 y: FADER_Y,
131};
132
133const SWITCH_X: f32 = 48.;
134const SWITCH_Y: f32 = 24.;
135const SWITCH_DIMENSIONS: Vec2 = Vec2 {
136 x: SWITCH_X,
137 y: SWITCH_Y,
138};
139
140const BANKS: [&str; 4] = ["A", "B", "C", "D"];
141const CONTROL_BANKS: [&str; 3] = ["A", "B", "C"];
142
143#[derive(Clone, Copy, PartialEq, Eq)]
144pub enum UserSelection {
145 Pad { id: usize },
146 Dial { id: usize },
147 Fader { id: usize },
148 Switch { id: usize },
149}
150
151pub struct MidiStatus {
152 pub success: bool,
153 pub message: String,
154 pub timestamp: Instant,
155}
156
157#[derive(Default)]
158pub struct UiState {
159 pub selected_item: Option<UserSelection>,
160 pub preset: Option<Preset>,
161 pub note_mapping: NoteMappingState,
162 pub off_color_mapping: ColorMappingState,
163 pub on_color_mapping: ColorMappingState,
164 pub user_error: Option<UserMsg>,
165}
166
167pub struct NoteMappingState {
168 pub scale_seq: ScaleSequence,
169 pub starting_from_pad: usize,
170 pub tonic_highlighting_enabled: bool,
171 pub tonic_color: PadColor,
172}
173impl Default for NoteMappingState {
174 fn default() -> Self {
175 Self {
176 scale_seq: ScaleSequence {
177 tonic: PitchClass::C,
178 scale: ScaleKind::Chromatic,
179 direction: SequenceDirection::Ascending,
180 octave: Octave::O4,
181 length: 64,
182 },
183 starting_from_pad: 0,
184 tonic_highlighting_enabled: true,
185 tonic_color: PadColor::Red,
186 }
187 }
188}
189
190pub struct ColorMappingState {
191 pub pattern: ColorPattern,
192 pub length: usize,
193 pub starting_from_pad: usize,
194}
195impl Default for ColorMappingState {
196 fn default() -> Self {
197 Self {
198 pattern: ColorPattern::Contiguous(PadColor::Off),
199 length: 64,
200 starting_from_pad: 0,
201 }
202 }
203}
204
205fn render_header(ui: &mut Ui, ui_state: &mut UiState, outbox: &mut Vec<AppMsg>) {
206 const ROW_SPACING: f32 = 8.0;
207
208 if let Some(preset) = &mut ui_state.preset {
209 ui.horizontal(|ui| {
210 ui.label("Slot:");
211 ComboBox::from_id_salt("header_preset_slot")
212 .selected_text(format!("{:?}", preset.global.preset_slot))
213 .show_ui(ui, |ui| {
214 for slot in PresetSlot::iter() {
215 if ui
216 .selectable_value(
217 &mut preset.global.preset_slot,
218 slot,
219 format!("{:?}", slot),
220 )
221 .clicked()
222 {
223 preset.global.preset_slot = slot;
224 }
225 }
226 });
227
228 ui.separator();
229
230 ui.label("Name:");
231
232 let mut text = preset
233 .global
234 .preset_name
235 .0
236 .iter()
237 .map(|&b| if b.is_ascii() { b as char } else { ' ' })
238 .collect::<String>()
239 .trim_end()
240 .to_string();
241
242 ui.add(
243 TextEdit::singleline(&mut text)
244 .char_limit(8)
245 .desired_width(80.0),
246 );
247
248 let mut buf = [b' '; 8];
249 for (i, b) in text.bytes().filter(|b| b.is_ascii()).take(8).enumerate() {
250 buf[i] = b;
251 }
252 preset.global.preset_name = PresetName(buf);
253
254 ui.separator();
255
256 ui.label("Tempo:");
257 let mut tempo_val = preset.global.tempo.0 as u32;
258 if ui
259 .add(DragValue::new(&mut tempo_val).range(30..=300))
260 .changed()
261 {
262 preset.global.tempo = Tempo(tempo_val as u16);
263 }
264
265 ui.separator();
266
267 ui.label("Division:");
268 ComboBox::from_id_salt("header_time_division")
269 .selected_text(format!("{}", preset.global.time_division))
270 .show_ui(ui, |ui| {
271 for variant in TimeDivision::iter() {
272 if ui
273 .selectable_value(
274 &mut preset.global.time_division,
275 variant,
276 format!("{}", variant),
277 )
278 .clicked()
279 {
280 preset.global.time_division = variant;
281 }
282 }
283 });
284 });
285
286 ui.add_space(ROW_SPACING);
287
288 ui.horizontal(|ui| {
289 ui.label("Div Switch:");
290 ComboBox::from_id_salt("header_time_division_switch")
291 .selected_text(format!("{}", preset.global.time_division_switch))
292 .show_ui(ui, |ui| {
293 for variant in TriggerKind::iter() {
294 if ui
295 .selectable_value(
296 &mut preset.global.time_division_switch,
297 variant,
298 format!("{}", variant),
299 )
300 .clicked()
301 {
302 preset.global.time_division_switch = variant;
303 }
304 }
305 });
306
307 ui.separator();
308
309 ui.label("Repeat:");
310 ComboBox::from_id_salt("header_note_repeat_switch")
311 .selected_text(format!("{}", preset.global.note_repeat_switch))
312 .show_ui(ui, |ui| {
313 for variant in TriggerKind::iter() {
314 if ui
315 .selectable_value(
316 &mut preset.global.note_repeat_switch,
317 variant,
318 format!("{}", variant),
319 )
320 .clicked()
321 {
322 preset.global.note_repeat_switch = variant;
323 }
324 }
325 });
326
327 ui.separator();
328
329 ui.label("Gate:");
330 let mut gate_val = preset.global.gate as u8;
331 if ui
332 .add(DragValue::new(&mut gate_val).range(0..=100))
333 .changed()
334 && let Ok(g) = GateValue::try_from(gate_val)
335 {
336 preset.global.gate = g;
337 }
338
339 ui.separator();
340
341 ui.label("Swing:");
342 ComboBox::from_id_salt("header_swing")
343 .selected_text(format!("{}", preset.global.swing))
344 .show_ui(ui, |ui| {
345 for variant in SwingKind::iter() {
346 if ui
347 .selectable_value(
348 &mut preset.global.swing,
349 variant,
350 format!("{}", variant),
351 )
352 .clicked()
353 {
354 preset.global.swing = variant;
355 }
356 }
357 });
358
359 ui.separator();
360
361 ui.label("Transport:");
362 ComboBox::from_id_salt("header_transport")
363 .selected_text(format!("{}", preset.global.transport))
364 .show_ui(ui, |ui| {
365 for variant in TransportKind::iter() {
366 if ui
367 .selectable_value(
368 &mut preset.global.transport,
369 variant,
370 format!("{}", variant),
371 )
372 .clicked()
373 {
374 preset.global.transport = variant;
375 }
376 }
377 });
378 });
379 } else {
380 ui.label("No preset loaded");
381 }
382
383 ui.add_space(ROW_SPACING);
384
385 ui.horizontal(|ui| {
386 if let Some(preset) = &ui_state.preset
387 && ui.button("Load from device").clicked()
388 {
389 ui_state.user_error = None;
390 outbox.push(AppMsg::Ui(UiEffect::RequestPresetFromDevice(
391 preset.global.preset_slot,
392 )));
393 }
394
395 if let Some(preset) = ui_state.preset
396 && ui.button("Send to device").clicked()
397 {
398 ui_state.user_error = None;
399 outbox.push(AppMsg::Ui(UiEffect::SendPresetToDevice(Box::new(preset))));
400 }
401
402 if let Some(status) = &ui_state.user_error {
403 let color = match status.kind {
404 midilab::message::UserMsgKind::Status => Color32::GREEN,
405 midilab::message::UserMsgKind::Error => Color32::RED,
406 };
407
408 ui.colored_label(color, &status.msg);
409 }
410 });
411}
412
413fn render_pad_patterns(ui: &mut Ui, ui_state: &mut UiState) {
414 ui.horizontal(|ui| {
415 ui.set_min_height(128.0);
416 render_note_mapping(ui, ui_state);
417
418 ui.add_space(32.0);
419
420 render_color_mapping(ui, ui_state);
421
422 ui.add_space(32.0);
423
424 render_on_color_mapping(ui, ui_state);
425
426 ui.add_space(32.0);
427
428 selection_compare_table(ui, ui_state);
429 });
430}
431
432fn render_note_mapping(ui: &mut Ui, ui_state: &mut UiState) {
433 ui.vertical(|ui| {
434 ui.label("Note Mapping");
435
436 ui.horizontal(|ui| {
437 ui.label("Tonic");
438 ComboBox::from_id_salt("note_mapping_tonic")
439 .selected_text(ui_state.note_mapping.scale_seq.tonic.to_string())
440 .show_ui(ui, |ui| {
441 for p in PitchClass::iter() {
442 if ui
443 .selectable_value(
444 &mut ui_state.note_mapping.scale_seq.tonic,
445 p,
446 p.to_string(),
447 )
448 .clicked()
449 {
450 ui_state.note_mapping.scale_seq.tonic = p;
451 }
452 }
453 });
454 });
455
456 ui.horizontal(|ui| {
457 ui.label("Scale");
458 ComboBox::from_id_salt("note_mapping_scale")
459 .selected_text(ui_state.note_mapping.scale_seq.scale.to_string())
460 .show_ui(ui, |ui| {
461 for s in ScaleKind::iter() {
462 if ui
463 .selectable_value(
464 &mut ui_state.note_mapping.scale_seq.scale,
465 s,
466 s.to_string(),
467 )
468 .clicked()
469 {
470 ui_state.note_mapping.scale_seq.scale = s;
471 }
472 }
473 });
474 });
475
476 ui.horizontal(|ui| {
477 ui.label("Octave");
478 ComboBox::from_id_salt("note_mapping_octave")
479 .selected_text(ui_state.note_mapping.scale_seq.octave.to_string())
480 .show_ui(ui, |ui| {
481 for s in Octave::iter() {
482 if ui
483 .selectable_value(
484 &mut ui_state.note_mapping.scale_seq.octave,
485 s,
486 s.to_string(),
487 )
488 .clicked()
489 {
490 ui_state.note_mapping.scale_seq.octave = s;
491 }
492 }
493 });
494 });
495
496 ui.horizontal(|ui| {
497 ui.label("Direction");
498 ComboBox::from_id_salt("note_mapping_direction")
499 .selected_text(ui_state.note_mapping.scale_seq.direction.to_string())
500 .show_ui(ui, |ui| {
501 for s in SequenceDirection::iter() {
502 if ui
503 .selectable_value(
504 &mut ui_state.note_mapping.scale_seq.direction,
505 s,
506 s.to_string(),
507 )
508 .clicked()
509 {
510 ui_state.note_mapping.scale_seq.direction = s;
511 }
512 }
513 });
514 });
515
516 ui.horizontal(|ui| {
517 ui.label("Starting from Pad");
518
519 ui.add(DragValue::new(&mut ui_state.note_mapping.starting_from_pad).range(0..=63))
520 });
521
522 ui.horizontal(|ui| {
523 ui.label("Length");
524
525 ui.add(DragValue::new(&mut ui_state.note_mapping.scale_seq.length).range(1..=64))
526 });
527
528 ui.add_space(8.0);
529
530 ui.checkbox(
531 &mut ui_state.note_mapping.tonic_highlighting_enabled,
532 "Tonic highlighting",
533 );
534 if ui_state.note_mapping.tonic_highlighting_enabled {
535 ui.horizontal(|ui| {
536 ui.label("Tonic color");
537 ComboBox::from_id_salt("tonic_highlight_color")
538 .selected_text(ui_state.note_mapping.tonic_color.to_string())
539 .show_ui(ui, |ui| {
540 for c in PadColor::iter() {
541 if ui
542 .selectable_value(
543 &mut ui_state.note_mapping.tonic_color,
544 c,
545 c.to_string(),
546 )
547 .clicked()
548 {
549 ui_state.note_mapping.tonic_color = c;
550 }
551 }
552 });
553 });
554 }
555
556 ui.add_space(8.0);
557
558 let resp = ui.button("Set pattern");
559 if resp.clicked()
560 && let Some(preset) = ui_state.preset.as_mut()
561 {
562 let scale_seq = ui_state.note_mapping.scale_seq;
563 preset.pads.set_note_pattern(
564 ui_state.note_mapping.starting_from_pad,
565 NotePattern::Scale(scale_seq),
566 );
567
568 if ui_state.note_mapping.tonic_highlighting_enabled {
569 let tonic_color = (scale_seq.tonic, ui_state.note_mapping.tonic_color);
570 preset.pads.highlight_tonics(
571 ui_state.note_mapping.starting_from_pad,
572 scale_seq.length,
573 tonic_color,
574 );
575 }
576 }
577 });
578}
579
580fn render_color_mapping(ui: &mut Ui, ui_state: &mut UiState) {
581 ui.vertical(|ui| {
582 ui.label("Off Color Mapping");
583
584 ui.horizontal(|ui| {
585 let is_contiguous = matches!(
586 ui_state.off_color_mapping.pattern,
587 ColorPattern::Contiguous(_)
588 );
589 if ui.selectable_label(is_contiguous, "Contiguous").clicked() && !is_contiguous {
590 ui_state.off_color_mapping.pattern = ColorPattern::Contiguous(PadColor::Off);
591 }
592 if ui.selectable_label(!is_contiguous, "Grouped").clicked() && is_contiguous {
593 ui_state.off_color_mapping.pattern = ColorPattern::Grouped(vec![]);
594 }
595 });
596
597 ui.add_space(4.0);
598
599 render_color_pattern_editor(ui, "off", &mut ui_state.off_color_mapping.pattern);
600
601 ui.add_space(4.0);
602
603 ui.horizontal(|ui| {
604 ui.label("Start Pad");
605 ui.add(DragValue::new(&mut ui_state.off_color_mapping.starting_from_pad).range(0..=63));
606 });
607
608 ui.horizontal(|ui| {
609 ui.label("Length");
610 ui.add(DragValue::new(&mut ui_state.off_color_mapping.length).range(1..=64));
611 });
612
613 if ui.button("Apply").clicked()
614 && let Some(preset) = ui_state.preset.as_mut()
615 {
616 preset.pads.set_off_color_pattern(
617 ui_state.off_color_mapping.starting_from_pad,
618 ui_state.off_color_mapping.length,
619 ui_state.off_color_mapping.pattern.clone(),
620 );
621 }
622 });
623}
624
625fn render_on_color_mapping(ui: &mut Ui, ui_state: &mut UiState) {
626 ui.vertical(|ui| {
627 ui.label("On Color Mapping");
628
629 ui.horizontal(|ui| {
630 let is_contiguous = matches!(
631 ui_state.on_color_mapping.pattern,
632 ColorPattern::Contiguous(_)
633 );
634 if ui.selectable_label(is_contiguous, "Contiguous").clicked() && !is_contiguous {
635 ui_state.on_color_mapping.pattern = ColorPattern::Contiguous(PadColor::Off);
636 }
637 if ui.selectable_label(!is_contiguous, "Grouped").clicked() && is_contiguous {
638 ui_state.on_color_mapping.pattern = ColorPattern::Grouped(vec![]);
639 }
640 });
641
642 ui.add_space(4.0);
643
644 render_color_pattern_editor(ui, "on", &mut ui_state.on_color_mapping.pattern);
645
646 ui.add_space(4.0);
647
648 ui.horizontal(|ui| {
649 ui.label("Start Pad");
650 ui.add(DragValue::new(&mut ui_state.on_color_mapping.starting_from_pad).range(0..=63));
651 });
652
653 ui.horizontal(|ui| {
654 ui.label("Length");
655 ui.add(DragValue::new(&mut ui_state.on_color_mapping.length).range(1..=64));
656 });
657
658 if ui.button("Apply").clicked()
659 && let Some(preset) = ui_state.preset.as_mut()
660 {
661 preset.pads.set_on_color_pattern(
662 ui_state.on_color_mapping.starting_from_pad,
663 ui_state.on_color_mapping.length,
664 ui_state.on_color_mapping.pattern.clone(),
665 );
666 }
667 });
668}
669
670fn render_color_pattern_editor(ui: &mut Ui, id_prefix: &str, pattern: &mut ColorPattern) {
671 match pattern {
672 ColorPattern::Contiguous(color) => {
673 ui.horizontal(|ui| {
674 ui.label("Color");
675 ComboBox::from_id_salt(format!("{}_color", id_prefix))
676 .selected_text(color.to_string())
677 .show_ui(ui, |ui| {
678 for c in PadColor::iter() {
679 ui.selectable_value(color, c, c.to_string());
680 }
681 });
682 });
683 }
684 ColorPattern::Grouped(sequences) => {
685 if ui.button("+ Add").clicked() {
686 sequences.push(ColorSequence {
687 len: 4,
688 color: PadColor::Off,
689 });
690 }
691
692 let mut to_remove: Option<usize> = None;
693 for (idx, seq) in sequences.iter_mut().enumerate() {
694 ui.horizontal(|ui| {
695 ComboBox::from_id_salt(format!("{}_{}_color", id_prefix, idx))
696 .width(80.0)
697 .selected_text(seq.color.to_string())
698 .show_ui(ui, |ui| {
699 for c in PadColor::iter() {
700 ui.selectable_value(&mut seq.color, c, c.to_string());
701 }
702 });
703
704 ui.label("x");
705 ui.add(DragValue::new(&mut seq.len).range(1..=64));
706
707 if ui.button("X").clicked() {
708 to_remove = Some(idx);
709 }
710 });
711 }
712
713 if let Some(idx) = to_remove {
714 sequences.remove(idx);
715 }
716 }
717 }
718}
719
720fn render_all_pad_banks(
721 ui: &mut Ui,
722 selected_item: &mut Option<UserSelection>,
723 pad_repo: &mut PadRepository,
724) {
725 let banks: Vec<Vec<Pad>> = pad_repo
726 .pads
727 .chunks(16)
728 .map(|chunk| chunk.to_vec())
729 .collect();
730
731 ui.add_space(16.0);
732 ui.horizontal(|ui| {
733 ui.add_space(16.0);
734 for (bank_id, bank) in banks.into_iter().enumerate() {
735 let bank_label = BANKS[bank_id].to_string();
736 render_pad_bank(ui, selected_item, bank, bank_label);
737 ui.add_space(32.0);
738 }
739 });
740}
741
742fn render_pad_bank(
743 ui: &mut Ui,
744 selected_item: &mut Option<UserSelection>,
745 pads: Vec<Pad>,
746 label: String,
747) {
748 let reordered: Vec<Pad> = pads.chunks(4).rev().flatten().cloned().collect();
749
750 ui.vertical(|ui| {
751 ui.label(format!("Bank {}", label));
752 ui.add_space(8.0);
753 Grid::new(format!("pad_bank_{}", label))
754 .num_columns(4)
755 .spacing([8.0, 8.0])
756 .show(ui, |ui| {
757 for (i, pad) in reordered.into_iter().enumerate() {
758 render_pad(ui, selected_item, pad);
759 if (i + 1) % 4 == 0 {
760 ui.end_row();
761 }
762 }
763 });
764 });
765}
766
767fn render_pad(ui: &mut Ui, selected_item: &mut Option<UserSelection>, pad: Pad) {
768 let (rect, resp) = ui.allocate_exact_size(PAD_DIMENSIONS, egui::Sense::click());
769
770 ui.painter().rect_filled(rect, 4.0, Color32::DARK_GRAY);
771
772 if let Some(UserSelection::Pad { id }) = selected_item
773 && pad.id == *id
774 {
775 ui.painter().rect_stroke(
776 rect,
777 4.0,
778 egui::Stroke::new(1.5, Color32::WHITE),
779 egui::StrokeKind::Outside,
780 );
781 }
782
783 let half_w = rect.width() * 0.5;
784 let half_h = rect.height() * 0.5;
785
786 let tl = egui::Rect::from_min_size(rect.min, vec2(half_w, half_h));
787 let tr = egui::Rect::from_min_size(rect.min + vec2(half_w, 0.0), vec2(half_w, half_h));
788 let bl = egui::Rect::from_min_size(rect.min + vec2(0.0, half_h), vec2(half_w, half_h));
789 let br = egui::Rect::from_min_size(rect.min + vec2(half_w, half_h), vec2(half_w, half_h));
790
791 ui.painter().text(
792 tl.center(),
793 egui::Align2::CENTER_CENTER,
794 pad.id.to_string(),
795 egui::FontId::proportional(12.0),
796 Color32::WHITE,
797 );
798
799 ui.painter().text(
800 tr.center(),
801 egui::Align2::CENTER_CENTER,
802 format!("♩{}", pad.note),
803 egui::FontId::proportional(12.0),
804 Color32::WHITE,
805 );
806
807 let (r, g, b) = *pad.off_color.as_rgb_color();
808 let off_color = Color32::from_rgb(r, g, b);
809 ui.painter().rect_filled(bl.shrink(4.0), 4.0, off_color);
810
811 let (r, g, b) = *pad.on_color.as_rgb_color();
812 let on_color = Color32::from_rgb(r, g, b);
813 ui.painter().rect_filled(br.shrink(4.0), 4.0, on_color);
814
815 if resp.clicked() {
816 click_pad(pad.id, selected_item);
817 }
818}
819
820fn render_editor(ui: &mut Ui, ui_state: &mut UiState) {
821 if let Some(preset) = &mut ui_state.preset {
822 render_all_pad_banks(ui, &mut ui_state.selected_item, &mut preset.pads);
823 ui.add_space(16.0);
824 render_all_control_banks(
825 ui,
826 &mut ui_state.selected_item,
827 &mut preset.dials,
828 &mut preset.faders,
829 &mut preset.switches,
830 );
831 } else {
832 ui.label("no pad repo");
833 }
834}
835
836fn click_pad(id: usize, selected_item: &mut Option<UserSelection>) {
837 if *selected_item == Some(UserSelection::Pad { id }) {
838 *selected_item = None;
839 } else {
840 *selected_item = Some(UserSelection::Pad { id });
841 }
842}
843
844fn render_all_control_banks(
845 ui: &mut Ui,
846 selected_item: &mut Option<UserSelection>,
847 dial_repo: &mut DialRepository,
848 fader_repo: &mut FaderRepository,
849 switch_repo: &mut SwitchRepository,
850) {
851 ui.add_space(16.0);
852 ui.horizontal(|ui| {
853 ui.add_space(16.0);
854 for (bank_idx, bank_label) in CONTROL_BANKS.iter().enumerate() {
855 render_control_bank(
856 ui,
857 selected_item,
858 dial_repo,
859 fader_repo,
860 switch_repo,
861 bank_idx,
862 bank_label,
863 );
864 ui.add_space(32.0);
865 }
866 });
867}
868
869fn render_control_bank(
870 ui: &mut Ui,
871 selected_item: &mut Option<UserSelection>,
872 dial_repo: &mut DialRepository,
873 fader_repo: &mut FaderRepository,
874 switch_repo: &mut SwitchRepository,
875 bank_idx: usize,
876 bank_label: &str,
877) {
878 ui.vertical(|ui| {
879 ui.label(format!("Control Bank {}", bank_label));
880 ui.add_space(8.0);
881
882 ui.horizontal(|ui| {
883 for dial_offset in 0..4 {
884 let dial_id = bank_idx * 4 + dial_offset;
885 let dial = dial_repo.0[dial_id];
886 render_dial(ui, selected_item, dial, dial_id);
887 ui.add_space(4.0);
888 }
889 });
890
891 ui.add_space(8.0);
892
893 ui.horizontal(|ui| {
894 for fader_offset in 0..4 {
895 let fader_id = bank_idx * 4 + fader_offset;
896 let fader = fader_repo.0[fader_id];
897 render_fader(ui, selected_item, fader, fader_id);
898 ui.add_space(4.0);
899 }
900 });
901
902 ui.add_space(8.0);
903
904 ui.horizontal(|ui| {
905 for switch_offset in 0..4 {
906 let switch_id = bank_idx * 4 + switch_offset;
907 let switch = switch_repo.0[switch_id];
908 render_switch(ui, selected_item, switch, switch_id);
909 ui.add_space(4.0);
910 }
911 });
912 });
913}
914
915fn render_dial(
916 ui: &mut Ui,
917 selected_item: &mut Option<UserSelection>,
918 _dial: Dial,
919 dial_id: usize,
920) {
921 let (rect, resp) = ui.allocate_exact_size(DIAL_DIMENSIONS, egui::Sense::click());
922
923 ui.painter().rect_filled(rect, 24.0, Color32::DARK_GRAY);
924
925 if let Some(UserSelection::Dial { id }) = selected_item
926 && dial_id == *id
927 {
928 ui.painter().rect_stroke(
929 rect,
930 24.0,
931 egui::Stroke::new(1.5, Color32::WHITE),
932 egui::StrokeKind::Outside,
933 );
934 }
935
936 ui.painter().text(
937 rect.center(),
938 egui::Align2::CENTER_CENTER,
939 dial_id.to_string(),
940 egui::FontId::proportional(12.0),
941 Color32::WHITE,
942 );
943
944 if resp.clicked() {
945 click_dial(dial_id, selected_item);
946 }
947}
948
949fn click_dial(id: usize, selected_item: &mut Option<UserSelection>) {
950 if *selected_item == Some(UserSelection::Dial { id }) {
951 *selected_item = None;
952 } else {
953 *selected_item = Some(UserSelection::Dial { id });
954 }
955}
956
957fn render_fader(
958 ui: &mut Ui,
959 selected_item: &mut Option<UserSelection>,
960 _fader: Fader,
961 fader_id: usize,
962) {
963 let (rect, resp) = ui.allocate_exact_size(FADER_DIMENSIONS, egui::Sense::click());
964
965 ui.painter().rect_filled(rect, 4.0, Color32::DARK_GRAY);
966
967 if let Some(UserSelection::Fader { id }) = selected_item
968 && fader_id == *id
969 {
970 ui.painter().rect_stroke(
971 rect,
972 4.0,
973 egui::Stroke::new(1.5, Color32::WHITE),
974 egui::StrokeKind::Outside,
975 );
976 }
977
978 ui.painter().text(
979 rect.center(),
980 egui::Align2::CENTER_CENTER,
981 fader_id.to_string(),
982 egui::FontId::proportional(10.0),
983 Color32::WHITE,
984 );
985
986 if resp.clicked() {
987 click_fader(fader_id, selected_item);
988 }
989}
990
991fn click_fader(id: usize, selected_item: &mut Option<UserSelection>) {
992 if *selected_item == Some(UserSelection::Fader { id }) {
993 *selected_item = None;
994 } else {
995 *selected_item = Some(UserSelection::Fader { id });
996 }
997}
998
999fn render_switch(
1000 ui: &mut Ui,
1001 selected_item: &mut Option<UserSelection>,
1002 _switch: Switch,
1003 switch_id: usize,
1004) {
1005 let (rect, resp) = ui.allocate_exact_size(SWITCH_DIMENSIONS, egui::Sense::click());
1006
1007 ui.painter().rect_filled(rect, 4.0, Color32::DARK_GRAY);
1008
1009 if let Some(UserSelection::Switch { id }) = selected_item
1010 && switch_id == *id
1011 {
1012 ui.painter().rect_stroke(
1013 rect,
1014 4.0,
1015 egui::Stroke::new(1.5, Color32::WHITE),
1016 egui::StrokeKind::Outside,
1017 );
1018 }
1019
1020 ui.painter().text(
1021 rect.center(),
1022 egui::Align2::CENTER_CENTER,
1023 switch_id.to_string(),
1024 egui::FontId::proportional(10.0),
1025 Color32::WHITE,
1026 );
1027
1028 if resp.clicked() {
1029 click_switch(switch_id, selected_item);
1030 }
1031}
1032
1033fn click_switch(id: usize, selected_item: &mut Option<UserSelection>) {
1034 if *selected_item == Some(UserSelection::Switch { id }) {
1035 *selected_item = None;
1036 } else {
1037 *selected_item = Some(UserSelection::Switch { id });
1038 }
1039}
1040
1041fn selection_compare_table(ui: &mut Ui, ui_state: &mut UiState) {
1042 ui.vertical(|ui| {
1043 let selection_label = match ui_state.selected_item {
1044 Some(UserSelection::Pad { id }) => format!("Pad {}", id),
1045 Some(UserSelection::Dial { id }) => {
1046 let bank = CONTROL_BANKS[id / 4];
1047 let num = (id % 4) + 1;
1048 format!("Dial {}{}", bank, num)
1049 }
1050 Some(UserSelection::Fader { id }) => {
1051 let bank = CONTROL_BANKS[id / 4];
1052 let num = (id % 4) + 1;
1053 format!("Fader {}{}", bank, num)
1054 }
1055 Some(UserSelection::Switch { id }) => {
1056 let bank = CONTROL_BANKS[id / 4];
1057 let num = (id % 4) + 1;
1058 format!("Switch {}{}", bank, num)
1059 }
1060 None => "None".to_string(),
1061 };
1062 ui.label(format!("Selected: {}", selection_label));
1063
1064 if let Some(preset) = &mut ui_state.preset {
1065 match ui_state.selected_item {
1066 Some(UserSelection::Pad { id: index }) => {
1067 if let Some(pad) = preset.pads.pads.iter_mut().find(|p| p.id == index) {
1068 render_pad_compare_grid(ui, pad);
1069 }
1070 }
1071 Some(UserSelection::Dial { id: index }) => {
1072 if let Some(dial) = preset.dials.0.get_mut(index) {
1073 render_dial_compare_grid(ui, dial);
1074 }
1075 }
1076 Some(UserSelection::Fader { id: index }) => {
1077 if let Some(fader) = preset.faders.0.get_mut(index) {
1078 render_fader_compare_grid(ui, fader);
1079 }
1080 }
1081 Some(UserSelection::Switch { id: index }) => {
1082 if let Some(switch) = preset.switches.0.get_mut(index) {
1083 render_switch_compare_grid(ui, switch);
1084 }
1085 }
1086 None => {}
1087 }
1088 }
1089 });
1090}
1091
1092fn render_pad_compare_grid(ui: &mut Ui, pad: &mut Pad) {
1093 Grid::new("pad_compare_grid")
1094 .striped(true)
1095 .spacing([16.0, 6.0])
1096 .show(ui, |ui| {
1097 ui.label("Field");
1098 ui.label("Value");
1099 ui.end_row();
1100
1101 row_edit_pad_kind(ui, "kind", &mut pad.kind);
1102 row_edit_channel(ui, "channel", &mut pad.channel);
1103 row_edit_note(ui, "note", &mut pad.note);
1104 row_edit_midi2din(ui, "midi to din", &mut pad.midi2din);
1105 row_edit_trigger_kind(ui, "trigger", &mut pad.trigger);
1106 row_edit_aftertouch_kind(ui, "aftertouch", &mut pad.aftertouch);
1107 row_edit_u8(ui, "program", &mut pad.program);
1108 row_edit_u8(ui, "msb", &mut pad.msb);
1109 row_edit_u8(ui, "lsb", &mut pad.lsb);
1110 row_edit_pad_color(ui, "off color", &mut pad.off_color);
1111 row_edit_pad_color(ui, "on color", &mut pad.on_color);
1112 });
1113}
1114
1115fn render_dial_compare_grid(ui: &mut Ui, dial: &mut Dial) {
1116 Grid::new("dial_compare_grid")
1117 .striped(true)
1118 .spacing([16.0, 6.0])
1119 .show(ui, |ui| {
1120 ui.label("Field");
1121 ui.label("Value");
1122 ui.end_row();
1123
1124 row_edit_dial_kind(ui, "kind", &mut dial.kind);
1125 row_edit_channel(ui, "channel", &mut dial.channel);
1126 row_edit_u8(ui, "midicc", &mut dial.midicc);
1127 row_edit_u8(ui, "min", &mut dial.min);
1128 row_edit_u8(ui, "max", &mut dial.max);
1129 row_edit_midi2din(ui, "midi to din", &mut dial.midi2din);
1130 row_edit_u8(ui, "msb", &mut dial.msb);
1131 row_edit_u8(ui, "lsb", &mut dial.lsb);
1132 row_edit_u8(ui, "value", &mut dial.value);
1133 });
1134}
1135
1136fn render_fader_compare_grid(ui: &mut Ui, fader: &mut Fader) {
1137 Grid::new("fader_compare_grid")
1138 .striped(true)
1139 .spacing([16.0, 6.0])
1140 .show(ui, |ui| {
1141 ui.label("Field");
1142 ui.label("Value");
1143 ui.end_row();
1144
1145 row_edit_fader_kind(ui, "kind", &mut fader.kind);
1146 row_edit_channel(ui, "channel", &mut fader.channel);
1147 row_edit_u8(ui, "midicc", &mut fader.midicc);
1148 row_edit_u8(ui, "min", &mut fader.min);
1149 row_edit_u8(ui, "max", &mut fader.max);
1150 row_edit_midi2din(ui, "midi to din", &mut fader.midi2din);
1151 });
1152}
1153
1154fn render_switch_compare_grid(ui: &mut Ui, switch: &mut Switch) {
1155 Grid::new("switch_compare_grid")
1156 .striped(true)
1157 .spacing([16.0, 6.0])
1158 .show(ui, |ui| {
1159 ui.label("Field");
1160 ui.label("Value");
1161 ui.end_row();
1162
1163 row_edit_switch_kind(ui, "kind", &mut switch.kind);
1164 row_edit_channel(ui, "channel", &mut switch.channel);
1165 row_edit_u8(ui, "midicc", &mut switch.midicc);
1166 row_edit_trigger_kind(ui, "mode", &mut switch.mode);
1167 row_edit_u8(ui, "prog", &mut switch.prog);
1168 row_edit_u8(ui, "msb", &mut switch.msb);
1169 row_edit_u8(ui, "lsb", &mut switch.lsb);
1170 row_edit_midi2din(ui, "midi to din", &mut switch.midi2din);
1171 row_edit_u8(ui, "note", &mut switch.note);
1172 row_edit_u8(ui, "velo", &mut switch.velo);
1173 row_edit_midi2din(ui, "invert", &mut switch.invert);
1174 });
1175}
1176
1177fn row_edit_u8(ui: &mut Ui, name: &str, value: &mut u8) {
1178 ui.label(name);
1179 let mut val = *value as u32;
1180 if ui.add(DragValue::new(&mut val).range(0..=127)).changed() {
1181 *value = val as u8;
1182 }
1183 ui.end_row();
1184}
1185
1186fn row_edit_note(ui: &mut Ui, name: &str, value: &mut Note) {
1187 ui.label(name);
1188 let mut val = *value as u32;
1189 if ui.add(DragValue::new(&mut val).range(0..=127)).changed()
1190 && let Ok(note) = Note::try_from(val as u8)
1191 {
1192 *value = note;
1193 }
1194 ui.end_row();
1195}
1196
1197fn row_edit_pad_kind(ui: &mut Ui, name: &str, value: &mut PadKind) {
1198 ui.label(name);
1199 ComboBox::from_id_salt(name)
1200 .selected_text(format!("{}", value))
1201 .show_ui(ui, |ui| {
1202 for variant in PadKind::iter() {
1203 ui.selectable_value(value, variant, format!("{}", variant));
1204 }
1205 });
1206 ui.end_row();
1207}
1208
1209fn row_edit_channel(ui: &mut Ui, name: &str, value: &mut MidiChannel) {
1210 ui.label(name);
1211 ComboBox::from_id_salt(name)
1212 .selected_text(format!("{}", value))
1213 .show_ui(ui, |ui| {
1214 for variant in MidiChannel::iter() {
1215 ui.selectable_value(value, variant, format!("{}", variant));
1216 }
1217 });
1218 ui.end_row();
1219}
1220
1221fn row_edit_dial_kind(ui: &mut Ui, name: &str, value: &mut DialKind) {
1222 ui.label(name);
1223 ComboBox::from_id_salt(name)
1224 .selected_text(format!("{:?}", value))
1225 .show_ui(ui, |ui| {
1226 for variant in DialKind::iter() {
1227 ui.selectable_value(value, variant, format!("{:?}", variant));
1228 }
1229 });
1230 ui.end_row();
1231}
1232
1233fn row_edit_fader_kind(ui: &mut Ui, name: &str, value: &mut FaderKind) {
1234 ui.label(name);
1235 ComboBox::from_id_salt(name)
1236 .selected_text(format!("{:?}", value))
1237 .show_ui(ui, |ui| {
1238 for variant in FaderKind::iter() {
1239 ui.selectable_value(value, variant, format!("{:?}", variant));
1240 }
1241 });
1242 ui.end_row();
1243}
1244
1245fn row_edit_switch_kind(ui: &mut Ui, name: &str, value: &mut SwitchKind) {
1246 ui.label(name);
1247 ComboBox::from_id_salt(name)
1248 .selected_text(format!("{:?}", value))
1249 .show_ui(ui, |ui| {
1250 for variant in SwitchKind::iter() {
1251 ui.selectable_value(value, variant, format!("{:?}", variant));
1252 }
1253 });
1254 ui.end_row();
1255}
1256
1257fn row_edit_trigger_kind(ui: &mut Ui, name: &str, value: &mut TriggerKind) {
1258 ui.label(name);
1259 ComboBox::from_id_salt(name)
1260 .selected_text(format!("{}", value))
1261 .show_ui(ui, |ui| {
1262 for variant in TriggerKind::iter() {
1263 ui.selectable_value(value, variant, format!("{}", variant));
1264 }
1265 });
1266 ui.end_row();
1267}
1268
1269fn row_edit_aftertouch_kind(ui: &mut Ui, name: &str, value: &mut AfterTouchKind) {
1270 ui.label(name);
1271 ComboBox::from_id_salt(name)
1272 .selected_text(format!("{}", value))
1273 .show_ui(ui, |ui| {
1274 for variant in AfterTouchKind::iter() {
1275 ui.selectable_value(value, variant, format!("{}", variant));
1276 }
1277 });
1278 ui.end_row();
1279}
1280
1281fn row_edit_pad_color(ui: &mut Ui, name: &str, value: &mut PadColor) {
1282 ui.label(name);
1283 ComboBox::from_id_salt(name)
1284 .selected_text(format!("{}", value))
1285 .show_ui(ui, |ui| {
1286 for variant in PadColor::iter() {
1287 ui.selectable_value(value, variant, format!("{}", variant));
1288 }
1289 });
1290 ui.end_row();
1291}
1292
1293fn row_edit_midi2din(ui: &mut Ui, name: &str, value: &mut ActiveState) {
1294 ui.label(name);
1295 ComboBox::from_id_salt(name)
1296 .selected_text(format!("{}", value))
1297 .show_ui(ui, |ui| {
1298 for variant in ActiveState::iter() {
1299 ui.selectable_value(value, variant, format!("{}", variant));
1300 }
1301 });
1302 ui.end_row();
1303}