use super::binding_helpers::{add_matching_output_binding, output_binding_status_data1};
use super::color_editor::render_color_mappings_section;
use super::input_column::render_input_column;
use super::learn_capture::apply_learn_captures;
use super::output_column::render_output_column;
use super::test_panel::render_test_device_box;
use super::{MidiControllerTestState, iconized};
use crate::midi_learn::{LearnState, MidiPortDiagnostics};
use egui::Ui;
use egui_phosphor_icons::icons;
use gled_model::{
asset::{
Asset,
midi_controller::{MidiController, normalize_controller_key},
},
collection::Collections,
};
use gled_protocol::midi::TestCommand;
use kanal::Sender;
pub(super) fn midi_controller_editor(
ui: &mut Ui,
controller: &mut Asset<MidiController>,
test_state: &mut MidiControllerTestState,
test_command_sender: &Sender<TestCommand>,
dirty: &mut bool,
project: &mut Option<gled_model::asset::project::Project>,
collections: &mut Collections,
filter: &mut String,
diagnostics: &[MidiPortDiagnostics],
learn_state: &mut LearnState,
) {
learn_state.flush_timeouts();
apply_learn_captures(controller, dirty, collections, learn_state);
egui::ScrollArea::vertical().show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(iconized(ui, icons::FUNNEL, " Filter:"));
ui.text_edit_singleline(filter);
if !filter.is_empty() && ui.button(icons::X).clicked() {
filter.clear();
}
});
ui.add_space(4.0);
let filter_str = filter.as_str();
let controller_id = controller.id;
let mapping = &mut controller.data.mapping;
let test_device_selected = test_state.selected_output_port.is_some();
let hovered_status_data1 = test_state.hovered_status_data1;
let mut hovered_status_data1_next = None;
let selected_test_port = render_test_device_box(
ui,
test_state,
diagnostics,
controller_id,
test_command_sender,
);
if let Some(port_name) = selected_test_port {
disconnect_project_mapping_for_test_port(project, &port_name);
}
ui.add_space(6.0);
test_state.value_preview_binding_indices.clear();
test_state.blackout_blink_preview_binding_indices.clear();
let mut remove_input = None;
let mut pending_matching_output = Vec::new();
let mut remove_output = None;
ui.columns(2, |cols| {
let (left, right) = cols.split_at_mut(1);
let (ri, pmo) = render_input_column(
&mut left[0],
&mut mapping.input_bindings,
filter_str,
hovered_status_data1,
&mut hovered_status_data1_next,
dirty,
controller_id,
learn_state,
);
remove_input = ri;
pending_matching_output = pmo;
remove_output = render_output_column(
&mut right[0],
&mut mapping.output_bindings,
&mut mapping.color_mappings,
test_device_selected,
filter_str,
hovered_status_data1,
&mut hovered_status_data1_next,
&mut test_state.value_preview_binding_indices,
&mut test_state.blackout_blink_preview_binding_indices,
dirty,
controller_id,
learn_state,
);
});
test_state.hovered_status_data1 = hovered_status_data1_next;
ui.separator();
render_color_mappings_section(
ui,
&mut mapping.color_mappings,
&mapping.output_bindings,
test_command_sender,
dirty,
controller_id,
);
for (name, status, data1, action) in pending_matching_output {
if add_matching_output_binding(mapping, &name, status, data1, &action) {
*dirty = true;
}
}
if let Some(index) = remove_input {
mapping.input_bindings.remove(index);
*dirty = true;
}
if let Some(index) = remove_output {
if (test_state.value_preview_binding_indices.contains(&index)
|| test_state
.value_preview_binding_indices_previous
.contains(&index)
|| test_state
.blackout_blink_preview_binding_indices
.contains(&index)
|| test_state
.blackout_blink_preview_binding_indices_previous
.contains(&index))
&& let Some((status, data1)) = output_binding_status_data1(
&mapping
.output_bindings
.get(index)
.cloned()
.unwrap_or_default(),
&mapping.color_mappings,
)
{
let _ = test_command_sender.send(TestCommand::ClearOutput { status, data1 });
}
mapping.output_bindings.remove(index);
*dirty = true;
}
emit_test_output_commands(&mapping.output_bindings, test_state, test_command_sender);
});
}
fn disconnect_project_mapping_for_test_port(
project: &mut Option<gled_model::asset::project::Project>,
selected_port_name: &str,
) {
let Some(project) = project.as_mut() else {
return;
};
let normalized_selected = normalize_controller_key(selected_port_name);
project
.midi_active_mappings
.retain(|port_name, _| normalize_controller_key(port_name) != normalized_selected);
}
fn emit_test_output_commands(
bindings: &[gled_model::asset::midi_controller::MidiOutputBinding],
test_state: &mut MidiControllerTestState,
test_command_sender: &Sender<TestCommand>,
) {
use gled_model::asset::midi_controller::{MidiOutputBindingKind, MidiValueSource};
for &index in &test_state.value_preview_binding_indices {
if let Some(binding) = bindings.get(index)
&& let MidiOutputBindingKind::Value(value_output) = &binding.kind
{
let _ = test_command_sender.send(TestCommand::SendOutput {
status: value_output.status,
data1: value_output.data1,
value: value_output.active_value,
});
}
}
for &index in &test_state.value_preview_binding_indices_previous {
if !test_state.value_preview_binding_indices.contains(&index)
&& let Some(binding) = bindings.get(index)
&& let MidiOutputBindingKind::Value(value_output) = &binding.kind
{
let _ = test_command_sender.send(TestCommand::ClearOutput {
status: value_output.status,
data1: value_output.data1,
});
}
}
test_state.value_preview_binding_indices_previous =
test_state.value_preview_binding_indices.clone();
for &index in &test_state.blackout_blink_preview_binding_indices {
if let Some(binding) = bindings.get(index)
&& let MidiOutputBindingKind::Value(value_output) = &binding.kind
&& let MidiValueSource::BeatFlankPulse {
blackout_blink_value: Some(v),
..
} = &value_output.source
{
let _ = test_command_sender.send(TestCommand::SendOutput {
status: value_output.status,
data1: value_output.data1,
value: *v,
});
}
}
for &index in &test_state.blackout_blink_preview_binding_indices_previous {
if !test_state
.blackout_blink_preview_binding_indices
.contains(&index)
&& let Some(binding) = bindings.get(index)
&& let MidiOutputBindingKind::Value(value_output) = &binding.kind
{
let _ = test_command_sender.send(TestCommand::ClearOutput {
status: value_output.status,
data1: value_output.data1,
});
}
}
test_state.blackout_blink_preview_binding_indices_previous =
test_state.blackout_blink_preview_binding_indices.clone();
}