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