714 lines
24 KiB
Rust
714 lines
24 KiB
Rust
use crate::{
|
|
app::{App, InputMode, SelectedRegister},
|
|
input_form::draw_structured_input_form,
|
|
};
|
|
use ratatui::{
|
|
backend::Backend,
|
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
|
style::{Color, Modifier, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap},
|
|
Frame, Terminal,
|
|
};
|
|
use std::error::Error;
|
|
|
|
pub fn ui(f: &mut Frame, app: &mut App) {
|
|
if app.show_help {
|
|
draw_help(f);
|
|
return;
|
|
}
|
|
|
|
draw_structured_ui(f, app);
|
|
|
|
// Input popup if needed
|
|
if matches!(
|
|
app.input_mode,
|
|
InputMode::EditValue | InputMode::SaveFile | InputMode::LoadFile
|
|
) {
|
|
draw_input_popup(f, app);
|
|
}
|
|
}
|
|
|
|
fn draw_structured_ui(f: &mut Frame, app: &mut App) {
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(3),
|
|
Constraint::Length(7),
|
|
Constraint::Min(5),
|
|
Constraint::Length(3),
|
|
])
|
|
.split(f.size());
|
|
|
|
// Title
|
|
let title = Paragraph::new("FCB1010 Structured Note Mapper")
|
|
.style(
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)
|
|
.alignment(Alignment::Center)
|
|
.block(Block::default().borders(Borders::ALL));
|
|
f.render_widget(title, chunks[0]);
|
|
|
|
// Register values
|
|
draw_register_values(f, app, chunks[1]);
|
|
|
|
// Main content area
|
|
if app.input_mode == InputMode::StructuredInput {
|
|
draw_structured_input_form(f, &app.structured_form, chunks[2]);
|
|
} else {
|
|
draw_structured_notes_list(f, app, chunks[2]);
|
|
}
|
|
|
|
// Status bar
|
|
let status_text = if app.input_mode == InputMode::StructuredInput {
|
|
"Enter: Save note | Esc: Cancel | Space/↑↓: Navigate fields | 0/1: LED state | Letters: Text input"
|
|
} else {
|
|
"←→: Select register | 0-7: Toggle bit | v: Edit value | r: Reset | f: Fill | n: New note | ↑↓: Navigate | Del: Delete | s: Save | l: Load | h: Help | q: Quit"
|
|
};
|
|
|
|
let status = Paragraph::new(status_text)
|
|
.style(Style::default().fg(Color::White))
|
|
.block(Block::default().borders(Borders::ALL));
|
|
f.render_widget(status, chunks[3]);
|
|
}
|
|
|
|
fn draw_structured_notes_list(f: &mut Frame, app: &mut App, area: Rect) {
|
|
let items: Vec<ListItem> = app
|
|
.structured_notes
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, note)| {
|
|
let mut content = String::new();
|
|
|
|
// Add register values in binary format first
|
|
let ic03 = note.register_values.get("IC03").unwrap_or(&0);
|
|
let ic10 = note.register_values.get("IC10").unwrap_or(&0);
|
|
let ic11 = note.register_values.get("IC11").unwrap_or(&0);
|
|
content.push_str(&format!(
|
|
"[IC03:{:08b}, IC10:{:08b}, IC11:{:08b}]",
|
|
ic03, ic10, ic11
|
|
));
|
|
|
|
// Add dash separator and note description
|
|
content.push_str(&format!(" - {}. {}", i + 1, note.description));
|
|
|
|
ListItem::new(content)
|
|
})
|
|
.collect();
|
|
|
|
let mut notes_list_state = app.notes_list_state.clone();
|
|
let notes_list = List::new(items)
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.title("Structured Notes (Enter to restore register values, Del to remove)"),
|
|
)
|
|
.highlight_style(
|
|
Style::default()
|
|
.fg(Color::Yellow)
|
|
.add_modifier(Modifier::BOLD),
|
|
)
|
|
.highlight_symbol("→ ");
|
|
|
|
f.render_stateful_widget(notes_list, area, &mut notes_list_state);
|
|
}
|
|
|
|
fn draw_register_values(f: &mut Frame, app: &App, area: Rect) {
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage(33),
|
|
Constraint::Percentage(33),
|
|
Constraint::Percentage(34),
|
|
])
|
|
.split(area);
|
|
|
|
let registers = [
|
|
(&SelectedRegister::IC03, "IC03"),
|
|
(&SelectedRegister::IC10, "IC10"),
|
|
(&SelectedRegister::IC11, "IC11"),
|
|
];
|
|
|
|
for (i, (register, name)) in registers.iter().enumerate() {
|
|
let value = *app.register_values.get(*name).unwrap_or(&0);
|
|
let is_selected = app.selected_register == **register;
|
|
|
|
let mut lines = Vec::new();
|
|
lines.push(Line::from(vec![
|
|
Span::styled(
|
|
format!("{}: ", name),
|
|
Style::default().add_modifier(Modifier::BOLD),
|
|
),
|
|
Span::raw(format!("{:08b} = {}", value, value)),
|
|
]));
|
|
|
|
// Bit representation
|
|
let mut bit_spans = Vec::new();
|
|
for bit in (0..8).rev() {
|
|
let is_set = (value >> bit) & 1 == 1;
|
|
let style = if is_set {
|
|
Style::default()
|
|
.fg(Color::Green)
|
|
.add_modifier(Modifier::BOLD)
|
|
} else {
|
|
Style::default().fg(Color::DarkGray)
|
|
};
|
|
bit_spans.push(Span::styled(
|
|
format!("[{}]", if is_set { "■" } else { "□" }),
|
|
style,
|
|
));
|
|
}
|
|
lines.push(Line::from(bit_spans));
|
|
|
|
// Bit numbers
|
|
let mut bit_num_spans = Vec::new();
|
|
for bit in (0..8).rev() {
|
|
bit_num_spans.push(Span::styled(
|
|
format!(" {} ", bit),
|
|
Style::default().fg(Color::Yellow),
|
|
));
|
|
}
|
|
lines.push(Line::from(bit_num_spans));
|
|
|
|
let border_style = if is_selected {
|
|
Style::default().fg(Color::Yellow)
|
|
} else {
|
|
Style::default()
|
|
};
|
|
|
|
let block = Block::default()
|
|
.borders(Borders::ALL)
|
|
.style(border_style)
|
|
.title(if is_selected {
|
|
format!("→ {}", name)
|
|
} else {
|
|
name.to_string()
|
|
});
|
|
|
|
let paragraph = Paragraph::new(lines).block(block);
|
|
f.render_widget(paragraph, chunks[i]);
|
|
}
|
|
}
|
|
|
|
fn draw_input_popup(f: &mut Frame, app: &App) {
|
|
let area = centered_rect(60, 20, f.size());
|
|
f.render_widget(Clear, area);
|
|
|
|
let title = match app.input_mode {
|
|
InputMode::EditValue => "Edit Value",
|
|
InputMode::SaveFile => "Save File",
|
|
InputMode::LoadFile => "Load File",
|
|
_ => "Input",
|
|
};
|
|
|
|
let block = Block::default()
|
|
.title(title)
|
|
.borders(Borders::ALL)
|
|
.style(Style::default().fg(Color::Yellow));
|
|
|
|
// Create text with cursor using char-aware operations
|
|
let mut spans = Vec::new();
|
|
let chars: Vec<char> = app.input_buffer.chars().collect();
|
|
|
|
if chars.is_empty() {
|
|
spans.push(Span::styled(
|
|
"█",
|
|
Style::default()
|
|
.fg(Color::White)
|
|
.add_modifier(Modifier::REVERSED),
|
|
));
|
|
} else {
|
|
if app.cursor_position > 0 {
|
|
let before_cursor: String = chars.iter().take(app.cursor_position).collect();
|
|
spans.push(Span::raw(before_cursor));
|
|
}
|
|
|
|
if app.cursor_position < chars.len() {
|
|
let cursor_char = chars[app.cursor_position];
|
|
spans.push(Span::styled(
|
|
cursor_char.to_string(),
|
|
Style::default()
|
|
.fg(Color::White)
|
|
.add_modifier(Modifier::REVERSED),
|
|
));
|
|
|
|
if app.cursor_position + 1 < chars.len() {
|
|
let after_cursor: String = chars.iter().skip(app.cursor_position + 1).collect();
|
|
spans.push(Span::raw(after_cursor));
|
|
}
|
|
} else {
|
|
spans.push(Span::styled(
|
|
"█",
|
|
Style::default()
|
|
.fg(Color::White)
|
|
.add_modifier(Modifier::REVERSED),
|
|
));
|
|
}
|
|
}
|
|
|
|
let paragraph = Paragraph::new(Line::from(spans)).block(block);
|
|
|
|
f.render_widget(paragraph, area);
|
|
}
|
|
|
|
fn draw_help(f: &mut Frame) {
|
|
let help_text = vec![
|
|
"FCB1010 Structured Note Mapper - Help",
|
|
"",
|
|
"Register Control:",
|
|
" ←→ - Select IC03, IC10, IC11 register",
|
|
" 0-7 - Toggle bit 0-7 of selected register",
|
|
" v - Edit register value directly (0-255)",
|
|
" r - Reset register to 0",
|
|
" f - Fill register (set to 255)",
|
|
"",
|
|
"Notes Management:",
|
|
" n - Create new structured note",
|
|
" ↑↓ - Navigate notes list",
|
|
" Enter - Restore register values from selected note",
|
|
" Del - Delete selected note",
|
|
"",
|
|
"File Operations:",
|
|
" s - Save mapping to file",
|
|
" l - Load mapping from file",
|
|
"",
|
|
"In Structured Note Input:",
|
|
" 0/1 - Set LED state for current field",
|
|
" Space/↑↓ - Navigate between fields",
|
|
" Text input - For display segments and button fields",
|
|
" Enter - Save structured note",
|
|
" Esc - Cancel input",
|
|
"",
|
|
"LED Fields:",
|
|
" Switch 1, Switch 2, Switches, Select, Number,",
|
|
" Value 1, Value 2, Direct Select, MIDI Function,",
|
|
" MIDI Chan, Config, Expression Pedal A/B",
|
|
"",
|
|
"Text Fields:",
|
|
" Display 1/2/3 - Active display segments",
|
|
" Button LEDs - Active button LEDs",
|
|
" Button Presses - Buttons that react to press",
|
|
"",
|
|
"File Format:",
|
|
" Saves/loads in original format for compatibility",
|
|
" Register editor + structured notes for full control",
|
|
"",
|
|
"Other:",
|
|
" h - Toggle this help",
|
|
" q - Quit",
|
|
"",
|
|
"Press 'h' again to return to main view",
|
|
];
|
|
|
|
let help_paragraph = Paragraph::new(
|
|
help_text
|
|
.iter()
|
|
.map(|&line| Line::from(line))
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
.block(Block::default().title("Help").borders(Borders::ALL))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
f.render_widget(help_paragraph, f.size());
|
|
}
|
|
|
|
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
|
let popup_layout = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Percentage((100 - percent_y) / 2),
|
|
Constraint::Percentage(percent_y),
|
|
Constraint::Percentage((100 - percent_y) / 2),
|
|
])
|
|
.split(r);
|
|
|
|
Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([
|
|
Constraint::Percentage((100 - percent_x) / 2),
|
|
Constraint::Percentage(percent_x),
|
|
Constraint::Percentage((100 - percent_x) / 2),
|
|
])
|
|
.split(popup_layout[1])[1]
|
|
}
|
|
|
|
pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result<(), Box<dyn Error>> {
|
|
loop {
|
|
terminal.draw(|f| ui(f, &mut app))?;
|
|
|
|
if let crossterm::event::Event::Key(key) = crossterm::event::read()? {
|
|
if key.kind == crossterm::event::KeyEventKind::Press {
|
|
if handle_input(&mut app, key.code)? {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn handle_input(app: &mut App, key: crossterm::event::KeyCode) -> Result<bool, Box<dyn Error>> {
|
|
match app.input_mode {
|
|
InputMode::StructuredInput => app.handle_structured_input(key),
|
|
_ => handle_legacy_input(app, key),
|
|
}
|
|
}
|
|
|
|
fn handle_legacy_input(
|
|
app: &mut App,
|
|
key: crossterm::event::KeyCode,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
match app.input_mode {
|
|
InputMode::Normal => handle_normal_input(app, key),
|
|
InputMode::EditValue => handle_edit_value_input(app, key),
|
|
InputMode::SaveFile => handle_save_file_input(app, key),
|
|
InputMode::LoadFile => handle_load_file_input(app, key),
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
|
|
fn handle_normal_input(
|
|
app: &mut App,
|
|
key: crossterm::event::KeyCode,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
use crossterm::event::KeyCode;
|
|
|
|
match key {
|
|
KeyCode::Char('q') => {
|
|
let _ = app.command_sender.send(crate::midi::MidiCommand::Quit);
|
|
return Ok(true);
|
|
}
|
|
KeyCode::Char('h') => app.show_help = !app.show_help,
|
|
KeyCode::Char('n') => {
|
|
app.start_structured_input();
|
|
}
|
|
KeyCode::Left => {
|
|
app.selected_register = match app.selected_register {
|
|
SelectedRegister::IC03 => SelectedRegister::IC11,
|
|
SelectedRegister::IC10 => SelectedRegister::IC03,
|
|
SelectedRegister::IC11 => SelectedRegister::IC10,
|
|
};
|
|
}
|
|
KeyCode::Right => {
|
|
app.selected_register = match app.selected_register {
|
|
SelectedRegister::IC03 => SelectedRegister::IC10,
|
|
SelectedRegister::IC10 => SelectedRegister::IC11,
|
|
SelectedRegister::IC11 => SelectedRegister::IC03,
|
|
};
|
|
}
|
|
KeyCode::Char(c @ '0'..='7') => {
|
|
if let Some(bit) = c.to_digit(10) {
|
|
app.toggle_bit(bit as u8);
|
|
}
|
|
}
|
|
KeyCode::Char('r') => {
|
|
app.set_current_value(0);
|
|
app.status_message = format!("{} reset to 0", app.selected_register.as_str());
|
|
}
|
|
KeyCode::Char('f') => {
|
|
app.set_current_value(255);
|
|
app.status_message = format!("{} set to 255", app.selected_register.as_str());
|
|
}
|
|
KeyCode::Char('v') => {
|
|
app.input_mode = InputMode::EditValue;
|
|
app.input_buffer = app.get_current_value().to_string();
|
|
app.cursor_position = app.input_buffer.chars().count();
|
|
}
|
|
KeyCode::Char('s') => {
|
|
app.input_mode = InputMode::SaveFile;
|
|
app.input_buffer = "mapping.json".to_string();
|
|
app.cursor_position = app.input_buffer.chars().count();
|
|
}
|
|
KeyCode::Char('l') => {
|
|
app.input_mode = InputMode::LoadFile;
|
|
app.input_buffer = "mapping.json".to_string();
|
|
app.cursor_position = app.input_buffer.chars().count();
|
|
}
|
|
KeyCode::Up => {
|
|
let notes_len = app.structured_notes.len();
|
|
|
|
if let Some(selected) = app.notes_list_state.selected() {
|
|
if selected > 0 {
|
|
app.notes_list_state.select(Some(selected - 1));
|
|
}
|
|
} else if notes_len > 0 {
|
|
app.notes_list_state.select(Some(0));
|
|
}
|
|
}
|
|
KeyCode::Down => {
|
|
let notes_len = app.structured_notes.len();
|
|
|
|
if let Some(selected) = app.notes_list_state.selected() {
|
|
if selected < notes_len.saturating_sub(1) {
|
|
app.notes_list_state.select(Some(selected + 1));
|
|
}
|
|
} else if notes_len > 0 {
|
|
app.notes_list_state.select(Some(0));
|
|
}
|
|
}
|
|
KeyCode::Enter => {
|
|
if let Some(selected) = app.notes_list_state.selected() {
|
|
if let Some(note) = app.structured_notes.get(selected) {
|
|
// Restore register values from note
|
|
app.register_values = note.register_values.clone();
|
|
app.status_message = "Register values restored from note".to_string();
|
|
|
|
// Send MIDI updates for all registers
|
|
for (register_name, &value) in &app.register_values {
|
|
match register_name.as_str() {
|
|
"IC03" => {
|
|
let old_selected = app.selected_register;
|
|
app.selected_register = SelectedRegister::IC03;
|
|
app.send_midi_value(value);
|
|
app.selected_register = old_selected;
|
|
}
|
|
"IC10" => {
|
|
let old_selected = app.selected_register;
|
|
app.selected_register = SelectedRegister::IC10;
|
|
app.send_midi_value(value);
|
|
app.selected_register = old_selected;
|
|
}
|
|
"IC11" => {
|
|
let old_selected = app.selected_register;
|
|
app.selected_register = SelectedRegister::IC11;
|
|
app.send_midi_value(value);
|
|
app.selected_register = old_selected;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
KeyCode::Delete => {
|
|
if let Some(selected) = app.notes_list_state.selected() {
|
|
if selected < app.structured_notes.len() {
|
|
app.structured_notes.remove(selected);
|
|
if app.structured_notes.is_empty() {
|
|
app.notes_list_state.select(None);
|
|
} else if selected >= app.structured_notes.len() {
|
|
app.notes_list_state
|
|
.select(Some(app.structured_notes.len() - 1));
|
|
}
|
|
app.status_message = "Note deleted".to_string();
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(false)
|
|
}
|
|
|
|
// Implement other input handlers (edit_value, save_file, load_file)
|
|
|
|
fn handle_edit_value_input(
|
|
app: &mut App,
|
|
key: crossterm::event::KeyCode,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
use crossterm::event::KeyCode;
|
|
|
|
match key {
|
|
KeyCode::Enter => {
|
|
match app.input_buffer.parse::<u8>() {
|
|
Ok(value) => {
|
|
app.set_current_value(value);
|
|
app.status_message =
|
|
format!("{} set to {}", app.selected_register.as_str(), value);
|
|
}
|
|
Err(_) => {
|
|
app.status_message = "Invalid value (0-255)".to_string();
|
|
}
|
|
}
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Esc => {
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Left => {
|
|
if app.cursor_position > 0 {
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Right => {
|
|
let char_count = app.input_buffer.chars().count();
|
|
if app.cursor_position < char_count {
|
|
app.cursor_position += 1;
|
|
}
|
|
}
|
|
KeyCode::Home => {
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::End => {
|
|
app.cursor_position = app.input_buffer.chars().count();
|
|
}
|
|
KeyCode::Backspace => {
|
|
if app.cursor_position > 0 {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.remove(app.cursor_position - 1);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Delete => {
|
|
let char_count = app.input_buffer.chars().count();
|
|
if app.cursor_position < char_count {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.remove(app.cursor_position);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
}
|
|
}
|
|
KeyCode::Char(c) => {
|
|
if let Ok(potential_value) =
|
|
format!("{}{}", &app.input_buffer[..app.cursor_position], c).parse::<u16>()
|
|
{
|
|
if potential_value <= 255 {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.insert(app.cursor_position, c);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
app.cursor_position += 1;
|
|
}
|
|
} else if app.input_buffer.len() < 3 {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.insert(app.cursor_position, c);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
app.cursor_position += 1;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(false)
|
|
}
|
|
|
|
fn handle_save_file_input(
|
|
app: &mut App,
|
|
key: crossterm::event::KeyCode,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
use crossterm::event::KeyCode;
|
|
|
|
match key {
|
|
KeyCode::Enter => {
|
|
match app.save_mapping(&app.input_buffer) {
|
|
Ok(_) => app.status_message = format!("Saved to {}", app.input_buffer),
|
|
Err(e) => app.status_message = format!("Save failed: {}", e),
|
|
}
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Esc => {
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Left => {
|
|
if app.cursor_position > 0 {
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Right => {
|
|
let char_count = app.input_buffer.chars().count();
|
|
if app.cursor_position < char_count {
|
|
app.cursor_position += 1;
|
|
}
|
|
}
|
|
KeyCode::Home => {
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::End => {
|
|
app.cursor_position = app.input_buffer.chars().count();
|
|
}
|
|
KeyCode::Backspace => {
|
|
if app.cursor_position > 0 {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.remove(app.cursor_position - 1);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Delete => {
|
|
let char_count = app.input_buffer.chars().count();
|
|
if app.cursor_position < char_count {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.remove(app.cursor_position);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
}
|
|
}
|
|
KeyCode::Char(c) => {
|
|
let mut chars: Vec<char> = app.input_buffer.chars().collect();
|
|
chars.insert(app.cursor_position, c);
|
|
app.input_buffer = chars.into_iter().collect();
|
|
app.cursor_position += 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(false)
|
|
}
|
|
|
|
fn handle_load_file_input(
|
|
app: &mut App,
|
|
key: crossterm::event::KeyCode,
|
|
) -> Result<bool, Box<dyn Error>> {
|
|
use crossterm::event::KeyCode;
|
|
|
|
match key {
|
|
KeyCode::Enter => {
|
|
let filename = app.input_buffer.clone();
|
|
match app.load_mapping(&filename) {
|
|
Ok(_) => {
|
|
if app.status_message.is_empty() {
|
|
app.status_message = format!("Loaded from {}", filename);
|
|
}
|
|
}
|
|
Err(e) => app.status_message = format!("Load failed: {}", e),
|
|
}
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Esc => {
|
|
app.input_mode = InputMode::Normal;
|
|
app.input_buffer.clear();
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::Left => {
|
|
if app.cursor_position > 0 {
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Right => {
|
|
if app.cursor_position < app.input_buffer.len() {
|
|
app.cursor_position += 1;
|
|
}
|
|
}
|
|
KeyCode::Home => {
|
|
app.cursor_position = 0;
|
|
}
|
|
KeyCode::End => {
|
|
app.cursor_position = app.input_buffer.len();
|
|
}
|
|
KeyCode::Backspace => {
|
|
if app.cursor_position > 0 {
|
|
app.input_buffer.remove(app.cursor_position - 1);
|
|
app.cursor_position -= 1;
|
|
}
|
|
}
|
|
KeyCode::Delete => {
|
|
if app.cursor_position < app.input_buffer.len() {
|
|
app.input_buffer.remove(app.cursor_position);
|
|
}
|
|
}
|
|
KeyCode::Char(c) => {
|
|
app.input_buffer.insert(app.cursor_position, c);
|
|
app.cursor_position += 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
Ok(false)
|
|
}
|