fcb_looper/mapper/src/input_form.rs
2025-08-05 12:03:26 +02:00

318 lines
12 KiB
Rust

use crate::data::StructuredNote;
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame,
};
use std::collections::HashMap;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum InputField {
Switch1,
Switch2,
Switches,
Select,
Number,
Value1,
Value2,
DirectSelect,
MidiFunction,
MidiChan,
Config,
ExpressionPedalA,
ExpressionPedalB,
Display1,
Display2,
Display3,
ButtonLeds,
ButtonPresses,
}
impl InputField {
pub fn next(&self) -> Self {
match self {
InputField::Switch1 => InputField::Switch2,
InputField::Switch2 => InputField::Switches,
InputField::Switches => InputField::Select,
InputField::Select => InputField::Number,
InputField::Number => InputField::Value1,
InputField::Value1 => InputField::Value2,
InputField::Value2 => InputField::DirectSelect,
InputField::DirectSelect => InputField::MidiFunction,
InputField::MidiFunction => InputField::MidiChan,
InputField::MidiChan => InputField::Config,
InputField::Config => InputField::ExpressionPedalA,
InputField::ExpressionPedalA => InputField::ExpressionPedalB,
InputField::ExpressionPedalB => InputField::Display1,
InputField::Display1 => InputField::Display2,
InputField::Display2 => InputField::Display3,
InputField::Display3 => InputField::ButtonLeds,
InputField::ButtonLeds => InputField::ButtonPresses,
InputField::ButtonPresses => InputField::Switch1, // Wrap around
}
}
pub fn prev(&self) -> Self {
match self {
InputField::Switch1 => InputField::ButtonPresses,
InputField::Switch2 => InputField::Switch1,
InputField::Switches => InputField::Switch2,
InputField::Select => InputField::Switches,
InputField::Number => InputField::Select,
InputField::Value1 => InputField::Number,
InputField::Value2 => InputField::Value1,
InputField::DirectSelect => InputField::Value2,
InputField::MidiFunction => InputField::DirectSelect,
InputField::MidiChan => InputField::MidiFunction,
InputField::Config => InputField::MidiChan,
InputField::ExpressionPedalA => InputField::Config,
InputField::ExpressionPedalB => InputField::ExpressionPedalA,
InputField::Display1 => InputField::ExpressionPedalB,
InputField::Display2 => InputField::Display1,
InputField::Display3 => InputField::Display2,
InputField::ButtonLeds => InputField::Display3,
InputField::ButtonPresses => InputField::ButtonLeds,
}
}
pub fn name(&self) -> &str {
match self {
InputField::Switch1 => "Switch 1",
InputField::Switch2 => "Switch 2",
InputField::Switches => "Switches",
InputField::Select => "Select",
InputField::Number => "Number",
InputField::Value1 => "Value 1",
InputField::Value2 => "Value 2",
InputField::DirectSelect => "Direct Select",
InputField::MidiFunction => "MIDI Function",
InputField::MidiChan => "MIDI Chan",
InputField::Config => "Config",
InputField::ExpressionPedalA => "Expression Pedal A",
InputField::ExpressionPedalB => "Expression Pedal B",
InputField::Display1 => "Display 1",
InputField::Display2 => "Display 2",
InputField::Display3 => "Display 3",
InputField::ButtonLeds => "Button LEDs",
InputField::ButtonPresses => "Button Presses",
}
}
pub fn is_led_field(&self) -> bool {
matches!(
self,
InputField::Switch1
| InputField::Switch2
| InputField::Switches
| InputField::Select
| InputField::Number
| InputField::Value1
| InputField::Value2
| InputField::DirectSelect
| InputField::MidiFunction
| InputField::MidiChan
| InputField::Config
| InputField::ExpressionPedalA
| InputField::ExpressionPedalB
)
}
pub fn is_text_field(&self) -> bool {
matches!(
self,
InputField::Display1
| InputField::Display2
| InputField::Display3
| InputField::ButtonLeds
| InputField::ButtonPresses
)
}
}
pub struct StructuredInputForm {
pub current_note: StructuredNote,
pub current_field: InputField,
pub cursor_position: usize,
}
impl StructuredInputForm {
pub fn new(register_values: HashMap<String, u8>) -> Self {
let mut current_note = StructuredNote::default();
current_note.register_values = register_values;
Self {
current_note,
current_field: InputField::Switch1,
cursor_position: 0,
}
}
pub fn reset(&mut self, register_values: HashMap<String, u8>) {
self.current_note = StructuredNote::default();
self.current_note.register_values = register_values;
self.current_field = InputField::Switch1;
self.cursor_position = 0;
}
pub fn get_led_value(&self, field: &InputField) -> Option<u8> {
match field {
InputField::Switch1 => self.current_note.switch_1,
InputField::Switch2 => self.current_note.switch_2,
InputField::Switches => self.current_note.switches,
InputField::Select => self.current_note.select,
InputField::Number => self.current_note.number,
InputField::Value1 => self.current_note.value_1,
InputField::Value2 => self.current_note.value_2,
InputField::DirectSelect => self.current_note.direct_select,
InputField::MidiFunction => self.current_note.midi_function,
InputField::MidiChan => self.current_note.midi_chan,
InputField::Config => self.current_note.config,
InputField::ExpressionPedalA => self.current_note.expression_pedal_a,
InputField::ExpressionPedalB => self.current_note.expression_pedal_b,
_ => None,
}
}
pub fn get_text_value(&self, field: &InputField) -> &str {
match field {
InputField::Display1 => &self.current_note.display_1,
InputField::Display2 => &self.current_note.display_2,
InputField::Display3 => &self.current_note.display_3,
InputField::ButtonLeds => &self.current_note.button_leds,
InputField::ButtonPresses => &self.current_note.button_presses,
_ => "",
}
}
pub fn set_led_value(&mut self, field: &InputField, value: Option<u8>) {
match field {
InputField::Switch1 => self.current_note.switch_1 = value,
InputField::Switch2 => self.current_note.switch_2 = value,
InputField::Switches => self.current_note.switches = value,
InputField::Select => self.current_note.select = value,
InputField::Number => self.current_note.number = value,
InputField::Value1 => self.current_note.value_1 = value,
InputField::Value2 => self.current_note.value_2 = value,
InputField::DirectSelect => self.current_note.direct_select = value,
InputField::MidiFunction => self.current_note.midi_function = value,
InputField::MidiChan => self.current_note.midi_chan = value,
InputField::Config => self.current_note.config = value,
InputField::ExpressionPedalA => self.current_note.expression_pedal_a = value,
InputField::ExpressionPedalB => self.current_note.expression_pedal_b = value,
_ => {}
}
}
pub fn get_text_value_mut(&mut self, field: &InputField) -> Option<&mut String> {
match field {
InputField::Display1 => Some(&mut self.current_note.display_1),
InputField::Display2 => Some(&mut self.current_note.display_2),
InputField::Display3 => Some(&mut self.current_note.display_3),
InputField::ButtonLeds => Some(&mut self.current_note.button_leds),
InputField::ButtonPresses => Some(&mut self.current_note.button_presses),
_ => None,
}
}
pub fn finalize_note(&mut self) -> StructuredNote {
self.current_note.description = self.current_note.generate_description();
self.current_note.clone()
}
}
pub fn draw_structured_input_form(f: &mut Frame, form: &StructuredInputForm, area: Rect) {
let mut lines = Vec::new();
// Helper function to create field line
let create_field_line = |field: &InputField, form: &StructuredInputForm| -> Line {
let is_current = form.current_field == *field;
let prefix = if is_current { "" } else { " " };
let style = if is_current {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
if field.is_led_field() {
let value = form.get_led_value(field);
let value_str = match value {
Some(1) => "1",
Some(0) => "0",
_ => "",
};
Line::from(vec![Span::styled(
format!("{}{}: {}", prefix, field.name().to_lowercase(), value_str),
style,
)])
} else {
let text = form.get_text_value(field);
let display_text = if is_current {
let mut display = text.to_string();
if form.cursor_position <= display.len() {
display.insert(form.cursor_position, '│');
}
display
} else if text.is_empty() {
"".to_string()
} else {
text.to_string()
};
Line::from(vec![Span::styled(
format!(
"{}{} (list of active segments): {}",
prefix,
field.name().to_lowercase(),
display_text
),
style,
)])
}
};
// Switch controls
lines.push(create_field_line(&InputField::Switch1, form));
lines.push(create_field_line(&InputField::Switch2, form));
lines.push(Line::from(""));
// Main LED controls
lines.push(create_field_line(&InputField::Switches, form));
lines.push(create_field_line(&InputField::Select, form));
lines.push(create_field_line(&InputField::Number, form));
lines.push(create_field_line(&InputField::Value1, form));
lines.push(create_field_line(&InputField::Value2, form));
lines.push(Line::from(""));
// Additional controls
lines.push(create_field_line(&InputField::DirectSelect, form));
lines.push(create_field_line(&InputField::MidiFunction, form));
lines.push(create_field_line(&InputField::MidiChan, form));
lines.push(create_field_line(&InputField::Config, form));
lines.push(Line::from(""));
// Expression pedals
lines.push(create_field_line(&InputField::ExpressionPedalA, form));
lines.push(create_field_line(&InputField::ExpressionPedalB, form));
lines.push(Line::from(""));
// Display segments
lines.push(create_field_line(&InputField::Display1, form));
lines.push(create_field_line(&InputField::Display2, form));
lines.push(create_field_line(&InputField::Display3, form));
lines.push(Line::from(""));
// Button controls
lines.push(create_field_line(&InputField::ButtonLeds, form));
lines.push(create_field_line(&InputField::ButtonPresses, form));
let block = Paragraph::new(lines).block(
Block::default()
.borders(Borders::ALL)
.title("Structured Note Input"),
);
f.render_widget(block, area);
}