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::NotePattern;
17use midilab::manufacturer::akai::mpd226::Preset;
18use midilab::manufacturer::akai::mpd226::control::Pad;
19use midilab::manufacturer::akai::mpd226::control::value_kind::AfterTouchKind;
20use midilab::manufacturer::akai::mpd226::control::value_kind::GateValue;
21use midilab::manufacturer::akai::mpd226::control::value_kind::Midi2Din;
22use midilab::manufacturer::akai::mpd226::control::value_kind::PadColor;
23use midilab::manufacturer::akai::mpd226::control::value_kind::PadKind;
24use midilab::manufacturer::akai::mpd226::control::value_kind::PresetName;
25use midilab::manufacturer::akai::mpd226::control::value_kind::PresetSlot;
26use midilab::manufacturer::akai::mpd226::control::value_kind::SwingKind;
27use midilab::manufacturer::akai::mpd226::control::value_kind::Tempo;
28use midilab::manufacturer::akai::mpd226::control::value_kind::TimeDivision;
29use midilab::manufacturer::akai::mpd226::control::value_kind::TransportKind;
30use midilab::manufacturer::akai::mpd226::control::value_kind::TriggerKind;
31use midilab::manufacturer::akai::mpd226::repository::PadRepository;
32use midilab::message::AppMsg;
33use midilab::message::UiEffect;
34use midilab::message::UiMsg;
35use midilab::message::UserMsg;
36use midilab::midi::Note;
37use midilab::scale::Octave;
38use midilab::scale::PitchClass;
39use midilab::scale::ScaleKind;
40use midilab::scale::ScaleSequence;
41use midilab::scale::SequenceDirection;
42use tokio::sync::mpsc::UnboundedReceiver;
43use tokio::sync::mpsc::UnboundedSender;
44
45#[allow(unused)]
46pub struct AkaiMpd226Editor {
47 ui_state: UiState,
48 outbox: Vec<AppMsg>,
49 app_tx: UnboundedSender<AppMsg>,
50 ui_rx: UnboundedReceiver<UiMsg>,
51}
52
53impl AkaiMpd226Editor {
54 pub fn new(app_tx: UnboundedSender<AppMsg>, ui_rx: UnboundedReceiver<UiMsg>) -> Self {
55 Self {
56 ui_state: UiState::default(),
57 outbox: Vec::new(),
58 app_tx,
59 ui_rx,
60 }
61 }
62
63 fn poll_ui_msgs(&mut self) {
64 while let Ok(msg) = self.ui_rx.try_recv() {
65 match msg {
66 UiMsg::UpdatePreset(preset) => {
67 self.ui_state.preset = Some(*preset);
68 }
69
70 UiMsg::UserMsg(e) => {
71 self.ui_state.user_error = Some(e);
72 }
73 }
74 }
75 }
76}
77
78impl eframe::App for AkaiMpd226Editor {
79 fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
80 self.poll_ui_msgs();
81
82 CentralPanel::default().show(ctx, |ui| {
83 ui.vertical(|ui| {
84 render_header(ui, &mut self.ui_state, &mut self.outbox);
85 ui.add_space(16.0);
86 render_editor(ui, &mut self.ui_state);
87 ui.add_space(16.0);
88 render_pad_patterns(ui, &mut self.ui_state);
89 });
90 });
91
92 for msg in self.outbox.drain(..) {
93 let _ = self.app_tx.send(msg);
94 }
95
96 ctx.request_repaint_after(std::time::Duration::from_millis(17));
97 }
98}
99
100const APP_X: f32 = 1295.;
101const APP_Y: f32 = 1024.;
102pub const APP_DIMENSIONS: Vec2 = Vec2 { x: APP_X, y: APP_Y };
103
104const PAD_X: f32 = 64.;
105const PAD_Y: f32 = 64.;
106const PAD_DIMENSIONS: Vec2 = Vec2 { x: PAD_X, y: PAD_Y };
107
108const BANKS: [&str; 4] = ["A", "B", "C", "D"];
109
110#[derive(Clone, Copy, PartialEq, Eq)]
111pub enum UserSelection {
112 Pad { id: usize },
113}
114
115pub struct MidiStatus {
116 pub success: bool,
117 pub message: String,
118 pub timestamp: Instant,
119}
120
121#[derive(Default)]
122pub struct UiState {
123 pub selected_item: Option<UserSelection>,
124 pub preset: Option<Preset>,
125 pub note_mapping: NoteMappingState,
126 pub off_color_mapping: ColorMappingState,
127 pub on_color_mapping: ColorMappingState,
128 pub user_error: Option<UserMsg>,
129}
130
131pub struct NoteMappingState {
132 pub scale_seq: ScaleSequence,
133 pub starting_from_pad: usize,
134 pub tonic_highlighting_enabled: bool,
135 pub tonic_color: PadColor,
136}
137impl Default for NoteMappingState {
138 fn default() -> Self {
139 Self {
140 scale_seq: ScaleSequence {
141 tonic: PitchClass::C,
142 scale: ScaleKind::Chromatic,
143 direction: SequenceDirection::Ascending,
144 octave: Octave::O4,
145 length: 64,
146 },
147 starting_from_pad: 0,
148 tonic_highlighting_enabled: true,
149 tonic_color: PadColor::Red,
150 }
151 }
152}
153
154pub struct ColorMappingState {
155 pub color: PadColor,
156 pub length: usize,
157 pub starting_from_pad: usize,
158}
159impl Default for ColorMappingState {
160 fn default() -> Self {
161 Self {
162 color: PadColor::Off,
163 length: 64,
164 starting_from_pad: 0,
165 }
166 }
167}
168
169fn render_header(ui: &mut Ui, ui_state: &mut UiState, outbox: &mut Vec<AppMsg>) {
170 const ROW_SPACING: f32 = 8.0;
171
172 if let Some(preset) = &mut ui_state.preset {
173 ui.horizontal(|ui| {
174 ui.label("Slot:");
175 ComboBox::from_id_salt("header_preset_slot")
176 .selected_text(format!("{:?}", preset.global.preset_slot))
177 .show_ui(ui, |ui| {
178 for slot in PresetSlot::iter() {
179 if ui
180 .selectable_value(
181 &mut preset.global.preset_slot,
182 slot,
183 format!("{:?}", slot),
184 )
185 .clicked()
186 {
187 preset.global.preset_slot = slot;
188 }
189 }
190 });
191
192 ui.separator();
193
194 ui.label("Name:");
195
196 let mut text = preset
197 .global
198 .preset_name
199 .0
200 .iter()
201 .map(|&b| if b.is_ascii() { b as char } else { ' ' })
202 .collect::<String>()
203 .trim_end()
204 .to_string();
205
206 ui.add(
207 TextEdit::singleline(&mut text)
208 .char_limit(8)
209 .desired_width(80.0),
210 );
211
212 let mut buf = [b' '; 8];
213 for (i, b) in text.bytes().filter(|b| b.is_ascii()).take(8).enumerate() {
214 buf[i] = b;
215 }
216 preset.global.preset_name = PresetName(buf);
217
218 ui.separator();
219
220 ui.label("Tempo:");
221 let mut tempo_val = preset.global.tempo.0 as u32;
222 if ui
223 .add(DragValue::new(&mut tempo_val).range(30..=300))
224 .changed()
225 {
226 preset.global.tempo = Tempo(tempo_val as u16);
227 }
228
229 ui.separator();
230
231 ui.label("Division:");
232 ComboBox::from_id_salt("header_time_division")
233 .selected_text(format!("{}", preset.global.time_division))
234 .show_ui(ui, |ui| {
235 for variant in TimeDivision::iter() {
236 if ui
237 .selectable_value(
238 &mut preset.global.time_division,
239 variant,
240 format!("{}", variant),
241 )
242 .clicked()
243 {
244 preset.global.time_division = variant;
245 }
246 }
247 });
248 });
249
250 ui.add_space(ROW_SPACING);
251
252 ui.horizontal(|ui| {
253 ui.label("Div Switch:");
254 ComboBox::from_id_salt("header_time_division_switch")
255 .selected_text(format!("{}", preset.global.time_division_switch))
256 .show_ui(ui, |ui| {
257 for variant in TriggerKind::iter() {
258 if ui
259 .selectable_value(
260 &mut preset.global.time_division_switch,
261 variant,
262 format!("{}", variant),
263 )
264 .clicked()
265 {
266 preset.global.time_division_switch = variant;
267 }
268 }
269 });
270
271 ui.separator();
272
273 ui.label("Repeat:");
274 ComboBox::from_id_salt("header_note_repeat_switch")
275 .selected_text(format!("{}", preset.global.note_repeat_switch))
276 .show_ui(ui, |ui| {
277 for variant in TriggerKind::iter() {
278 if ui
279 .selectable_value(
280 &mut preset.global.note_repeat_switch,
281 variant,
282 format!("{}", variant),
283 )
284 .clicked()
285 {
286 preset.global.note_repeat_switch = variant;
287 }
288 }
289 });
290
291 ui.separator();
292
293 ui.label("Gate:");
294 let mut gate_val = preset.global.gate as u8;
295 if ui
296 .add(DragValue::new(&mut gate_val).range(0..=100))
297 .changed()
298 && let Ok(g) = GateValue::try_from(gate_val)
299 {
300 preset.global.gate = g;
301 }
302
303 ui.separator();
304
305 ui.label("Swing:");
306 ComboBox::from_id_salt("header_swing")
307 .selected_text(format!("{}", preset.global.swing))
308 .show_ui(ui, |ui| {
309 for variant in SwingKind::iter() {
310 if ui
311 .selectable_value(
312 &mut preset.global.swing,
313 variant,
314 format!("{}", variant),
315 )
316 .clicked()
317 {
318 preset.global.swing = variant;
319 }
320 }
321 });
322
323 ui.separator();
324
325 ui.label("Transport:");
326 ComboBox::from_id_salt("header_transport")
327 .selected_text(format!("{}", preset.global.transport))
328 .show_ui(ui, |ui| {
329 for variant in TransportKind::iter() {
330 if ui
331 .selectable_value(
332 &mut preset.global.transport,
333 variant,
334 format!("{}", variant),
335 )
336 .clicked()
337 {
338 preset.global.transport = variant;
339 }
340 }
341 });
342 });
343 } else {
344 ui.label("No preset loaded");
345 }
346
347 ui.add_space(ROW_SPACING);
348
349 ui.horizontal(|ui| {
350 if let Some(preset) = &ui_state.preset
351 && ui.button("Load from device").clicked()
352 {
353 ui_state.user_error = None;
354 outbox.push(AppMsg::Ui(UiEffect::RequestPresetFromDevice(
355 preset.global.preset_slot,
356 )));
357 }
358
359 if let Some(preset) = ui_state.preset
360 && ui.button("Send to device").clicked()
361 {
362 ui_state.user_error = None;
363 outbox.push(AppMsg::Ui(UiEffect::SendPresetToDevice(Box::new(preset))));
364 }
365
366 if let Some(status) = &ui_state.user_error {
367 let color = match status.kind {
368 midilab::message::UserMsgKind::Status => Color32::GREEN,
369 midilab::message::UserMsgKind::Error => Color32::RED,
370 };
371
372 ui.colored_label(color, &status.msg);
373 }
374 });
375}
376
377fn render_pad_patterns(ui: &mut Ui, ui_state: &mut UiState) {
378 ui.horizontal(|ui| {
379 ui.set_min_height(128.0);
380 render_note_mapping(ui, ui_state);
381
382 ui.add_space(32.0);
383
384 render_color_mapping(ui, ui_state);
385
386 ui.add_space(32.0);
387
388 render_on_color_mapping(ui, ui_state);
389
390 ui.add_space(32.0);
391
392 pad_compare_table(ui, ui_state);
393 });
394}
395
396fn render_note_mapping(ui: &mut Ui, ui_state: &mut UiState) {
397 ui.vertical(|ui| {
398 ui.label("Note Mapping");
399
400 ui.horizontal(|ui| {
401 ui.label("Tonic");
402 ComboBox::from_id_salt("note_mapping_tonic")
403 .selected_text(ui_state.note_mapping.scale_seq.tonic.to_string())
404 .show_ui(ui, |ui| {
405 for p in PitchClass::iter() {
406 if ui
407 .selectable_value(
408 &mut ui_state.note_mapping.scale_seq.tonic,
409 p,
410 p.to_string(),
411 )
412 .clicked()
413 {
414 ui_state.note_mapping.scale_seq.tonic = p;
415 }
416 }
417 });
418 });
419
420 ui.horizontal(|ui| {
421 ui.label("Scale");
422 ComboBox::from_id_salt("note_mapping_scale")
423 .selected_text(ui_state.note_mapping.scale_seq.scale.to_string())
424 .show_ui(ui, |ui| {
425 for s in ScaleKind::iter() {
426 if ui
427 .selectable_value(
428 &mut ui_state.note_mapping.scale_seq.scale,
429 s,
430 s.to_string(),
431 )
432 .clicked()
433 {
434 ui_state.note_mapping.scale_seq.scale = s;
435 }
436 }
437 });
438 });
439
440 ui.horizontal(|ui| {
441 ui.label("Octave");
442 ComboBox::from_id_salt("note_mapping_octave")
443 .selected_text(ui_state.note_mapping.scale_seq.octave.to_string())
444 .show_ui(ui, |ui| {
445 for s in Octave::iter() {
446 if ui
447 .selectable_value(
448 &mut ui_state.note_mapping.scale_seq.octave,
449 s,
450 s.to_string(),
451 )
452 .clicked()
453 {
454 ui_state.note_mapping.scale_seq.octave = s;
455 }
456 }
457 });
458 });
459
460 ui.horizontal(|ui| {
461 ui.label("Direction");
462 ComboBox::from_id_salt("note_mapping_direction")
463 .selected_text(ui_state.note_mapping.scale_seq.direction.to_string())
464 .show_ui(ui, |ui| {
465 for s in SequenceDirection::iter() {
466 if ui
467 .selectable_value(
468 &mut ui_state.note_mapping.scale_seq.direction,
469 s,
470 s.to_string(),
471 )
472 .clicked()
473 {
474 ui_state.note_mapping.scale_seq.direction = s;
475 }
476 }
477 });
478 });
479
480 ui.horizontal(|ui| {
481 ui.label("Starting from Pad");
482
483 ui.add(DragValue::new(&mut ui_state.note_mapping.starting_from_pad).range(0..=63))
484 });
485
486 ui.horizontal(|ui| {
487 ui.label("Length");
488
489 ui.add(DragValue::new(&mut ui_state.note_mapping.scale_seq.length).range(1..=64))
490 });
491
492 ui.add_space(8.0);
493
494 ui.checkbox(
495 &mut ui_state.note_mapping.tonic_highlighting_enabled,
496 "Tonic highlighting",
497 );
498 if ui_state.note_mapping.tonic_highlighting_enabled {
499 ui.horizontal(|ui| {
500 ui.label("Tonic color");
501 ComboBox::from_id_salt("tonic_highlight_color")
502 .selected_text(ui_state.note_mapping.tonic_color.to_string())
503 .show_ui(ui, |ui| {
504 for c in PadColor::iter() {
505 if ui
506 .selectable_value(
507 &mut ui_state.note_mapping.tonic_color,
508 c,
509 c.to_string(),
510 )
511 .clicked()
512 {
513 ui_state.note_mapping.tonic_color = c;
514 }
515 }
516 });
517 });
518 }
519
520 ui.add_space(8.0);
521
522 let resp = ui.button("Set pattern");
523 if resp.clicked()
524 && let Some(preset) = ui_state.preset.as_mut()
525 {
526 let scale_seq = ui_state.note_mapping.scale_seq;
527 preset.pads.set_note_pattern(
528 ui_state.note_mapping.starting_from_pad,
529 NotePattern::Scale(scale_seq),
530 );
531
532 if ui_state.note_mapping.tonic_highlighting_enabled {
533 let tonic_color = (scale_seq.tonic, ui_state.note_mapping.tonic_color);
534 preset.pads.highlight_tonics(
535 ui_state.note_mapping.starting_from_pad,
536 scale_seq.length,
537 tonic_color,
538 );
539 }
540 }
541 });
542}
543
544fn render_color_mapping(ui: &mut Ui, ui_state: &mut UiState) {
545 ui.vertical(|ui| {
546 ui.label("Color Mapping");
547
548 ui.horizontal(|ui| {
549 ui.label("Color");
550 ComboBox::from_id_salt("color_mapping_color")
551 .selected_text(ui_state.off_color_mapping.color.to_string())
552 .show_ui(ui, |ui| {
553 for c in PadColor::iter() {
554 if ui
555 .selectable_value(
556 &mut ui_state.off_color_mapping.color,
557 c,
558 c.to_string(),
559 )
560 .clicked()
561 {
562 ui_state.off_color_mapping.color = c;
563 }
564 }
565 });
566 });
567
568 ui.horizontal(|ui| {
569 ui.label("Starting from Pad");
570 ui.add(DragValue::new(&mut ui_state.off_color_mapping.starting_from_pad).range(0..=63))
571 });
572
573 ui.horizontal(|ui| {
574 ui.label("Length");
575 ui.add(DragValue::new(&mut ui_state.off_color_mapping.length).range(1..=64))
576 });
577
578 let resp = ui.button("Set color");
579 if resp.clicked()
580 && let Some(preset) = ui_state.preset.as_mut()
581 {
582 preset.pads.set_off_color_pattern(
583 ui_state.off_color_mapping.starting_from_pad,
584 ui_state.off_color_mapping.length,
585 ColorPattern::Contiguous(ui_state.off_color_mapping.color),
586 );
587 }
588 });
589}
590
591fn render_on_color_mapping(ui: &mut Ui, ui_state: &mut UiState) {
592 ui.vertical(|ui| {
593 ui.label("On Color Mapping");
594
595 ui.horizontal(|ui| {
596 ui.label("Color");
597 ComboBox::from_id_salt("on_color_mapping_color")
598 .selected_text(ui_state.on_color_mapping.color.to_string())
599 .show_ui(ui, |ui| {
600 for c in PadColor::iter() {
601 if ui
602 .selectable_value(
603 &mut ui_state.on_color_mapping.color,
604 c,
605 c.to_string(),
606 )
607 .clicked()
608 {
609 ui_state.on_color_mapping.color = c;
610 }
611 }
612 });
613 });
614
615 ui.horizontal(|ui| {
616 ui.label("Starting from Pad");
617 ui.add(DragValue::new(&mut ui_state.on_color_mapping.starting_from_pad).range(0..=63))
618 });
619
620 ui.horizontal(|ui| {
621 ui.label("Length");
622 ui.add(DragValue::new(&mut ui_state.on_color_mapping.length).range(1..=64))
623 });
624
625 let resp = ui.button("Set color");
626 if resp.clicked()
627 && let Some(preset) = ui_state.preset.as_mut()
628 {
629 preset.pads.set_on_color_pattern(
630 ui_state.on_color_mapping.starting_from_pad,
631 ui_state.on_color_mapping.length,
632 ColorPattern::Contiguous(ui_state.on_color_mapping.color),
633 );
634 }
635 });
636}
637
638fn render_all_pad_banks(
639 ui: &mut Ui,
640 selected_pad: &mut Option<UserSelection>,
641 pad_repo: &mut PadRepository,
642) {
643 let banks: Vec<Vec<Pad>> = pad_repo
644 .pads
645 .chunks(16)
646 .map(|chunk| chunk.to_vec())
647 .collect();
648
649 ui.add_space(16.0);
650 ui.horizontal(|ui| {
651 ui.add_space(16.0);
652 for (bank_id, bank) in banks.into_iter().enumerate() {
653 let bank_label = BANKS[bank_id].to_string();
654 render_pad_bank(ui, selected_pad, bank, bank_label);
655 ui.add_space(32.0);
656 }
657 });
658}
659
660fn render_pad_bank(
661 ui: &mut Ui,
662 selected_pad: &mut Option<UserSelection>,
663 pads: Vec<Pad>,
664 label: String,
665) {
666 let reordered: Vec<Pad> = pads.chunks(4).rev().flatten().cloned().collect();
667
668 ui.vertical(|ui| {
669 ui.label(format!("Bank {}", label));
670 ui.add_space(8.0);
671 Grid::new(format!("pad_bank_{}", label))
672 .num_columns(4)
673 .spacing([8.0, 8.0])
674 .show(ui, |ui| {
675 for (i, pad) in reordered.into_iter().enumerate() {
676 render_pad(ui, selected_pad, pad);
677 if (i + 1) % 4 == 0 {
678 ui.end_row();
679 }
680 }
681 });
682 });
683}
684
685fn render_pad(ui: &mut Ui, selected_pad: &mut Option<UserSelection>, pad: Pad) {
686 let (rect, resp) = ui.allocate_exact_size(PAD_DIMENSIONS, egui::Sense::click());
687
688 ui.painter().rect_filled(rect, 4.0, Color32::DARK_GRAY);
689
690 if let Some(UserSelection::Pad { id }) = selected_pad
691 && pad.id == *id
692 {
693 ui.painter().rect_stroke(
694 rect,
695 4.0,
696 egui::Stroke::new(1.5, Color32::WHITE),
697 egui::StrokeKind::Outside,
698 );
699 }
700
701 let half_w = rect.width() * 0.5;
702 let half_h = rect.height() * 0.5;
703
704 let tl = egui::Rect::from_min_size(rect.min, vec2(half_w, half_h));
705 let tr = egui::Rect::from_min_size(rect.min + vec2(half_w, 0.0), vec2(half_w, half_h));
706 let bl = egui::Rect::from_min_size(rect.min + vec2(0.0, half_h), vec2(half_w, half_h));
707 let br = egui::Rect::from_min_size(rect.min + vec2(half_w, half_h), vec2(half_w, half_h));
708
709 ui.painter().text(
710 tl.center(),
711 egui::Align2::CENTER_CENTER,
712 pad.id.to_string(),
713 egui::FontId::proportional(12.0),
714 Color32::WHITE,
715 );
716
717 ui.painter().text(
718 tr.center(),
719 egui::Align2::CENTER_CENTER,
720 format!("♩{}", pad.note),
721 egui::FontId::proportional(12.0),
722 Color32::WHITE,
723 );
724
725 let (r, g, b) = *pad.off_color.as_rgb_color();
726 let off_color = Color32::from_rgb(r, g, b);
727 ui.painter().rect_filled(bl.shrink(4.0), 4.0, off_color);
728
729 let (r, g, b) = *pad.on_color.as_rgb_color();
730 let on_color = Color32::from_rgb(r, g, b);
731 ui.painter().rect_filled(br.shrink(4.0), 4.0, on_color);
732
733 if resp.clicked() {
734 click_pad(pad.id, selected_pad);
735 }
736}
737
738fn render_editor(ui: &mut Ui, ui_state: &mut UiState) {
739 if let Some(preset) = &mut ui_state.preset {
740 render_all_pad_banks(ui, &mut ui_state.selected_item, &mut preset.pads);
741 } else {
742 ui.label("no pad repo");
743 }
744}
745
746fn click_pad(id: usize, selected_pad: &mut Option<UserSelection>) {
747 if *selected_pad == Some(UserSelection::Pad { id }) {
748 *selected_pad = None;
749 } else {
750 *selected_pad = Some(UserSelection::Pad { id });
751 }
752}
753
754fn pad_compare_table(ui: &mut Ui, ui_state: &mut UiState) {
755 ui.vertical(|ui| {
756 let idx_label = match ui_state.selected_item {
757 Some(UserSelection::Pad { id }) => id.to_string(),
758 None => "None".to_string(),
759 };
760 ui.label(format!("Selected Pad: {}", idx_label));
761
762 if let Some(UserSelection::Pad { id: index }) = ui_state.selected_item
763 && let Some(preset) = &mut ui_state.preset
764 && let Some(pad) = preset.pads.pads.iter_mut().find(|p| p.id == index)
765 {
766 render_pad_compare_grid(ui, pad);
767 }
768 });
769}
770
771fn render_pad_compare_grid(ui: &mut Ui, pad: &mut Pad) {
772 Grid::new("pad_compare_grid")
773 .striped(true)
774 .spacing([16.0, 6.0])
775 .show(ui, |ui| {
776 ui.label("Field");
777 ui.label("Value");
778 ui.end_row();
779
780 row_edit_pad_kind(ui, "kind", &mut pad.kind);
781 row_edit_u8(ui, "channel", &mut pad.channel);
782 row_edit_note(ui, "note", &mut pad.note);
783 row_edit_midi2din(ui, "midi2din", &mut pad.midi2din);
784 row_edit_trigger_kind(ui, "trigger", &mut pad.trigger);
785 row_edit_aftertouch_kind(ui, "aftertouch", &mut pad.aftertouch);
786 row_edit_u8(ui, "program", &mut pad.program);
787 row_edit_u8(ui, "msb", &mut pad.msb);
788 row_edit_u8(ui, "lsb", &mut pad.lsb);
789 row_edit_pad_color(ui, "off color", &mut pad.off_color);
790 row_edit_pad_color(ui, "on color", &mut pad.on_color);
791 });
792}
793
794fn row_edit_u8(ui: &mut Ui, name: &str, value: &mut u8) {
795 ui.label(name);
796 let mut val = *value as u32;
797 if ui.add(DragValue::new(&mut val).range(0..=127)).changed() {
798 *value = val as u8;
799 }
800 ui.end_row();
801}
802
803fn row_edit_note(ui: &mut Ui, name: &str, value: &mut Note) {
804 ui.label(name);
805 let mut val = *value as u32;
806 if ui.add(DragValue::new(&mut val).range(0..=127)).changed()
807 && let Ok(note) = Note::try_from(val as u8)
808 {
809 *value = note;
810 }
811 ui.end_row();
812}
813
814fn row_edit_pad_kind(ui: &mut Ui, name: &str, value: &mut PadKind) {
815 ui.label(name);
816 ComboBox::from_id_salt(name)
817 .selected_text(format!("{}", value))
818 .show_ui(ui, |ui| {
819 for variant in PadKind::iter() {
820 ui.selectable_value(value, variant, format!("{}", variant));
821 }
822 });
823 ui.end_row();
824}
825
826fn row_edit_trigger_kind(ui: &mut Ui, name: &str, value: &mut TriggerKind) {
827 ui.label(name);
828 ComboBox::from_id_salt(name)
829 .selected_text(format!("{}", value))
830 .show_ui(ui, |ui| {
831 for variant in TriggerKind::iter() {
832 ui.selectable_value(value, variant, format!("{}", variant));
833 }
834 });
835 ui.end_row();
836}
837
838fn row_edit_aftertouch_kind(ui: &mut Ui, name: &str, value: &mut AfterTouchKind) {
839 ui.label(name);
840 ComboBox::from_id_salt(name)
841 .selected_text(format!("{}", value))
842 .show_ui(ui, |ui| {
843 for variant in AfterTouchKind::iter() {
844 ui.selectable_value(value, variant, format!("{}", variant));
845 }
846 });
847 ui.end_row();
848}
849
850fn row_edit_pad_color(ui: &mut Ui, name: &str, value: &mut PadColor) {
851 ui.label(name);
852 ComboBox::from_id_salt(name)
853 .selected_text(format!("{}", value))
854 .show_ui(ui, |ui| {
855 for variant in PadColor::iter() {
856 ui.selectable_value(value, variant, format!("{}", variant));
857 }
858 });
859 ui.end_row();
860}
861
862fn row_edit_midi2din(ui: &mut Ui, name: &str, value: &mut Midi2Din) {
863 ui.label(name);
864 ComboBox::from_id_salt(name)
865 .selected_text(format!("{}", value))
866 .show_ui(ui, |ui| {
867 for variant in Midi2Din::iter() {
868 ui.selectable_value(value, variant, format!("{}", variant));
869 }
870 });
871 ui.end_row();
872}