use crate::midi_learn::MidiPortDiagnostics;
use egui::{ComboBox, Ui};
use gled_model::asset::midi_controller::normalize_controller_key;
use gled_model::asset::project::Project;
use gled_model::asset::{Asset, midi_controller::MidiController};
use gled_model::collection::Collections;
use std::collections::BTreeSet;
#[derive(Clone)]
pub(super) struct MidiControllerSelectionRow {
port_name: String,
input_connected: bool,
output_connected: bool,
}
#[derive(Clone)]
pub(super) struct MidiMappingAssetOptions {
options: Vec<(
Option<gled_model::asset_id::AssetId<MidiController>>,
String,
)>,
}
pub(super) fn midi_mapping_settings(
ui: &mut Ui,
project: &mut Project,
controllers: &[MidiControllerSelectionRow],
options: &MidiMappingAssetOptions,
) {
ui.heading("Active MIDI Mapping by Controller");
ui.add_space(3.0);
if controllers.is_empty() {
ui.label("No MIDI controllers detected yet. Open MIDI Controllers window to inspect incoming ports.");
return;
}
if options.options.len() <= 1 {
ui.small("No MIDI mapping assets available. You can still remove existing assignments.");
}
egui::ScrollArea::vertical()
.id_salt("external_devices_midi_mapping_list")
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible)
.show(ui, |ui| {
for controller in controllers {
ui.separator();
ui.label(&controller.port_name);
ui.small(format!(
"Input: {} | Output: {}",
if controller.input_connected {
"connected"
} else {
"missing"
},
if controller.output_connected {
"connected"
} else {
"missing"
}
));
let current = project.midi_active_mappings.get(&controller.port_name);
let current = current.or_else(|| {
let normalized = normalize_controller_key(&controller.port_name);
let mut matches = project
.midi_active_mappings
.iter()
.filter(|(key, _)| normalize_controller_key(key) == normalized)
.map(|(_, mapping)| mapping);
let first = matches.next()?;
if matches.next().is_some() {
return None;
}
Some(first)
});
let mut selected_index = options
.options
.iter()
.position(|(controller_id, _)| current.is_some_and(|c| c == controller_id))
.unwrap_or(0);
let original_index = selected_index;
ComboBox::new(format!("midi_mapping_{}", controller.port_name), "")
.selected_text(options.options[selected_index].1.clone())
.show_ui(ui, |ui| {
for (index, (_, label)) in options.options.iter().enumerate() {
ui.selectable_value(&mut selected_index, index, label);
}
});
let remove_clicked = selected_index != 0 && ui.button("Remove Mapping").clicked();
if remove_clicked {
selected_index = 0;
}
let combobox_changed = selected_index != original_index;
if combobox_changed || remove_clicked {
let (controller_id, _) = &options.options[selected_index];
let exact_key = controller.port_name.clone();
match controller_id {
Some(controller_id) => {
project
.midi_active_mappings
.insert(exact_key, Some(*controller_id));
}
None => {
project.midi_active_mappings.remove(&exact_key);
}
}
}
}
});
}
pub(super) fn collect_midi_mapping_options(collections: &Collections) -> MidiMappingAssetOptions {
let mut options = vec![(None, "None".to_owned())];
let mut controllers: Vec<_> = Asset::<MidiController>::all(collections);
controllers.sort_by(|a, b| a.name().cmp(b.name()));
for controller in controllers {
options.push((Some(controller.id), controller.name().to_owned()));
}
MidiMappingAssetOptions { options }
}
pub(super) fn collect_midi_controller_rows(
project: &Project,
diagnostics: &[MidiPortDiagnostics],
) -> Vec<MidiControllerSelectionRow> {
let configured_ports = project
.midi_active_mappings
.keys()
.map(|key| normalize_controller_key(key));
let mut all_ports: BTreeSet<String> = BTreeSet::new();
for diag in diagnostics {
all_ports.insert(normalize_controller_key(&diag.port_name));
}
for output_port in crate::ui::frame_requests::devices().midi_outputs {
all_ports.insert(normalize_controller_key(&output_port));
}
for configured in configured_ports {
all_ports.insert(configured);
}
all_ports
.into_iter()
.filter(|port_name| !is_gled_midi_port(port_name))
.map(|port_name| {
let input_connected = diagnostics.iter().any(|diag| {
normalize_controller_key(&diag.port_name) == port_name && diag.input_connected
});
let output_connected = diagnostics.iter().any(|diag| {
normalize_controller_key(&diag.port_name) == port_name && diag.output_connected
});
MidiControllerSelectionRow {
port_name,
input_connected,
output_connected,
}
})
.collect()
}
fn is_gled_midi_port(port_name: &str) -> bool {
port_name.contains("gled_read_input") || port_name.contains("gled_write_output")
}