gled_model/asset/midi_controller/
mode.rs1use serde::{Deserialize, Serialize};
6use strum::{Display, EnumIter};
7
8use super::{MidiControllerMapping, MidiInputAction, MidiValueSource};
9
10#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
11pub struct MidiMode {
12 pub name: String,
13 #[serde(default)]
16 pub highlight: Option<MidiModeHighlight>,
17}
18
19#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Display, EnumIter)]
22pub enum MidiModeHighlight {
23 #[strum(serialize = "Scene Grid")]
24 SceneGrid,
25 #[strum(serialize = "Scene Settings")]
26 SceneSettings,
27}
28
29pub fn binding_in_mode(binding_mode: Option<u8>, active: u8) -> bool {
31 binding_mode.is_none_or(|mode| mode == active)
32}
33
34impl MidiControllerMapping {
35 pub fn mode_uses(&self, mode: u8) -> usize {
37 let inputs = self.input_bindings.iter().filter(|binding| {
38 binding.mode == Some(mode)
39 || matches!(binding.action, MidiInputAction::SelectMode { mode: selected } if selected == mode)
40 });
41 let outputs = self.output_bindings.iter().filter(|binding| {
42 binding.mode == Some(mode)
43 || binding
44 .kind
45 .source()
46 .is_some_and(|source| matches!(source, MidiValueSource::ModeActive { mode: shown } if *shown == mode))
47 });
48 inputs.count() + outputs.count()
49 }
50
51 pub fn remove_unused_mode(&mut self, mode: u8) -> bool {
54 if usize::from(mode) >= self.modes.len() || self.mode_uses(mode) > 0 {
55 return false;
56 }
57 self.modes.remove(usize::from(mode));
58 let shift = |index: &mut u8| {
59 if *index > mode {
60 *index -= 1;
61 }
62 };
63 for binding in &mut self.input_bindings {
64 if let Some(index) = binding.mode.as_mut() {
65 shift(index);
66 }
67 if let MidiInputAction::SelectMode { mode: index } = &mut binding.action {
68 shift(index);
69 }
70 }
71 for binding in &mut self.output_bindings {
72 if let Some(index) = binding.mode.as_mut() {
73 shift(index);
74 }
75 if let Some(MidiValueSource::ModeActive { mode: index }) = binding.kind.source_mut() {
76 shift(index);
77 }
78 }
79 true
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::asset::midi_controller::{
87 MidiInputBinding, MidiOutputBinding, MidiOutputBindingKind, MidiValueOutput,
88 };
89
90 fn mapping() -> MidiControllerMapping {
91 MidiControllerMapping {
92 modes: ["Rows", "Spare", "Animations"]
93 .map(|name| MidiMode {
94 name: name.to_owned(),
95 highlight: None,
96 })
97 .to_vec(),
98 input_bindings: vec![
99 MidiInputBinding {
100 mode: Some(2),
101 ..Default::default()
102 },
103 MidiInputBinding {
104 action: MidiInputAction::SelectMode { mode: 2 },
105 ..Default::default()
106 },
107 ],
108 output_bindings: vec![MidiOutputBinding {
109 kind: MidiOutputBindingKind::Value(MidiValueOutput {
110 source: MidiValueSource::ModeActive { mode: 2 },
111 ..Default::default()
112 }),
113 ..Default::default()
114 }],
115 ..Default::default()
116 }
117 }
118
119 #[test]
120 fn a_binding_without_a_mode_works_in_every_mode() {
121 assert!(binding_in_mode(None, 3));
122 assert!(binding_in_mode(Some(1), 1));
123 assert!(!binding_in_mode(Some(1), 0));
124 }
125
126 #[test]
127 fn removing_a_mode_moves_the_later_ones_down() {
128 let mut mapping = mapping();
129 assert!(mapping.remove_unused_mode(1));
130 let names: Vec<&str> = mapping
131 .modes
132 .iter()
133 .map(|mode| mode.name.as_str())
134 .collect();
135 assert_eq!(names, ["Rows", "Animations"]);
136 assert_eq!(mapping.input_bindings[0].mode, Some(1));
137 assert_eq!(
138 mapping.input_bindings[1].action,
139 MidiInputAction::SelectMode { mode: 1 }
140 );
141 assert_eq!(
142 mapping.output_bindings[0].kind.source(),
143 Some(&MidiValueSource::ModeActive { mode: 1 })
144 );
145 }
146
147 #[test]
148 fn a_mode_in_use_stays() {
149 let mut mapping = mapping();
150 assert_eq!(mapping.mode_uses(2), 3);
151 assert!(!mapping.remove_unused_mode(2));
152 assert_eq!(mapping.modes.len(), 3);
153 }
154}