From abbe0dc8f76f5f39b77755d38290903d294ba89e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 7 Nov 2024 01:00:20 +0100 Subject: [PATCH] wip: command system: then for sequencer --- crates/tek_core/src/command.rs | 11 +- crates/tek_sequencer/src/sequencer_cmd.rs | 476 +++++++++++++++++----- crates/tek_sequencer/src/transport_cmd.rs | 2 +- 3 files changed, 378 insertions(+), 111 deletions(-) diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index d2653176..000f9921 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -27,15 +27,16 @@ pub trait Command: Sized { pub trait HandleKey + 'static>: Sized { const HANDLE_KEY_MAP: &'static [(KeyEvent, C)]; - fn handle_key (&mut self, key: &KeyEvent) -> Perhaps { - let mut run_command: Option<&'static C> = None; + fn match_key (key: &KeyEvent) -> Option<&'static C> { for (binding, command) in Self::HANDLE_KEY_MAP.iter() { if key == binding { - run_command = Some(command); - break + return Some(command); } } - if let Some(command) = run_command { + None + } + fn handle_key (&mut self, key: &KeyEvent) -> Perhaps { + if let Some(command) = Self::match_key(key) { command.run(self) } else { Ok(None) diff --git a/crates/tek_sequencer/src/sequencer_cmd.rs b/crates/tek_sequencer/src/sequencer_cmd.rs index 21cbb653..b8a0cf55 100644 --- a/crates/tek_sequencer/src/sequencer_cmd.rs +++ b/crates/tek_sequencer/src/sequencer_cmd.rs @@ -1,5 +1,6 @@ use crate::*; +#[derive(Clone, PartialEq)] enum SequencerCommand { FocusNext, FocusPrev, @@ -10,132 +11,397 @@ enum SequencerCommand { Transport(TransportCommand), Phrase(PhrasePoolCommand), Editor(PhraseEditorCommand), + // TODO: 1-8 seek markers that by default start every 8th of the phrase } - -enum PhrasePoolCommand { +#[derive(Clone, PartialEq)] +pub enum PhrasePoolCommand { + Previous, + Next, + MoveUp, + MoveDown, + Delete, + Append, + Insert, + Duplicate, + RandomColor, + Edit, + Name(PhraseNameCommand), + Length(PhraseLengthCommand), } - -enum PhraseLengthCommand { +#[derive(Clone, PartialEq)] +pub enum PhraseNameCommand { + Begin, + Backspace, + Append(char), + Set(String), + Confirm, + Cancel, } - -enum PhraseEditorCommand { +#[derive(Clone, PartialEq)] +pub enum PhraseLengthCommand { + Begin, + Next, + Previous, + Increment, + Decrement, + Set(usize), + Confirm, + Cancel, +} +#[derive(Clone, PartialEq)] +pub enum PhraseEditorCommand { + ToggleDirection, + EnterEditMode, + ExitEditMode, + NoteLengthDecrement, + NoteLengthIncrement, + TimeZoomIn, + TimeZoomOut, + NoteAppend, + NoteSet, + NotePageUp, + NotePageDown, + GoUp, + GoDown, + GoLeft, + GoRight, +} +impl HandleKey for Sequencer { + const HANDLE_KEY_MAP: &'static [(KeyEvent, SequencerCommand)] = &[ + (key(KeyCode::Tab), SequencerCommand::FocusNext), + (shift(key(KeyCode::Tab)), SequencerCommand::FocusPrev), + (key(KeyCode::BackTab), SequencerCommand::FocusPrev), + (shift(key(KeyCode::BackTab)), SequencerCommand::FocusPrev), + (key(KeyCode::Up), SequencerCommand::FocusUp), + (key(KeyCode::Down), SequencerCommand::FocusDown), + (key(KeyCode::Left), SequencerCommand::FocusLeft), + (key(KeyCode::Right), SequencerCommand::FocusRight), + (key(KeyCode::Char(' ')), SequencerCommand::Transport(TransportCommand::TogglePlay)), // FIXME go through transport + ]; +} +impl HandleKey for PhrasePool { + const HANDLE_KEY_MAP: &'static [(KeyEvent, PhrasePoolCommand)] = &[ + (key(KeyCode::Up), PhrasePoolCommand::Previous), + (key(KeyCode::Down), PhrasePoolCommand::Next), + (key(KeyCode::Char(',')), PhrasePoolCommand::MoveUp), + (key(KeyCode::Char('.')), PhrasePoolCommand::MoveDown), + (key(KeyCode::Delete), PhrasePoolCommand::Delete), + (key(KeyCode::Char('a')), PhrasePoolCommand::Append), + (key(KeyCode::Char('i')), PhrasePoolCommand::Insert), + (key(KeyCode::Char('d')), PhrasePoolCommand::Duplicate), + (key(KeyCode::Char('c')), PhrasePoolCommand::RandomColor), + (key(KeyCode::Char('n')), PhrasePoolCommand::Name(PhraseNameCommand::Begin)), + (key(KeyCode::Char('t')), PhrasePoolCommand::Length(PhraseLengthCommand::Begin)), + ]; +} +impl HandleKey for PhrasePool { + const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseNameCommand)] = &[ + (key(KeyCode::Backspace), PhraseNameCommand::Backspace), + (key(KeyCode::Enter), PhraseNameCommand::Confirm), + (key(KeyCode::Esc), PhraseNameCommand::Cancel), + ]; +} +impl HandleKey for PhrasePool { + const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseLengthCommand)] = &[ + (key(KeyCode::Up), PhraseLengthCommand::Increment), + (key(KeyCode::Down), PhraseLengthCommand::Decrement), + (key(KeyCode::Right), PhraseLengthCommand::Next), + (key(KeyCode::Left), PhraseLengthCommand::Previous), + (key(KeyCode::Enter), PhraseLengthCommand::Confirm), + (key(KeyCode::Esc), PhraseLengthCommand::Cancel), + ]; +} +impl HandleKey for PhraseEditor { + const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseEditorCommand)] = &[ + (key(KeyCode::Char('`')), PhraseEditorCommand::ToggleDirection), + (key(KeyCode::Enter), PhraseEditorCommand::EnterEditMode), + (key(KeyCode::Esc), PhraseEditorCommand::ExitEditMode), + (key(KeyCode::Char('[')), PhraseEditorCommand::NoteLengthDecrement), + (key(KeyCode::Char(']')), PhraseEditorCommand::NoteLengthIncrement), + (key(KeyCode::Char('a')), PhraseEditorCommand::NoteAppend), + (key(KeyCode::Char('s')), PhraseEditorCommand::NoteSet), + (key(KeyCode::Char('-')), PhraseEditorCommand::TimeZoomOut), + (key(KeyCode::Char('_')), PhraseEditorCommand::TimeZoomOut), + (key(KeyCode::Char('=')), PhraseEditorCommand::TimeZoomIn), + (key(KeyCode::Char('+')), PhraseEditorCommand::TimeZoomIn), + (key(KeyCode::PageUp), PhraseEditorCommand::NotePageUp), + (key(KeyCode::PageDown), PhraseEditorCommand::NotePageDown), + (key(KeyCode::Up), PhraseEditorCommand::GoUp), + (key(KeyCode::Down), PhraseEditorCommand::GoDown), + (key(KeyCode::Left), PhraseEditorCommand::GoLeft), + (key(KeyCode::Right), PhraseEditorCommand::GoRight), + ]; } - /// Handle top-level events in standalone sequencer. impl Handle for Sequencer { fn handle (&mut self, from: &TuiInput) -> Perhaps { - if !match self.focused() { + let handled = match self.focused() { SequencerFocus::Transport => self.transport.handle(from)?, SequencerFocus::PhrasePool => self.phrases.handle(from)?, SequencerFocus::PhraseEditor => self.editor.handle(from)? - }.unwrap_or(false) { - match from.event() { - // Tab navigation - 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(); }, - // Directional navigation - key!(KeyCode::Up) => { self.focus_up(); }, - key!(KeyCode::Down) => { self.focus_down(); }, - key!(KeyCode::Left) => { self.focus_left(); }, - key!(KeyCode::Right) => { self.focus_right(); }, - // Global play/pause binding - key!(KeyCode::Char(' ')) => match self.transport { - Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, - None => { return Ok(None) } - }, - _ => {} - } - }; - Ok(Some(true)) + }.unwrap_or(false); + if handled { + return Ok(Some(true)) + } + if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { + let _undo = self.handle_key(key)?; + return Ok(Some(true)) + } + Ok(None) } } impl Handle for PhrasePool { fn handle (&mut self, from: &TuiInput) -> Perhaps { - match self.mode { - Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) => { - let mut phrase = self.phrases[phrase].write().unwrap(); - match from.event() { - key!(KeyCode::Backspace) => { phrase.name.pop(); }, - key!(KeyCode::Char(c)) => { phrase.name.push(*c); }, - key!(Shift-KeyCode::Char(c)) => { phrase.name.push(*c); }, - key!(KeyCode::Esc) => { phrase.name = old_name.clone(); self.mode = None; }, - key!(KeyCode::Enter) => { self.mode = None; }, - _ => return Ok(Some(true)) + if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { + match self.mode { + Some(PhrasePoolMode::Rename(..)) => { + if HandleKey::::match_key(key).is_some() { + let _undo = HandleKey::::handle_key(self, key)?; + return Ok(Some(true)) + } else if let KeyEvent { code: KeyCode::Char(c), .. } = key { + PhraseNameCommand::Append(*c).run(self)?; + return Ok(Some(true)) + } + }, + Some(PhrasePoolMode::Length(..)) => { + let _undo = HandleKey::::handle_key(self, key)?; + return Ok(Some(true)) + }, + None => { + let _undo = HandleKey::::handle_key(self, key)?; + return Ok(Some(true)) } - }, - Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) => { - match from.event() { - key!(KeyCode::Left) => { focus.prev() }, - key!(KeyCode::Right) => { focus.next() }, - key!(KeyCode::Esc) => { self.mode = None; }, - key!(KeyCode::Enter) => { - self.phrases[phrase].write().unwrap().length = *length; - self.mode = None; - }, - key!(KeyCode::Up) => match focus { - PhraseLengthFocus::Bar => { *length += 4 * PPQ }, - PhraseLengthFocus::Beat => { *length += PPQ }, - PhraseLengthFocus::Tick => { *length += 1 }, - }, - key!(KeyCode::Down) => match focus { - PhraseLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, - PhraseLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, - PhraseLengthFocus::Tick => { *length = length.saturating_sub(1) }, - }, - _ => return Ok(Some(true)) - } - }, - None => match from.event() { - key!(KeyCode::Up) => { self.select_prev() }, - key!(KeyCode::Down) => { self.select_next() }, - key!(KeyCode::Delete) => { self.delete_selected() }, - key!(KeyCode::Char('a')) => { self.append_new(None, None) }, - key!(KeyCode::Char('i')) => { self.insert_new(None, None) }, - key!(KeyCode::Char('d')) => { self.insert_dup() }, - key!(KeyCode::Char('c')) => { self.randomize_color() }, - key!(KeyCode::Char('n')) => { self.begin_rename() }, - key!(KeyCode::Char('t')) => { self.begin_length() }, - key!(KeyCode::Char(',')) => { self.move_up() }, - key!(KeyCode::Char('.')) => { self.move_down() }, - _ => return Ok(None), } } - - return Ok(Some(true)) + Ok(None) } } impl Handle for PhraseEditor { fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('`')) => { self.mode = !self.mode; }, - key!(KeyCode::Enter) => { self.entered = true; }, - key!(KeyCode::Esc) => { self.entered = false; }, - key!(KeyCode::Char('[')) => if self.entered { self.note_length_dec() }, - key!(KeyCode::Char(']')) => if self.entered { self.note_length_inc() }, - key!(KeyCode::Char('a')) => if self.entered { self.put(); self.time_cursor_advance(); }, - key!(KeyCode::Char('s')) => if self.entered { self.put(); }, - key!(KeyCode::Char('-')) => self.time_zoom_out(), - key!(KeyCode::Char('_')) => self.time_zoom_out(), - key!(KeyCode::Char('=')) => self.time_zoom_in(), - key!(KeyCode::Char('+')) => self.time_zoom_in(), - key!(KeyCode::PageUp) => self.note_page_up(), - key!(KeyCode::PageDown) => self.note_page_down(), - key!(KeyCode::Up) => match self.entered { - true => self.note_cursor_inc(), false => self.note_scroll_inc(), - }, - key!(KeyCode::Down) => match self.entered { - true => self.note_cursor_dec(), false => self.note_scroll_dec(), - }, - key!(KeyCode::Left) => match self.entered { - true => self.time_cursor_dec(), false => self.time_scroll_dec(), - }, - key!(KeyCode::Right) => match self.entered { - true => self.time_cursor_inc(), false => self.time_scroll_inc(), - }, - _ => { return Ok(None) } + if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { + let _undo = self.handle_key(key)?; + return Ok(Some(true)) } - 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::Phrase(command) => { + return command + .run(&mut*state.phrases.write().unwrap()) + .map(|x|x.map(SequencerCommand::Phrase)) + }, + 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::Name(PhraseNameCommand::Begin) => { + state.begin_rename() + }, + Self::Name(_) => { + unreachable!() + }, + Self::Length(PhraseLengthCommand::Begin) => { + state.begin_length() + }, + Self::Length(_) => { + unreachable!() + }, + } + Ok(None) + } +} +impl Command> for PhraseNameCommand { + 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) } } diff --git a/crates/tek_sequencer/src/transport_cmd.rs b/crates/tek_sequencer/src/transport_cmd.rs index 8523fb4d..c8afc30d 100644 --- a/crates/tek_sequencer/src/transport_cmd.rs +++ b/crates/tek_sequencer/src/transport_cmd.rs @@ -10,7 +10,7 @@ impl Handle for TransportToolbar { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub enum TransportCommand { FocusNext, FocusPrev,