use crate::*; impl InputToCommand for TransportCommand { fn input_to_command (state: &T, input: &TuiInput) -> Option { to_transport_command(state, input) .or_else(||to_focus_command(input).map(TransportCommand::Focus)) } } impl InputToCommand for SequencerCommand { fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option { to_sequencer_command(state, input) .or_else(||to_focus_command(input).map(SequencerCommand::Focus)) } } impl InputToCommand for ArrangerCommand { fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option { to_arranger_command(state, input) .or_else(||to_focus_command(input).map(ArrangerCommand::Focus)) } } fn to_focus_command (input: &TuiInput) -> Option { use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc}; Some(match input.event() { key!(Tab) => FocusCommand::Next, key!(Shift-Tab) => FocusCommand::Prev, key!(BackTab) => FocusCommand::Prev, key!(Shift-BackTab) => FocusCommand::Prev, key!(Up) => FocusCommand::Up, key!(Down) => FocusCommand::Down, key!(Left) => FocusCommand::Left, key!(Right) => FocusCommand::Right, key!(Enter) => FocusCommand::Enter, key!(Esc) => FocusCommand::Exit, _ => return None }) } fn to_transport_command (state: &T, input: &TuiInput) -> Option where T: TransportControl { use ClockCommand::{SetBpm, SetQuant, SetSync}; use TransportCommand::{Focus, Clock}; use KeyCode::{Enter, Left, Right, Char}; Some(match input.event() { key!(Left) => Focus(FocusCommand::Prev), key!(Right) => Focus(FocusCommand::Next), key!(Char(' ')) => todo!("toolbar space"), key!(Shift-Char(' ')) => todo!("toolbar shift-space"), _ => match state.transport_focused().unwrap() { TransportFocus::Bpm => match input.event() { key!(Char(',')) => Clock(SetBpm(state.bpm().get() - 1.0)), key!(Char('.')) => Clock(SetBpm(state.bpm().get() + 1.0)), key!(Char('<')) => Clock(SetBpm(state.bpm().get() - 0.001)), key!(Char('>')) => Clock(SetBpm(state.bpm().get() + 0.001)), _ => return None, }, TransportFocus::Quant => match input.event() { key!(Char(',')) => Clock(SetQuant(state.prev_quant())), key!(Char('.')) => Clock(SetQuant(state.next_quant())), key!(Char('<')) => Clock(SetQuant(state.prev_quant())), key!(Char('>')) => Clock(SetQuant(state.next_quant())), _ => return None, }, TransportFocus::Sync => match input.event() { key!(Char(',')) => Clock(SetSync(state.prev_sync())), key!(Char('.')) => Clock(SetSync(state.next_sync())), key!(Char('<')) => Clock(SetSync(state.prev_sync())), key!(Char('>')) => Clock(SetSync(state.next_sync())), _ => return None, }, TransportFocus::Clock => match input.event() { key!(Char(',')) => todo!("transport seek bar"), key!(Char('.')) => todo!("transport seek bar"), key!(Char('<')) => todo!("transport seek beat"), key!(Char('>')) => todo!("transport seek beat"), _ => return None, }, TransportFocus::PlayPause => match input.event() { key!(Enter) => todo!("transport play toggle"), key!(Shift-Enter) => todo!("transport shift-play toggle"), _ => return None, }, } }) } fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option { use SequencerCommand as Cmd; if !state.entered() { return None } Some(match state.focused() { AppFocus::Menu => { todo!() }, AppFocus::Content(SequencerFocus::Transport(_)) => { match TransportCommand::input_to_command(state, input)? { TransportCommand::Clock(_) => { todo!() }, TransportCommand::Focus(command) => Cmd::Focus(command), } }, AppFocus::Content(SequencerFocus::Phrases) => { Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?) }, AppFocus::Content(SequencerFocus::PhraseEditor) => { Cmd::Editor(PhraseCommand::input_to_command(state, input)?) }, }) } fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option { use ArrangerCommand as Cmd; if !state.entered() { return None } Some(match state.focused() { AppFocus::Menu => todo!(), AppFocus::Content(ArrangerFocus::Transport(_)) => { use TransportCommand::{Clock, Focus}; match TransportCommand::input_to_command(state, input)? { Clock(_) => { todo!() }, Focus(command) => Cmd::Focus(command) } }, AppFocus::Content(ArrangerFocus::PhraseEditor) => { Cmd::Editor(PhraseCommand::input_to_command(state, input)?) }, AppFocus::Content(ArrangerFocus::Phrases) => match input.event() { key!(KeyCode::Char('e')) => { Cmd::EditPhrase(state.phrase_editing().clone()) }, _ => { Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?) } }, AppFocus::Content(ArrangerFocus::Arranger) => { use ArrangerSelection as Select; use ArrangerTrackCommand as Track; use ArrangerClipCommand as Clip; use ArrangerSceneCommand as Scene; use KeyCode::{Char, Up, Down, Left, Right, Enter, Delete}; match input.event() { key!(Char('e')) => Cmd::EditPhrase(state.phrase_editing().clone()), key!(Char('l')) => Cmd::Clip(Clip::SetLoop(false)), key!(Char('+')) => Cmd::Zoom(0), // TODO key!(Char('=')) => Cmd::Zoom(0), // TODO key!(Char('_')) => Cmd::Zoom(0), // TODO key!(Char('-')) => Cmd::Zoom(0), // TODO key!(Char('`')) => { todo!("toggle state mode") }, key!(Ctrl-Char('a')) => Cmd::Scene(Scene::Add), key!(Ctrl-Char('t')) => Cmd::Track(Track::Add), _ => match state.selected() { Select::Mix => match input.event() { key!(Down) => Cmd::Select(Select::Scene(0)), key!(Right) => Cmd::Select(Select::Track(0)), key!(Char(',')) => Cmd::Zoom(0), key!(Char('.')) => Cmd::Zoom(0), key!(Char('<')) => Cmd::Zoom(0), key!(Char('>')) => Cmd::Zoom(0), key!(Delete) => Cmd::Clear, key!(Char('c')) => Cmd::Color(ItemColor::random()), _ => return None }, Select::Track(t) => match input.event() { key!(Down) => Cmd::Select(Select::Clip(t, 0)), key!(Left) => Cmd::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }), key!(Right) => Cmd::Select(Select::Track(t + 1)), key!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)), key!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)), key!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)), key!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)), key!(Delete) => Cmd::Track(Track::Delete(t)), //key!(Char('c')) => Cmd::Track(Track::Color(t, ItemColor::random())), _ => return None }, Select::Scene(s) => match input.event() { key!(Up) => Cmd::Select(if s > 0 { Select::Scene(s - 1) } else { Select::Mix }), key!(Down) => Cmd::Select(Select::Scene(s + 1)), key!(Right) => Cmd::Select(Select::Clip(0, s)), key!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)), key!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)), key!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)), key!(Char('>')) => Cmd::Scene(Scene::Swap(s, s + 1)), key!(Enter) => Cmd::Scene(Scene::Play(s)), key!(Delete) => Cmd::Scene(Scene::Delete(s)), //key!(Char('c')) => Cmd::Track(Scene::Color(s, ItemColor::random())), _ => return None }, Select::Clip(t, s) => match input.event() { key!(Up) => Cmd::Select(if s > 0 { Select::Clip(t, s - 1) } else { Select::Track(t) }), key!(Down) => Cmd::Select(Select::Clip(t, s + 1)), key!(Left) => Cmd::Select(if t > 0 { Select::Clip(t - 1, s) } else { Select::Scene(s) }), key!(Right) => Cmd::Select(Select::Clip(t + 1, s)), key!(Char(',')) => Cmd::Clip(Clip::Set(t, s, None)), key!(Char('.')) => Cmd::Clip(Clip::Set(t, s, None)), key!(Char('<')) => Cmd::Clip(Clip::Set(t, s, None)), key!(Char('>')) => Cmd::Clip(Clip::Set(t, s, None)), key!(Delete) => Cmd::Clip(Clip::Set(t, s, None)), //key!(Char('c')) => Cmd::Clip(Clip::Color(t, s, ItemColor::random())), //key!(Char('g')) => Cmd::Clip(Clip(Clip::Get(t, s))), //key!(Char('s')) => Cmd::Clip(Clip(Clip::Set(t, s))), _ => return None }, } } } }) } impl InputToCommand for PhrasesCommand { fn input_to_command (state: &T, input: &TuiInput) -> Option { use PhrasePoolCommand as Phrase; use PhraseRenameCommand as Rename; use PhraseLengthCommand as Length; use KeyCode::{Up, Down, Delete, Char}; let index = state.phrase_index(); let count = state.phrases().len(); Some(match input.event() { key!(Up) => Self::Select(0), key!(Down) => Self::Select(0), key!(Char(',')) => if index > 1 { state.set_phrase_index(state.phrase_index().saturating_sub(1)); Self::Phrase(Phrase::Swap(index - 1, index)) } else { return None }, key!(Char('.')) => if index < count.saturating_sub(1) { state.set_phrase_index(state.phrase_index() + 1); Self::Phrase(Phrase::Swap(index + 1, index)) } else { return None }, key!(Delete) => if index > 0 { state.set_phrase_index(index.min(count.saturating_sub(1))); Self::Phrase(Phrase::Delete(index)) } else { return None }, key!(Char('a')) => Self::Phrase(Phrase::Add(count)), key!(Char('i')) => Self::Phrase(Phrase::Add(index + 1)), key!(Char('d')) => Self::Phrase(Phrase::Duplicate(index)), key!(Char('c')) => Self::Phrase(Phrase::Color(index, ItemColor::random())), key!(Char('n')) => Self::Rename(Rename::Begin), key!(Char('t')) => Self::Length(Length::Begin), _ => match state.phrases_mode() { Some(PhrasesMode::Rename(..)) => { Self::Rename(Rename::input_to_command(state, input)?) }, Some(PhrasesMode::Length(..)) => { Self::Length(Length::input_to_command(state, input)?) }, _ => return None } }) } } impl InputToCommand for PhraseLengthCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { use KeyCode::{Up, Down, Right, Left, Enter, Esc}; if let Some(PhrasesMode::Length(_, length, _)) = state.phrases_mode() { Some(match from.event() { key!(Up) => Self::Inc, key!(Down) => Self::Dec, key!(Right) => Self::Next, key!(Left) => Self::Prev, key!(Enter) => Self::Set(*length), key!(Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl InputToCommand for PhraseRenameCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { use KeyCode::{Char, Backspace, Enter, Esc}; if let Some(PhrasesMode::Rename(_, ref old_name)) = state.phrases_mode() { Some(match from.event() { key!(Char(c)) => { let mut new_name = old_name.clone(); new_name.push(*c); Self::Set(new_name) }, key!(Backspace) => { let mut new_name = old_name.clone(); new_name.pop(); Self::Set(new_name) }, key!(Enter) => Self::Confirm, key!(Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl InputToCommand for PhraseCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { use PhraseCommand::*; use KeyCode::{Char, Enter, Esc, Up, Down, PageUp, PageDown, Left, Right}; Some(match from.event() { key!(Char('`')) => ToggleDirection, key!(Enter) => EnterEditMode, key!(Esc) => ExitEditMode, key!(Char('a')) => NoteAppend, key!(Char('s')) => NoteSet, key!(Char('[')) => NoteLengthSet(prev_note_length(state.note_len())), key!(Char(']')) => NoteLengthSet(next_note_length(state.note_len())), key!(Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), key!(Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), key!(Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), key!(Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), key!(Up) => match state.phrase_editor_entered() { true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)), false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)), }, key!(Down) => match state.phrase_editor_entered() { true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)), false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)), }, key!(PageUp) => match state.phrase_editor_entered() { true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)), false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)), }, key!(PageDown) => match state.phrase_editor_entered() { true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)), false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(3)), }, key!(Left) => match state.phrase_editor_entered() { true => TimeCursorSet(state.note_axis().write().unwrap().point_minus(1)), false => TimeScrollSet(state.note_axis().write().unwrap().start_minus(1)), }, key!(Right) => match state.phrase_editor_entered() { true => TimeCursorSet(state.note_axis().write().unwrap().point_plus(1)), false => TimeScrollSet(state.note_axis().write().unwrap().start_plus(1)), }, _ => return None }) } }