use crate::*; impl InputToCommand for TransportCommand { fn input_to_command (state: &T, input: &TuiInput) -> Option { use KeyCode::Char; use ClockCommand::{SetBpm, SetQuant, SetSync}; use TransportFocus as Focused; use TransportCommand::{Focus, Clock}; let focused = state.transport_focused(); Some(match input.event() { key!(Left) => Focus(FocusCommand::Prev), key!(Right) => Focus(FocusCommand::Next), key!(Char('.')) => match focused { Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)), Focused::Quant => Clock(SetQuant(state.next_quant())), Focused::Sync => Clock(SetSync(state.next_sync())), Focused::PlayPause => Clock(todo!()), Focused::Clock => Clock(todo!()), _ => {todo!()} }, key!(Char(',')) => match focused { Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)), Focused::Quant => Clock(SetQuant(state.prev_quant())), Focused::Sync => Clock(SetSync(state.prev_sync())), Focused::PlayPause => Clock(todo!()), Focused::Clock => Clock(todo!()), _ => {todo!()} }, key!(Char('>')) => match focused { Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)), Focused::Quant => Clock(SetQuant(state.next_quant())), Focused::Sync => Clock(SetSync(state.next_sync())), Focused::PlayPause => Clock(todo!()), Focused::Clock => Clock(todo!()), _ => {todo!()} }, key!(Char('<')) => match focused { Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)), Focused::Quant => Clock(SetQuant(state.prev_quant())), Focused::Sync => Clock(SetSync(state.prev_sync())), Focused::PlayPause => Clock(todo!()), Focused::Clock => Clock(todo!()), _ => {todo!()} }, _ => return None }) } } impl InputToCommand for SequencerCommand where T: SequencerControl + TransportControl + PhrasesControl + PhraseEditorControl + HasFocus + FocusGrid { fn input_to_command (state: &T, input: &TuiInput) -> Option { use FocusCommand::*; use SequencerCommand::*; match input.event() { key!(KeyCode::Tab) => Some(Self::Focus(Next)), key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)), key!(KeyCode::BackTab) => Some(Self::Focus(Prev)), key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)), key!(KeyCode::Up) => Some(Self::Focus(Up)), key!(KeyCode::Down) => Some(Self::Focus(Down)), key!(KeyCode::Left) => Some(Self::Focus(Left)), key!(KeyCode::Right) => Some(Self::Focus(Right)), _ => Some(match state.focused() { SequencerFocus::Transport => { use TransportCommand::{Clock, Focus}; match TransportCommand::input_to_command(state, input)? { Clock(command) => { todo!() }, Focus(command) => { todo!() }, } }, SequencerFocus::Phrases => Phrases(PhrasesCommand::input_to_command(state, input)?), SequencerFocus::PhraseEditor => Editor(PhraseCommand::input_to_command(state, input)?), _ => return None, }) } } } impl InputToCommand for ArrangerCommand { fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option { use FocusCommand::*; use ArrangerCommand::*; Some(match input.event() { key!(KeyCode::Tab) => Self::Focus(Next), key!(Shift-KeyCode::Tab) => Self::Focus(Prev), key!(KeyCode::BackTab) => Self::Focus(Prev), key!(Shift-KeyCode::BackTab) => Self::Focus(Prev), key!(KeyCode::Up) => Self::Focus(Up), key!(KeyCode::Down) => Self::Focus(Down), key!(KeyCode::Left) => Self::Focus(Left), key!(KeyCode::Right) => Self::Focus(Right), key!(KeyCode::Enter) => Self::Focus(Enter), key!(KeyCode::Esc) => Self::Focus(Exit), key!(KeyCode::Char(' ')) => Self::Clock(ClockCommand::Play(None)), _ => match state.focused() { ArrangerFocus::Menu => { todo!() }, ArrangerFocus::Transport => { use TransportCommand::{Clock, Focus}; match TransportCommand::input_to_command(state, input)? { Clock(command) => { todo!() }, Focus(command) => { todo!() } } }, ArrangerFocus::PhraseEditor => Editor( PhraseCommand::input_to_command(state, input)? ), ArrangerFocus::Phrases => match input.event() { key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()), _ => Phrases(PhrasesCommand::input_to_command(state, input)?) }, ArrangerFocus::Arranger => { use ArrangerSelection as Select; use ArrangerTrackCommand as Track; use ArrangerClipCommand as Clip; use ArrangerSceneCommand as Scene; match input.event() { key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()), _ => match input.event() { // FIXME: boundary conditions key!(KeyCode::Up) => match state.selected() { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => if s > 0 { Select(Select::Scene(s - 1)) } else { Select(Select::Mix) }, Select::Clip(t, s) => if s > 0 { Select(Select::Clip(t, s - 1)) } else { Select(Select::Track(t)) }, }, key!(KeyCode::Down) => match state.selected() { Select::Mix => Select(Select::Scene(0)), Select::Track(t) => Select(Select::Clip(t, 0)), Select::Scene(s) => Select(Select::Scene(s + 1)), Select::Clip(t, s) => Select(Select::Clip(t, s + 1)), }, key!(KeyCode::Left) => match state.selected() { Select::Mix => return None, Select::Track(t) => if t > 0 { Select(Select::Track(t - 1)) } else { Select(Select::Mix) }, Select::Scene(s) => return None, Select::Clip(t, s) => if t > 0 { Select(Select::Clip(t - 1, s)) } else { Select(Select::Scene(s)) }, }, key!(KeyCode::Right) => match state.selected() { Select::Mix => return None, Select::Track(t) => Select(Select::Track(t + 1)), Select::Scene(s) => Select(Select::Clip(0, s)), Select::Clip(t, s) => Select(Select::Clip(t, s - 1)), }, key!(KeyCode::Char('+')) => Zoom(0), key!(KeyCode::Char('=')) => Zoom(0), key!(KeyCode::Char('_')) => Zoom(0), key!(KeyCode::Char('-')) => Zoom(0), key!(KeyCode::Char('`')) => { todo!("toggle state mode") }, key!(KeyCode::Char(',')) => match state.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('.')) => match state.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('<')) => match state.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('>')) => match state.selected() { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Enter) => match state.selected() { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => Scene(Scene::Play(s)), Select::Clip(t, s) => return None, }, key!(KeyCode::Delete) => match state.selected() { Select::Mix => Clear, Select::Track(t) => Track(Track::Delete(t)), Select::Scene(s) => Scene(Scene::Delete(s)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('c')) => match state.selected() { Select::Mix => Color(ItemColor::random()), Select::Track(t) => Track(Track::Delete(t)), Select::Scene(s) => Scene(Scene::Delete(s)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('s')) => match state.selected() { Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), _ => return None, }, key!(KeyCode::Char('g')) => match state.selected() { Select::Clip(t, s) => Clip(Clip::Get(t, s)), _ => return None, }, key!(Ctrl-KeyCode::Char('a')) => Scene(Scene::Add), key!(Ctrl-KeyCode::Char('t')) => Track(Track::Add), key!(KeyCode::Char('l')) => Clip(Clip::SetLoop(false)), _ => 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; let index = state.phrase_index(); let count = state.phrases().len(); match input.event() { key!(KeyCode::Up) => Some(Self::Select(0)), key!(KeyCode::Down) => Some(Self::Select(0)), key!(KeyCode::Char(',')) => { if index > 1 { state.set_phrase_index(state.phrase_index().saturating_sub(1)); Some(Self::Phrase(Phrase::Swap(index - 1, index))) } else { None } }, key!(KeyCode::Char('.')) => { if index < count.saturating_sub(1) { state.set_phrase_index(state.phrase_index() + 1); Some(Self::Phrase(Phrase::Swap(index + 1, index))) } else { None } }, key!(KeyCode::Delete) => { if index > 0 { state.set_phrase_index(index.min(count.saturating_sub(1))); Some(Self::Phrase(Phrase::Delete(index))) } else { None } }, key!(KeyCode::Char('a')) => Some(Self::Phrase(Phrase::Add(count))), key!(KeyCode::Char('i')) => Some(Self::Phrase(Phrase::Add(index + 1))), key!(KeyCode::Char('d')) => Some(Self::Phrase(Phrase::Duplicate(index))), key!(KeyCode::Char('c')) => Some(Self::Phrase(Phrase::Color(index, ItemColor::random()))), key!(KeyCode::Char('n')) => Some(Self::Rename(Rename::Begin)), key!(KeyCode::Char('t')) => Some(Self::Length(Length::Begin)), _ => match state.phrases_mode() { Some(PhrasesMode::Rename(..)) => { Rename::input_to_command(state, input).map(Self::Rename) }, Some(PhrasesMode::Length(..)) => { Length::input_to_command(state, input).map(Self::Length) }, _ => None } } } } impl InputToCommand for PhraseLengthCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { if let Some(PhrasesMode::Length(_, length, _)) = state.phrases_mode() { Some(match from.event() { key!(KeyCode::Up) => Self::Inc, key!(KeyCode::Down) => Self::Dec, key!(KeyCode::Right) => Self::Next, key!(KeyCode::Left) => Self::Prev, key!(KeyCode::Enter) => Self::Set(*length), key!(KeyCode::Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl InputToCommand for PhraseRenameCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { if let Some(PhrasesMode::Rename(_, ref old_name)) = state.phrases_mode() { Some(match from.event() { key!(KeyCode::Char(c)) => { let mut new_name = old_name.clone(); new_name.push(*c); Self::Set(new_name) }, key!(KeyCode::Backspace) => { let mut new_name = old_name.clone(); new_name.pop(); Self::Set(new_name) }, key!(KeyCode::Enter) => Self::Confirm, key!(KeyCode::Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl InputToCommand for PhraseCommand { fn input_to_command (state: &T, from: &TuiInput) -> Option { use PhraseCommand::*; Some(match from.event() { key!(KeyCode::Char('`')) => ToggleDirection, key!(KeyCode::Enter) => EnterEditMode, key!(KeyCode::Esc) => ExitEditMode, key!(KeyCode::Char('a')) => NoteAppend, key!(KeyCode::Char('s')) => NoteSet, key!(KeyCode::Char('[')) => NoteLengthSet(prev_note_length(state.note_len())), key!(KeyCode::Char(']')) => NoteLengthSet(next_note_length(state.note_len())), key!(KeyCode::Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), key!(KeyCode::Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)), key!(KeyCode::Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), key!(KeyCode::Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)), key!(KeyCode::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!(KeyCode::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!(KeyCode::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!(KeyCode::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!(KeyCode::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!(KeyCode::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 }) } }