use crate::midi_learn::{LearnState, MidiLearnRequest, MidiLearnTarget};
use egui::{ComboBox, DragValue, Ui};
use egui_phosphor_icons::icons;
use gled_model::asset::midi_controller::{
MidiController, MidiInputAction, MidiInputActionKind, MidiInputBinding,
};
use strum::IntoEnumIterator;
use super::{
action_converters::{
effect_target_editor, encoder_editor, input_action_from_kind, scene_target_editor,
},
binding_helpers::{input_binding_matches_filter, midi_status_label},
iconized,
};
pub(super) fn render_input_column(
ui: &mut Ui,
bindings: &mut Vec<MidiInputBinding>,
filter: &str,
hovered_status_data1: Option<(u8, u8)>,
hovered_status_data1_next: &mut Option<(u8, u8)>,
dirty: &mut bool,
controller_id: gled_model::asset_id::AssetId<MidiController>,
learn_state: &mut LearnState,
) -> (Option<usize>, Vec<(String, u8, u8, MidiInputAction)>) {
let mut remove_input = None;
let mut pending_matching_output = Vec::new();
let mut duplicate_inputs = Vec::new();
ui.horizontal(|ui| {
ui.label(icons::ARROW_SQUARE_IN);
ui.heading("Input Bindings");
});
ui.horizontal(|ui| {
if ui
.button(iconized(ui, icons::PLUS, " Add Input Binding"))
.clicked()
{
bindings.insert(0, MidiInputBinding::default());
*dirty = true;
}
});
for (index, binding) in bindings.iter_mut().enumerate() {
if !input_binding_matches_filter(binding, filter) {
continue;
}
let status_data1 = (binding.trigger.status, binding.trigger.data1);
let highlight = hovered_status_data1.is_some_and(|hovered| hovered == status_data1);
let base_stroke = ui.visuals().widgets.noninteractive.bg_stroke;
let response = egui::Frame::group(ui.style())
.stroke(if highlight {
egui::Stroke::new(base_stroke.width, ui.visuals().selection.stroke.color)
} else {
base_stroke
})
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(format!("Input {}", index + 1));
let learning = learn_state.active_request().is_some_and(|request| {
request.controller_id == controller_id
&& request.target == MidiLearnTarget::InputBinding(index)
});
if ui
.button(if learning {
iconized(ui, icons::MICROPHONE, " Waiting for MIDI...")
} else {
iconized(ui, icons::MICROPHONE, " Learn")
})
.clicked()
{
learn_state.arm(MidiLearnRequest {
controller_id,
target: MidiLearnTarget::InputBinding(index),
});
}
if ui
.button(iconized(ui, icons::PLUS, " Duplicate"))
.clicked()
{
duplicate_inputs.push(binding.clone());
}
if ui.button(iconized(ui, icons::TRASH, " Remove")).clicked() {
remove_input = Some(index);
}
});
if ui.text_edit_singleline(&mut binding.name).changed() {
*dirty = true;
}
ui.horizontal(|ui| {
ui.label("Status");
if ui
.add(DragValue::new(&mut binding.trigger.status).range(0..=255))
.changed()
{
*dirty = true;
}
ui.label("Data1");
if ui
.add(DragValue::new(&mut binding.trigger.data1).range(0..=127))
.changed()
{
*dirty = true;
}
});
ui.label(format!(
"Detected Message: {}",
midi_status_label(binding.trigger.status)
));
if ui
.checkbox(
&mut binding.trigger.match_data1,
"Match Data1 (disable for Pitch Bend style controls)",
)
.changed()
{
*dirty = true;
}
let mut action_kind = MidiInputActionKind::from(&binding.action);
ComboBox::new(format!("{}_input_kind_{index}", controller_id), "")
.selected_text(action_kind.to_string())
.show_ui(ui, |ui| {
for candidate in MidiInputActionKind::iter() {
let response = ui.selectable_value(
&mut action_kind,
candidate,
candidate.to_string(),
);
if candidate == MidiInputActionKind::SetSceneEffectLayout {
response.on_hover_text(
"0 lets the effect follow its tile's layout, 1 is the topology, \
2 and up the layouts in stack order",
);
}
}
});
let before = binding.action.clone();
binding.action = input_action_from_kind(action_kind, binding.action.clone());
if binding.action != before {
*dirty = true;
}
match &mut binding.action {
gled_model::asset::midi_controller::MidiInputAction::SelectScene { target }
| gled_model::asset::midi_controller::MidiInputAction::ToggleSceneActive {
target
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneActive { target }
| gled_model::asset::midi_controller::MidiInputAction::FlashScene { target }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneOpacity { target }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneInputDimmer {
target
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneBeatOffset {
target
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneIgnoreMainDimmer {
target
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneSetOffsetOnFlash {
target
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectSettingF32 {
target,
..
}
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectOpacity { target, .. }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectColorShift { target, .. }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectBeatProgression { target, .. }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectBeatOffset { target, .. }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectSpeedExponent { target, .. }
| gled_model::asset::midi_controller::MidiInputAction::SetSceneEffectLayout { target, .. } => {
scene_target_editor(
ui,
target,
dirty,
format!("{}_input_target_{index}", controller_id),
);
}
_ => {}
}
match &mut binding.action {
MidiInputAction::SetSceneEffectSettingF32 {
effect,
setting_index,
..
} => {
effect_target_editor(
ui,
effect,
dirty,
format!("{controller_id}_input_effect_{index}"),
);
ui.horizontal(|ui| {
ui.label("Setting Index");
if ui.add(DragValue::new(setting_index).range(0..=5)).changed() {
*dirty = true;
}
});
}
MidiInputAction::SetSceneEffectOpacity { effect, .. }
| MidiInputAction::SetSceneEffectColorShift { effect, .. }
| MidiInputAction::SetSceneEffectBeatProgression { effect, .. }
| MidiInputAction::SetSceneEffectBeatOffset { effect, .. }
| MidiInputAction::SetSceneEffectSpeedExponent { effect, .. }
| MidiInputAction::SetSceneEffectLayout { effect, .. } => {
effect_target_editor(
ui,
effect,
dirty,
format!("{controller_id}_input_effect_{index}"),
);
}
MidiInputAction::SelectSceneEffect { index: effect_index } => {
ui.horizontal(|ui| {
ui.label("Effect Index");
if ui.add(DragValue::new(effect_index).range(0..=255)).changed() {
*dirty = true;
}
});
}
MidiInputAction::SetMainDimmerRelative { encoder }
| MidiInputAction::SetSpeedRelative { encoder }
| MidiInputAction::SelectSceneRelative { encoder }
| MidiInputAction::SelectSceneEffectRelative { encoder } => {
encoder_editor(
ui,
encoder,
dirty,
format!("{controller_id}_input_encoding_{index}"),
);
}
_ => {}
}
if matches!(
binding.action,
gled_model::asset::midi_controller::MidiInputAction::SelectSceneDistributed
) {
ui.small("Maps value 0..127 evenly across existing scenes in row-major order.");
}
if ui
.button(iconized(ui, icons::PLUS, " Create Matching Output Binding"))
.clicked()
{
pending_matching_output.push((
binding.name.clone(),
binding.trigger.status,
binding.trigger.data1,
binding.action.clone(),
));
}
})
.response;
if response.hovered() {
*hovered_status_data1_next = Some(status_data1);
}
}
if !duplicate_inputs.is_empty() {
bindings.extend(duplicate_inputs);
*dirty = true;
}
(remove_input, pending_matching_output)
}