use std::collections::BTreeSet;
use egui::{ComboBox, DragValue, Ui};
use egui_phosphor_icons::icons;
use gled_model::asset::midi_controller::{
MidiControllerMapping, MidiMode, MidiModeHighlight, normalize_controller_key,
};
use strum::IntoEnumIterator;
use super::iconized;
#[derive(Debug, Default)]
pub(crate) struct BindingFilter {
pub(super) text: String,
pub(super) mode: Option<u8>,
}
impl BindingFilter {
pub(super) fn shows_mode(&self, binding_mode: Option<u8>) -> bool {
match (self.mode, binding_mode) {
(Some(shown), Some(mode)) => shown == mode,
_ => true,
}
}
}
pub(super) fn modes_section(
ui: &mut Ui,
mapping: &mut MidiControllerMapping,
filter: &mut BindingFilter,
dirty: &mut bool,
id: &str,
) {
ui.horizontal(|ui| {
ui.label(iconized(ui, icons::STACK, " Modes"));
if ui
.button(iconized(ui, icons::PLUS, " Add Mode"))
.on_hover_text("Bindings can then work in one mode only; a Select Mode input switches")
.clicked()
{
mapping.modes.push(MidiMode {
name: format!("Mode {}", mapping.modes.len() + 1),
highlight: None,
});
*dirty = true;
}
});
if mapping.modes.is_empty() {
filter.mode = None;
return;
}
let mut remove = None;
egui::Grid::new(format!("{id}_modes")).show(ui, |ui| {
for index in 0..mapping.modes.len() {
let Ok(mode) = u8::try_from(index) else {
break;
};
let uses = mapping.mode_uses(mode);
let Some(entry) = mapping.modes.get_mut(index) else {
break;
};
ui.label(if index == 0 {
format!("{} (start)", index + 1)
} else {
format!("{}", index + 1)
});
if ui.text_edit_singleline(&mut entry.name).changed() {
*dirty = true;
}
ui.label("Highlights");
let label = entry
.highlight
.map_or_else(|| "Nothing".to_owned(), |highlight| highlight.to_string());
ComboBox::new(format!("{id}_mode_highlight_{index}"), "")
.selected_text(label)
.show_ui(ui, |ui| {
if ui
.selectable_value(&mut entry.highlight, None, "Nothing")
.changed()
{
*dirty = true;
}
for highlight in MidiModeHighlight::iter() {
if ui
.selectable_value(
&mut entry.highlight,
Some(highlight),
highlight.to_string(),
)
.changed()
{
*dirty = true;
}
}
});
let response = ui.add_enabled(uses == 0, egui::Button::new(icons::TRASH));
if uses > 0 {
response.on_disabled_hover_text(format!("{uses} bindings use this mode"));
} else if response.clicked() {
remove = Some(mode);
}
ui.end_row();
}
});
if let Some(mode) = remove
&& mapping.remove_unused_mode(mode)
{
filter.mode = None;
*dirty = true;
}
ui.horizontal(|ui| {
ui.label("Show bindings of");
ComboBox::new(format!("{id}_mode_filter"), "")
.selected_text(mode_label(filter.mode, &mapping.modes))
.show_ui(ui, |ui| {
ui.selectable_value(&mut filter.mode, None, "All Modes");
for (index, mode) in mapping.modes.iter().enumerate() {
if let Ok(index) = u8::try_from(index) {
ui.selectable_value(&mut filter.mode, Some(index), &mode.name);
}
}
});
});
}
pub(super) fn binding_mode_editor(
ui: &mut Ui,
binding_mode: &mut Option<u8>,
modes: &[MidiMode],
dirty: &mut bool,
id: String,
) {
if modes.is_empty() {
return;
}
ui.horizontal(|ui| {
ui.label("Works in");
ComboBox::new(id, "")
.selected_text(mode_label(*binding_mode, modes))
.show_ui(ui, |ui| {
if ui
.selectable_value(binding_mode, None, "All Modes")
.changed()
{
*dirty = true;
}
for (index, mode) in modes.iter().enumerate() {
if let Ok(index) = u8::try_from(index)
&& ui
.selectable_value(binding_mode, Some(index), &mode.name)
.changed()
{
*dirty = true;
}
}
});
});
}
pub(super) fn mode_picker(
ui: &mut Ui,
mode: &mut u8,
modes: &[MidiMode],
dirty: &mut bool,
id: String,
) {
ui.horizontal(|ui| {
ui.label("Mode");
if modes.is_empty() {
if ui.add(DragValue::new(mode).range(0..=15)).changed() {
*dirty = true;
}
ui.small("Add modes above to pick them by name");
return;
}
ComboBox::new(id, "")
.selected_text(mode_label(Some(*mode), modes))
.show_ui(ui, |ui| {
for (index, entry) in modes.iter().enumerate() {
if let Ok(index) = u8::try_from(index)
&& ui.selectable_value(mode, index, &entry.name).changed()
{
*dirty = true;
}
}
});
});
}
pub(super) fn default_ports_section(
ui: &mut Ui,
ports: &mut Vec<String>,
known_ports: &[String],
dirty: &mut bool,
id: &str,
) {
ui.horizontal_wrapped(|ui| {
ui.label(iconized(ui, icons::PLUGS_CONNECTED, " Default for"))
.on_hover_text(
"These devices get this controller as soon as they are connected, \
unless the project assigns them another one or None",
);
let mut remove = None;
for (index, port) in ports.iter().enumerate() {
ui.label(port);
if ui.small_button(icons::X).clicked() {
remove = Some(index);
}
}
if let Some(index) = remove {
ports.remove(index);
*dirty = true;
}
let addable: BTreeSet<String> = known_ports
.iter()
.map(|port| normalize_controller_key(port))
.filter(|port| !ports.contains(port))
.collect();
if ports.is_empty() && addable.is_empty() {
ui.small("no device yet; connect one to add it");
}
if !addable.is_empty() {
ComboBox::new(format!("{id}_default_port"), "")
.selected_text("Add Deviceā¦")
.show_ui(ui, |ui| {
for port in addable {
if ui.selectable_label(false, &port).clicked() {
ports.push(port);
*dirty = true;
}
}
});
}
});
}
fn mode_label(mode: Option<u8>, modes: &[MidiMode]) -> String {
match mode {
None => "All Modes".to_owned(),
Some(mode) => modes
.get(usize::from(mode))
.map_or_else(|| format!("Mode {}", mode + 1), |mode| mode.name.clone()),
}
}