use crate::*; #[derive(Clone, PartialEq)] enum SequencerCommand { FocusNext, FocusPrev, FocusUp, FocusDown, FocusLeft, FocusRight, Transport(TransportCommand), Phrases(PhrasePoolCommand), Editor(PhraseEditorCommand), } #[derive(Clone, PartialEq)] pub enum PhrasePoolCommand { Previous, Next, MoveUp, MoveDown, Delete, Append, Insert, Duplicate, RandomColor, Edit, Rename(PhraseRenameCommand), Length(PhraseLengthCommand), } #[derive(Clone, PartialEq)] pub enum PhraseRenameCommand { Begin, Backspace, Append(char), Set(String), Confirm, Cancel, } #[derive(Clone, PartialEq)] pub enum PhraseLengthCommand { Begin, Next, Previous, Increment, Decrement, Set(usize), Confirm, Cancel, } #[derive(Clone, PartialEq)] pub enum PhraseEditorCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase ToggleDirection, EnterEditMode, ExitEditMode, NoteLengthDecrement, NoteLengthIncrement, TimeZoomIn, TimeZoomOut, NoteAppend, NoteSet, NotePageUp, NotePageDown, GoUp, GoDown, GoLeft, GoRight, } impl HandleKey for Sequencer { fn match_input (&self, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Tab) => Some(SequencerCommand::FocusNext), key!(Shift-KeyCode::Tab) => Some(SequencerCommand::FocusPrev), key!(KeyCode::BackTab) => Some(SequencerCommand::FocusPrev), key!(Shift-KeyCode::BackTab) => Some(SequencerCommand::FocusPrev), key!(KeyCode::Up) => Some(SequencerCommand::FocusUp), key!(KeyCode::Down) => Some(SequencerCommand::FocusDown), key!(KeyCode::Left) => Some(SequencerCommand::FocusLeft), key!(KeyCode::Right) => Some(SequencerCommand::FocusRight), key!(KeyCode::Char(' ')) => Some(SequencerCommand::Transport( TransportCommand::TogglePlay)), // FIXME go through transport _ => match self.focused() { SequencerFocus::Transport => self.transport.as_ref() .map(|t|t.read().unwrap().match_input(from).map(SequencerCommand::Transport)) .flatten(), SequencerFocus::PhrasePool => self.phrases.read().unwrap() .match_input(from) .map(SequencerCommand::Phrases), SequencerFocus::PhraseEditor => self.editor .match_input(from) .map(SequencerCommand::Editor), } } } } impl HandleKey for PhrasePool { fn match_input (&self, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Up) => Some(PhrasePoolCommand::Previous), key!(KeyCode::Down) => Some(PhrasePoolCommand::Next), key!(KeyCode::Char(',')) => Some(PhrasePoolCommand::MoveUp), key!(KeyCode::Char('.')) => Some(PhrasePoolCommand::MoveDown), key!(KeyCode::Delete) => Some(PhrasePoolCommand::Delete), key!(KeyCode::Char('a')) => Some(PhrasePoolCommand::Append), key!(KeyCode::Char('i')) => Some(PhrasePoolCommand::Insert), key!(KeyCode::Char('d')) => Some(PhrasePoolCommand::Duplicate), key!(KeyCode::Char('c')) => Some(PhrasePoolCommand::RandomColor), key!(KeyCode::Char('n')) => Some(PhrasePoolCommand::Rename(PhraseRenameCommand::Begin)), key!(KeyCode::Char('t')) => Some(PhrasePoolCommand::Length(PhraseLengthCommand::Begin)), _ => match self.mode { Some(PhrasePoolMode::Rename(..)) => HandleKey:: ::match_input(self, from).map(PhrasePoolCommand::Rename), Some(PhrasePoolMode::Length(..)) => HandleKey:: ::match_input(self, from).map(PhrasePoolCommand::Length), _ => None } } } } impl HandleKey for PhrasePool { fn match_input (&self, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Backspace) => Some(PhraseRenameCommand::Backspace), key!(KeyCode::Enter) => Some(PhraseRenameCommand::Confirm), key!(KeyCode::Esc) => Some(PhraseRenameCommand::Cancel), key!(KeyCode::Char(c)) => Some(PhraseRenameCommand::Append(*c)), _ => None } } } impl HandleKey for PhrasePool { fn match_input (&self, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Up) => Some(PhraseLengthCommand::Increment), key!(KeyCode::Down) => Some(PhraseLengthCommand::Decrement), key!(KeyCode::Right) => Some(PhraseLengthCommand::Next), key!(KeyCode::Left) => Some(PhraseLengthCommand::Previous), key!(KeyCode::Enter) => Some(PhraseLengthCommand::Confirm), key!(KeyCode::Esc) => Some(PhraseLengthCommand::Cancel), _ => None } } } impl HandleKey for PhraseEditor { fn match_input (&self, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Char('`')) => Some(PhraseEditorCommand::ToggleDirection), key!(KeyCode::Enter) => Some(PhraseEditorCommand::EnterEditMode), key!(KeyCode::Esc) => Some(PhraseEditorCommand::ExitEditMode), key!(KeyCode::Char('[')) => Some(PhraseEditorCommand::NoteLengthDecrement), key!(KeyCode::Char(']')) => Some(PhraseEditorCommand::NoteLengthIncrement), key!(KeyCode::Char('a')) => Some(PhraseEditorCommand::NoteAppend), key!(KeyCode::Char('s')) => Some(PhraseEditorCommand::NoteSet), key!(KeyCode::Char('-')) => Some(PhraseEditorCommand::TimeZoomOut), key!(KeyCode::Char('_')) => Some(PhraseEditorCommand::TimeZoomOut), key!(KeyCode::Char('=')) => Some(PhraseEditorCommand::TimeZoomIn), key!(KeyCode::Char('+')) => Some(PhraseEditorCommand::TimeZoomIn), key!(KeyCode::PageUp) => Some(PhraseEditorCommand::NotePageUp), key!(KeyCode::PageDown) => Some(PhraseEditorCommand::NotePageDown), key!(KeyCode::Up) => Some(PhraseEditorCommand::GoUp), key!(KeyCode::Down) => Some(PhraseEditorCommand::GoDown), key!(KeyCode::Left) => Some(PhraseEditorCommand::GoLeft), key!(KeyCode::Right) => Some(PhraseEditorCommand::GoRight), _ => None } } } /// Handle top-level events in standalone sequencer. impl Handle for Sequencer { fn handle (&mut self, from: &TuiInput) -> Perhaps { if let Some(command) = self.match_input(from) { let _undo = command.run(self)?; return Ok(Some(true)) } Ok(None) } } impl Handle for PhrasePool { fn handle (&mut self, from: &TuiInput) -> Perhaps { if let Some(command) = HandleKey::::match_input(self, from) { let _undo = command.run(self)?; return Ok(Some(true)) } Ok(None) } } impl Handle for PhraseEditor { fn handle (&mut self, from: &TuiInput) -> Perhaps { if let Some(command) = self.match_input(from) { let _undo = command.run(self)?; return Ok(Some(true)) } Ok(None) } } impl Command> for SequencerCommand { fn run (&self, state: &mut Sequencer) -> Perhaps { match self { Self::FocusNext => { state.focus_next(); }, Self::FocusPrev => { state.focus_prev(); }, Self::FocusUp => { state.focus_up(); }, Self::FocusDown => { state.focus_down(); }, Self::FocusLeft => { state.focus_left(); }, Self::FocusRight => { state.focus_right(); }, Self::Transport(command) => if let Some(ref transport) = state.transport { return command .run(&mut*transport.write().unwrap()) .map(|x|x.map(SequencerCommand::Transport)) }, Self::Phrases(command) => { return command .run(&mut*state.phrases.write().unwrap()) .map(|x|x.map(SequencerCommand::Phrases)) }, Self::Editor(command) => { return command .run(&mut state.editor) .map(|x|x.map(SequencerCommand::Editor)) }, } Ok(None) } } impl Command> for PhrasePoolCommand { fn run (&self, state: &mut PhrasePool) -> Perhaps { match self { Self::Previous => { state.select_prev() }, Self::Next => { state.select_next() }, Self::Delete => { state.delete_selected() }, Self::Append => { state.append_new(None, None) }, Self::Insert => { state.insert_new(None, None) }, Self::Duplicate => { state.insert_dup() }, Self::Edit => { todo!(); } Self::RandomColor => { state.randomize_color() }, Self::MoveUp => { state.move_up() }, Self::MoveDown => { state.move_down() }, Self::Rename(PhraseRenameCommand::Begin) => { state.begin_rename() }, Self::Rename(_) => { unreachable!() }, Self::Length(PhraseLengthCommand::Begin) => { state.begin_length() }, Self::Length(_) => { unreachable!() }, } Ok(None) } } impl Command> for PhraseRenameCommand { fn run (&self, state: &mut PhrasePool) -> Perhaps { if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = state.mode { match self { Self::Begin => { unreachable!(); }, Self::Backspace => { let mut phrase = state.phrases[phrase].write().unwrap(); let old_name = phrase.name.clone(); phrase.name.pop(); return Ok(Some(Self::Set(old_name))) }, Self::Append(c) => { let mut phrase = state.phrases[phrase].write().unwrap(); let old_name = phrase.name.clone(); phrase.name.push(*c); return Ok(Some(Self::Set(old_name))) }, Self::Set(s) => { let mut phrase = state.phrases[phrase].write().unwrap(); phrase.name = s.into(); return Ok(Some(Self::Set(old_name.clone()))) }, Self::Confirm => { let old_name = old_name.clone(); state.mode = None; return Ok(Some(Self::Set(old_name))) }, Self::Cancel => { let mut phrase = state.phrases[phrase].write().unwrap(); phrase.name = old_name.clone(); } }; Ok(None) } else if *self == Self::Begin { todo!() } else { unreachable!() } } } impl Command> for PhraseLengthCommand { fn run (&self, state: &mut PhrasePool) -> Perhaps { if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = state.mode { match self { Self::Begin => { unreachable!(); }, Self::Previous => { focus.prev() }, Self::Next => { focus.next() }, Self::Increment => match focus { PhraseLengthFocus::Bar => { *length += 4 * PPQ }, PhraseLengthFocus::Beat => { *length += PPQ }, PhraseLengthFocus::Tick => { *length += 1 }, }, Self::Decrement => match focus { PhraseLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, PhraseLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, PhraseLengthFocus::Tick => { *length = length.saturating_sub(1) }, }, Self::Cancel => { state.mode = None; }, Self::Confirm => { return Self::Set(*length).run(state) }, Self::Set(length) => { let mut phrase = state.phrases[phrase].write().unwrap(); let old_length = phrase.length; phrase.length = *length; state.mode = None; return Ok(Some(Self::Set(old_length))) }, } Ok(None) } else if *self == Self::Begin { todo!() } else { unreachable!() } } } impl Command> for PhraseEditorCommand { fn run (&self, state: &mut PhraseEditor) -> Perhaps { match self { Self::ToggleDirection => { state.mode = !state.mode; }, Self::EnterEditMode => { state.entered = true; }, Self::ExitEditMode => { state.entered = false; }, Self::TimeZoomOut => { state.time_zoom_out() }, Self::TimeZoomIn => { state.time_zoom_in() }, Self::NoteLengthDecrement => { state.note_length_dec() }, Self::NoteLengthIncrement => { state.note_length_inc() }, Self::NotePageUp => { state.note_page_up() }, Self::NotePageDown => { state.note_page_down() }, Self::NoteAppend => if state.entered { state.put(); state.time_cursor_advance(); }, Self::NoteSet => if state.entered { state.put(); }, Self::GoUp => match state.entered { true => state.note_cursor_inc(), false => state.note_scroll_inc(), }, Self::GoDown => match state.entered { true => state.note_cursor_dec(), false => state.note_scroll_dec(), }, Self::GoLeft => match state.entered { true => state.time_cursor_dec(), false => state.time_scroll_dec(), }, Self::GoRight => match state.entered { true => state.time_cursor_inc(), false => state.time_scroll_inc(), }, } Ok(None) } }