use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter};
use super::{MidiControllerMapping, MidiInputAction, MidiValueSource};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
pub struct MidiMode {
pub name: String,
#[serde(default)]
pub highlight: Option<MidiModeHighlight>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Display, EnumIter)]
pub enum MidiModeHighlight {
#[strum(serialize = "Scene Grid")]
SceneGrid,
#[strum(serialize = "Scene Settings")]
SceneSettings,
}
pub fn binding_in_mode(binding_mode: Option<u8>, active: u8) -> bool {
binding_mode.is_none_or(|mode| mode == active)
}
impl MidiControllerMapping {
pub fn mode_uses(&self, mode: u8) -> usize {
let inputs = self.input_bindings.iter().filter(|binding| {
binding.mode == Some(mode)
|| matches!(binding.action, MidiInputAction::SelectMode { mode: selected } if selected == mode)
});
let outputs = self.output_bindings.iter().filter(|binding| {
binding.mode == Some(mode)
|| binding
.kind
.source()
.is_some_and(|source| matches!(source, MidiValueSource::ModeActive { mode: shown } if *shown == mode))
});
inputs.count() + outputs.count()
}
pub fn remove_unused_mode(&mut self, mode: u8) -> bool {
if usize::from(mode) >= self.modes.len() || self.mode_uses(mode) > 0 {
return false;
}
self.modes.remove(usize::from(mode));
let shift = |index: &mut u8| {
if *index > mode {
*index -= 1;
}
};
for binding in &mut self.input_bindings {
if let Some(index) = binding.mode.as_mut() {
shift(index);
}
if let MidiInputAction::SelectMode { mode: index } = &mut binding.action {
shift(index);
}
}
for binding in &mut self.output_bindings {
if let Some(index) = binding.mode.as_mut() {
shift(index);
}
if let Some(MidiValueSource::ModeActive { mode: index }) = binding.kind.source_mut() {
shift(index);
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::asset::midi_controller::{
MidiInputBinding, MidiOutputBinding, MidiOutputBindingKind, MidiValueOutput,
};
fn mapping() -> MidiControllerMapping {
MidiControllerMapping {
modes: ["Rows", "Spare", "Animations"]
.map(|name| MidiMode {
name: name.to_owned(),
highlight: None,
})
.to_vec(),
input_bindings: vec![
MidiInputBinding {
mode: Some(2),
..Default::default()
},
MidiInputBinding {
action: MidiInputAction::SelectMode { mode: 2 },
..Default::default()
},
],
output_bindings: vec![MidiOutputBinding {
kind: MidiOutputBindingKind::Value(MidiValueOutput {
source: MidiValueSource::ModeActive { mode: 2 },
..Default::default()
}),
..Default::default()
}],
..Default::default()
}
}
#[test]
fn a_binding_without_a_mode_works_in_every_mode() {
assert!(binding_in_mode(None, 3));
assert!(binding_in_mode(Some(1), 1));
assert!(!binding_in_mode(Some(1), 0));
}
#[test]
fn removing_a_mode_moves_the_later_ones_down() {
let mut mapping = mapping();
assert!(mapping.remove_unused_mode(1));
let names: Vec<&str> = mapping
.modes
.iter()
.map(|mode| mode.name.as_str())
.collect();
assert_eq!(names, ["Rows", "Animations"]);
assert_eq!(mapping.input_bindings[0].mode, Some(1));
assert_eq!(
mapping.input_bindings[1].action,
MidiInputAction::SelectMode { mode: 1 }
);
assert_eq!(
mapping.output_bindings[0].kind.source(),
Some(&MidiValueSource::ModeActive { mode: 1 })
);
}
#[test]
fn a_mode_in_use_stays() {
let mut mapping = mapping();
assert_eq!(mapping.mode_uses(2), 3);
assert!(!mapping.remove_unused_mode(2));
assert_eq!(mapping.modes.len(), 3);
}
}