From 2b163e9e27827c9165c21a280559bf5ebc66e733 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 8 Nov 2024 19:10:24 +0100 Subject: [PATCH] cmdsys: HandleKey -> MatchInput --- crates/tek_core/src/command.rs | 50 ++---- crates/tek_sequencer/src/arranger_cmd.rs | 161 +++++++----------- crates/tek_sequencer/src/arranger_tui.rs | 129 +++++++++++++++ crates/tek_sequencer/src/sequencer_cmd.rs | 160 ++---------------- crates/tek_sequencer/src/sequencer_tui.rs | 188 ++++++++++++++++++---- crates/tek_sequencer/src/transport_cmd.rs | 25 --- crates/tek_sequencer/src/transport_tui.rs | 24 +++ 7 files changed, 389 insertions(+), 348 deletions(-) diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index 07ef059c..ee2ffc9d 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -3,56 +3,18 @@ use crate::*; pub trait Command: Sized { fn run (&self, state: &mut S) -> Perhaps; } - -pub trait HandleKey + Clone + 'static>: Sized { - const HANDLE_KEY_MAP: &'static [(KeyEvent, C)] = &[]; // FIXME: needs to be method - #[inline] fn match_input_static (from: &TuiInput) -> Option { - if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { - return Self::match_key_static(&key) - } - None - } - #[inline] fn match_key_static (key: &KeyEvent) -> Option { - for (binding, command) in Self::HANDLE_KEY_MAP.iter() { - if key == binding { - return Some(command.clone()); - } - } - None - } - #[inline] fn match_key (&self, key: &KeyEvent) -> Option { - Self::match_key_static(key) - } - #[inline] fn match_input (&self, input: &TuiInput) -> Option { - Self::match_input_static(input) - } - #[inline] fn handle_input (&mut self, input: &TuiInput) -> Perhaps { - if let Some(command) = self.match_input(input) { - command.run(self) - } else { - Ok(None) - } - } - #[inline] fn handle_key (&mut self, key: &KeyEvent) -> Perhaps { - if let Some(command) = self.match_key(key) { - command.run(self) - } else { - Ok(None) - } - } +pub trait MatchInput: Sized { + fn match_input (state: &S, input: &E::Input) -> Option; } - pub struct Menu, C: Command> { pub items: Vec>, pub index: usize, } - impl, C: Command> Menu { pub const fn item (command: C, name: &'static str, key: &'static str) -> MenuItem { MenuItem::Command(command, name, key) } } - pub enum MenuItem, C: Command> { /// Unused. __(PhantomData, PhantomData), @@ -61,3 +23,11 @@ pub enum MenuItem, C: Command> { /// A menu item with command, description and hotkey. Command(C, &'static str, &'static str) } +impl, C: Command> MenuItem { + pub fn sep () -> Self { + Self::Separator + } + pub fn cmd (command: C, text: &'static str, hotkey: &'static str) -> Self { + Self::Command(command, text, hotkey) + } +} diff --git a/crates/tek_sequencer/src/arranger_cmd.rs b/crates/tek_sequencer/src/arranger_cmd.rs index e199762d..bd28d4f5 100644 --- a/crates/tek_sequencer/src/arranger_cmd.rs +++ b/crates/tek_sequencer/src/arranger_cmd.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Clone, PartialEq)] -enum ArrangerCommand { +pub enum ArrangerCommand { FocusNext, FocusPrev, FocusUp, @@ -9,13 +9,12 @@ enum ArrangerCommand { FocusLeft, FocusRight, Transport(TransportCommand), - Phrase(PhrasePoolCommand), + Phrases(PhrasePoolCommand), Editor(PhraseEditorCommand), Arrangement(ArrangementCommand), } - #[derive(Clone, PartialEq)] -enum ArrangementCommand { +pub enum ArrangementCommand { ToggleViewMode, Delete, Activate, @@ -36,108 +35,58 @@ enum ArrangementCommand { GoLeft, GoRight, } - -/// Handle top-level events in standalone arranger. -impl Handle for Arranger { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if !self.handle_focused(from)?.unwrap_or(false) { - match from.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::Char('e')) => { self.edit_phrase(); }, - key!(KeyCode::Char(' ')) => { self.toggle_play()?; }, - key!(KeyCode::Char('n')) => { self.rename_selected(); }, - _ => return Ok(None) - } +impl Command> for ArrangerCommand { + fn run (&self, state: &mut Arranger) -> Perhaps { + use ArrangerCommand::*; + match self { + FocusNext => { state.focus_next(); }, + FocusPrev => { state.focus_prev(); }, + FocusUp => { state.focus_up(); }, + FocusDown => { state.focus_down(); }, + FocusLeft => { state.focus_left(); }, + FocusRight => { state.focus_right(); }, + Transport(command) => if let Some(ref transport) = state.transport { + return command.run(&mut*transport.write().unwrap()).map(|x|x.map(Transport)) + }, + Phrases(command) => { + return command.run(&mut*state.phrases.write().unwrap()).map(|x|x.map(Phrases)) + }, + Editor(command) => { + return command.run(&mut state.editor).map(|x|x.map(Editor)) + }, + Arrangement(command) => { + return command.run(&mut state.arrangement).map(|x|x.map(Arrangement)) + }, + } + state.show_phrase(); + state.update_status(); + Ok(None) + } +} +impl Command> for ArrangementCommand { + fn run (&self, state: &mut Arrangement) -> Perhaps { + use ArrangementCommand::*; + match self { + ToggleViewMode => { state.mode.to_next(); }, + Delete => { state.delete(); }, + Activate => { state.activate(); }, + Increment => { state.increment(); }, + Decrement => { state.decrement(); }, + ZoomIn => { state.zoom_in(); }, + ZoomOut => { state.zoom_out(); }, + MoveBack => { state.move_back(); }, + MoveForward => { state.move_forward(); }, + RandomColor => { state.randomize_color(); }, + Put => { state.phrase_put(); }, + Get => { state.phrase_get(); }, + AddScene => { state.scene_add(None, None)?; }, + AddTrack => { state.track_add(None, None)?; }, + ToggleLoop => { state.toggle_loop() }, + GoUp => { state.go_up() }, + GoDown => { state.go_down() }, + GoLeft => { state.go_left() }, + GoRight => { state.go_right() }, }; - self.update_status(); - Ok(Some(true)) - } -} -impl Arranger { - /// Helper for event passthru to focused component - fn handle_focused (&mut self, from: &TuiInput) -> Perhaps { - match self.focused() { - ArrangerFocus::Transport => self.transport.handle(from), - ArrangerFocus::PhrasePool => self.handle_pool(from), - ArrangerFocus::PhraseEditor => self.editor.handle(from), - ArrangerFocus::Arrangement => self.handle_arrangement(from) - .and_then(|result|{self.show_phrase();Ok(result)}), - } - } - /// Helper for phrase event passthru when phrase pool is focused - fn handle_pool (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('<')) => { - self.phrases_split = self.phrases_split.saturating_sub(1).max(12); - }, - key!(KeyCode::Char('>')) => { - self.phrases_split = self.phrases_split + 1; - }, - _ => return self.phrases.handle(from) - } - Ok(Some(true)) - } - /// Helper for phrase event passthru when arrangement is focused - fn handle_arrangement (&mut self, from: &TuiInput) -> Perhaps { - let mut handle_phrase = ||{ - let result = self.phrases.handle(from); - self.arrangement.phrase_put(); - result - }; - match from.event() { - key!(KeyCode::Char('a')) => return handle_phrase(), - key!(KeyCode::Char('i')) => return handle_phrase(), - key!(KeyCode::Char('d')) => return handle_phrase(), - key!(KeyCode::Char('<')) => if self.arrangement.selected == ArrangementFocus::Mix { - self.arrangement_split = self.arrangement_split.saturating_sub(1).max(12); - } else { - return self.arrangement.handle(from) - }, - key!(KeyCode::Char('>')) => if self.arrangement.selected == ArrangementFocus::Mix { - self.arrangement_split = self.arrangement_split + 1; - } else { - return self.arrangement.handle(from) - }, - _ => return self.arrangement.handle(from) - } - self.show_phrase(); - Ok(Some(true)) - } -} -/// Handle events for arrangement. -impl Handle for Arrangement { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('`')) => { self.mode.to_next(); }, - key!(KeyCode::Delete) => { self.delete(); }, - key!(KeyCode::Enter) => { self.activate(); }, - key!(KeyCode::Char('.')) => { self.increment(); }, - key!(KeyCode::Char(',')) => { self.decrement(); }, - key!(KeyCode::Char('+')) => { self.zoom_in(); }, - key!(KeyCode::Char('=')) => { self.zoom_in(); }, - key!(KeyCode::Char('_')) => { self.zoom_out(); }, - key!(KeyCode::Char('-')) => { self.zoom_out(); }, - key!(KeyCode::Char('<')) => { self.move_back(); }, - key!(KeyCode::Char('>')) => { self.move_forward(); }, - key!(KeyCode::Char('c')) => { self.randomize_color(); }, - key!(KeyCode::Char('s')) => { self.phrase_put(); }, - key!(KeyCode::Char('g')) => { self.phrase_get(); }, - key!(Ctrl-KeyCode::Char('a')) => { self.scene_add(None, None)?; }, - key!(Ctrl-KeyCode::Char('t')) => { self.track_add(None, None)?; }, - key!(KeyCode::Char('l')) => { self.toggle_loop() }, - key!(KeyCode::Up) => { self.go_up() }, - key!(KeyCode::Down) => { self.go_down() }, - key!(KeyCode::Left) => { self.go_left() }, - key!(KeyCode::Right) => { self.go_right() }, - _ => return Ok(None) - } - Ok(Some(true)) + Ok(None) } } diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index ec769f62..ffa6377a 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -502,3 +502,132 @@ impl<'a> Content for HorizontalArranger<'a, Tui> { ) } } +/// Handle top-level events in standalone arranger. +impl Handle for Arranger { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = ArrangerCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +/// Handle events for arrangement. +impl Handle for Arrangement { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = ArrangementCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +impl MatchInput> for ArrangerCommand { + fn match_input (state: &Arranger, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Tab) => Some(Self::FocusNext), + key!(Shift-KeyCode::Tab) => Some(Self::FocusPrev), + key!(KeyCode::BackTab) => Some(Self::FocusPrev), + key!(Shift-KeyCode::BackTab) => Some(Self::FocusPrev), + key!(KeyCode::Up) => Some(Self::FocusUp), + key!(KeyCode::Down) => Some(Self::FocusDown), + key!(KeyCode::Left) => Some(Self::FocusLeft), + key!(KeyCode::Right) => Some(Self::FocusRight), + key!(KeyCode::Char(' ')) => Some(Self::Transport(TransportCommand::TogglePlay)), + _ => match state.focused() { + ArrangerFocus::Transport => state.transport.as_ref() + .map(|t|TransportCommand::match_input(&*t.read().unwrap(), input) + .map(Self::Transport)) + .flatten(), + ArrangerFocus::PhrasePool => + PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) + .map(Self::Phrases), + ArrangerFocus::PhraseEditor => + PhraseEditorCommand::match_input(&state.editor, input) + .map(Self::Editor), + ArrangerFocus::Arrangement => + ArrangementCommand::match_input(&state.arrangement, &input) + .map(Self::Arrangement) + } + } + } +} +impl MatchInput> for ArrangementCommand { + fn match_input (_: &Arrangement, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Char('`')) => Some(Self::ToggleViewMode), + key!(KeyCode::Delete) => Some(Self::Delete), + key!(KeyCode::Enter) => Some(Self::Activate), + key!(KeyCode::Char('.')) => Some(Self::Increment), + key!(KeyCode::Char(',')) => Some(Self::Decrement), + key!(KeyCode::Char('+')) => Some(Self::ZoomIn), + key!(KeyCode::Char('=')) => Some(Self::ZoomOut), + key!(KeyCode::Char('_')) => Some(Self::ZoomOut), + key!(KeyCode::Char('-')) => Some(Self::ZoomOut), + key!(KeyCode::Char('<')) => Some(Self::MoveBack), + key!(KeyCode::Char('>')) => Some(Self::MoveForward), + key!(KeyCode::Char('c')) => Some(Self::RandomColor), + key!(KeyCode::Char('s')) => Some(Self::Put), + key!(KeyCode::Char('g')) => Some(Self::Get), + key!(Ctrl-KeyCode::Char('a')) => Some(Self::AddScene), + key!(Ctrl-KeyCode::Char('t')) => Some(Self::AddTrack), + key!(KeyCode::Char('l')) => Some(Self::ToggleLoop), + key!(KeyCode::Up) => Some(Self::GoUp), + key!(KeyCode::Down) => Some(Self::GoDown), + key!(KeyCode::Left) => Some(Self::GoLeft), + key!(KeyCode::Right) => Some(Self::GoRight), + _ => None + } + } +} +//impl Arranger { + ///// Helper for event passthru to focused component + //fn handle_focused (&mut self, from: &TuiInput) -> Perhaps { + //match self.focused() { + //ArrangerFocus::Transport => self.transport.handle(from), + //ArrangerFocus::PhrasePool => self.handle_pool(from), + //ArrangerFocus::PhraseEditor => self.editor.handle(from), + //ArrangerFocus::Arrangement => self.handle_arrangement(from) + //.and_then(|result|{self.show_phrase();Ok(result)}), + //} + //} + ///// Helper for phrase event passthru when phrase pool is focused + //fn handle_pool (&mut self, from: &TuiInput) -> Perhaps { + //match from.event() { + //key!(KeyCode::Char('<')) => { + //self.phrases_split = self.phrases_split.saturating_sub(1).max(12); + //}, + //key!(KeyCode::Char('>')) => { + //self.phrases_split = self.phrases_split + 1; + //}, + //_ => return self.phrases.handle(from) + //} + //Ok(Some(true)) + //} + ///// Helper for phrase event passthru when arrangement is focused + //fn handle_arrangement (&mut self, from: &TuiInput) -> Perhaps { + //let mut handle_phrase = ||{ + //let result = self.phrases.handle(from); + //self.arrangement.phrase_put(); + //result + //}; + //match from.event() { + //key!(KeyCode::Char('a')) => return handle_phrase(), + //key!(KeyCode::Char('i')) => return handle_phrase(), + //key!(KeyCode::Char('d')) => return handle_phrase(), + //key!(KeyCode::Char('<')) => if self.arrangement.selected == ArrangementFocus::Mix { + //self.arrangement_split = self.arrangement_split.saturating_sub(1).max(12); + //} else { + //return self.arrangement.handle(from) + //}, + //key!(KeyCode::Char('>')) => if self.arrangement.selected == ArrangementFocus::Mix { + //self.arrangement_split = self.arrangement_split + 1; + //} else { + //return self.arrangement.handle(from) + //}, + //_ => return self.arrangement.handle(from) + //} + //self.show_phrase(); + //Ok(Some(true)) + //} +//} diff --git a/crates/tek_sequencer/src/sequencer_cmd.rs b/crates/tek_sequencer/src/sequencer_cmd.rs index ee0ebb47..a6b97b90 100644 --- a/crates/tek_sequencer/src/sequencer_cmd.rs +++ b/crates/tek_sequencer/src/sequencer_cmd.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Clone, PartialEq)] -enum SequencerCommand { +pub enum SequencerCommand { FocusNext, FocusPrev, FocusUp, @@ -66,156 +66,24 @@ pub enum PhraseEditorCommand { 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::Prev), - 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::Inc), - key!(KeyCode::Down) => Some(PhraseLengthCommand::Dec), - key!(KeyCode::Right) => Some(PhraseLengthCommand::Next), - key!(KeyCode::Left) => Some(PhraseLengthCommand::Prev), - 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::NoteLengthDec), - key!(KeyCode::Char(']')) => Some(PhraseEditorCommand::NoteLengthInc), - 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 { + use SequencerCommand::*; 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)) + FocusNext => { state.focus_next(); }, + FocusPrev => { state.focus_prev(); }, + FocusUp => { state.focus_up(); }, + FocusDown => { state.focus_down(); }, + FocusLeft => { state.focus_left(); }, + FocusRight => { state.focus_right(); }, + Transport(command) => if let Some(ref transport) = state.transport { + return command.run(&mut*transport.write().unwrap()).map(|x|x.map(Transport)) }, - Self::Phrases(command) => { - return command - .run(&mut*state.phrases.write().unwrap()) - .map(|x|x.map(SequencerCommand::Phrases)) + Phrases(command) => { + return command.run(&mut*state.phrases.write().unwrap()).map(|x|x.map(Phrases)) }, - Self::Editor(command) => { - return command - .run(&mut state.editor) - .map(|x|x.map(SequencerCommand::Editor)) + Editor(command) => { + return command.run(&mut state.editor).map(|x|x.map(Editor)) }, } Ok(None) diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index 5d2bd3bb..aeb8a917 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -10,6 +10,42 @@ impl Content for Sequencer { }) } } +impl Handle for Sequencer { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = SequencerCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +impl MatchInput> for SequencerCommand { + fn match_input (state: &Sequencer, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Tab) => Some(Self::FocusNext), + key!(Shift-KeyCode::Tab) => Some(Self::FocusPrev), + key!(KeyCode::BackTab) => Some(Self::FocusPrev), + key!(Shift-KeyCode::BackTab) => Some(Self::FocusPrev), + key!(KeyCode::Up) => Some(Self::FocusUp), + key!(KeyCode::Down) => Some(Self::FocusDown), + key!(KeyCode::Left) => Some(Self::FocusLeft), + key!(KeyCode::Right) => Some(Self::FocusRight), + key!(KeyCode::Char(' ')) => Some(Self::Transport(TransportCommand::TogglePlay)), + _ => match state.focused() { + SequencerFocus::Transport => state.transport.as_ref() + .map(|t|TransportCommand::match_input(&*t.read().unwrap(), input) + .map(Self::Transport)) + .flatten(), + SequencerFocus::PhrasePool => + PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) + .map(Self::Phrases), + SequencerFocus::PhraseEditor => + PhraseEditorCommand::match_input(&state.editor, input) + .map(Self::Editor), + } + } + } +} // TODO: Display phrases always in order of appearance impl Content for PhrasePool { type Engine = Tui; @@ -45,6 +81,96 @@ impl Content for PhrasePool { Layers::new(move|add|{ add(&content)?; Ok(add(&title)?) }) } } +impl Handle for PhrasePool { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = PhrasePoolCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +impl MatchInput> for PhrasePoolCommand { + fn match_input (state: &PhrasePool, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Up) => Some(Self::Prev), + key!(KeyCode::Down) => Some(Self::Next), + key!(KeyCode::Char(',')) => Some(Self::MoveUp), + key!(KeyCode::Char('.')) => Some(Self::MoveDown), + key!(KeyCode::Delete) => Some(Self::Delete), + key!(KeyCode::Char('a')) => Some(Self::Append), + key!(KeyCode::Char('i')) => Some(Self::Insert), + key!(KeyCode::Char('d')) => Some(Self::Duplicate), + key!(KeyCode::Char('c')) => Some(Self::RandomColor), + key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)), + key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)), + _ => match state.mode { + Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::match_input(state, input) + .map(Self::Rename), + Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::match_input(state, input) + .map(Self::Length), + _ => None + } + } + } +} +impl MatchInput> for PhraseRenameCommand { + fn match_input (_: &PhrasePool, from: &TuiInput) -> Option { + match from.event() { + key!(KeyCode::Backspace) => Some(Self::Backspace), + key!(KeyCode::Enter) => Some(Self::Confirm), + key!(KeyCode::Esc) => Some(Self::Cancel), + key!(KeyCode::Char(c)) => Some(Self::Append(*c)), + _ => None + } + } +} +impl MatchInput> for PhraseLengthCommand { + fn match_input (_: &PhrasePool, from: &TuiInput) -> Option { + match from.event() { + key!(KeyCode::Up) => Some(Self::Inc), + key!(KeyCode::Down) => Some(Self::Dec), + key!(KeyCode::Right) => Some(Self::Next), + key!(KeyCode::Left) => Some(Self::Prev), + key!(KeyCode::Enter) => Some(Self::Confirm), + key!(KeyCode::Esc) => Some(Self::Cancel), + _ => None + } + } +} +impl Content for PhraseLength { + type Engine = Tui; + fn content (&self) -> impl Widget { + Layers::new(move|add|{ + match self.focus { + None => add(&row!( + " ", self.bars_string(), + ".", self.beats_string(), + ".", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Bar) => add(&row!( + "[", self.bars_string(), + "]", self.beats_string(), + ".", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Beat) => add(&row!( + " ", self.bars_string(), + "[", self.beats_string(), + "]", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Tick) => add(&row!( + " ", self.bars_string(), + ".", self.beats_string(), + "[", self.ticks_string(), + "]" + )), + } + }) + } +} impl Content for PhraseEditor { type Engine = Tui; fn content (&self) -> impl Widget { @@ -307,36 +433,36 @@ pub(crate) fn keys_vert () -> Buffer { }); buffer } -impl Content for PhraseLength { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move|add|{ - match self.focus { - None => add(&row!( - " ", self.bars_string(), - ".", self.beats_string(), - ".", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Bar) => add(&row!( - "[", self.bars_string(), - "]", self.beats_string(), - ".", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Beat) => add(&row!( - " ", self.bars_string(), - "[", self.beats_string(), - "]", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Tick) => add(&row!( - " ", self.bars_string(), - ".", self.beats_string(), - "[", self.ticks_string(), - "]" - )), - } - }) +impl Handle for PhraseEditor { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = PhraseEditorCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +impl MatchInput> for PhraseEditorCommand { + fn match_input (_: &PhraseEditor, from: &TuiInput) -> Option { + match from.event() { + key!(KeyCode::Char('`')) => Some(Self::ToggleDirection), + key!(KeyCode::Enter) => Some(Self::EnterEditMode), + key!(KeyCode::Esc) => Some(Self::ExitEditMode), + key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec), + key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc), + key!(KeyCode::Char('a')) => Some(Self::NoteAppend), + key!(KeyCode::Char('s')) => Some(Self::NoteSet), + key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut), + key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut), + key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn), + key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn), + key!(KeyCode::PageUp) => Some(Self::NotePageUp), + key!(KeyCode::PageDown) => Some(Self::NotePageDown), + key!(KeyCode::Up) => Some(Self::GoUp), + key!(KeyCode::Down) => Some(Self::GoDown), + key!(KeyCode::Left) => Some(Self::GoLeft), + key!(KeyCode::Right) => Some(Self::GoRight), + _ => None + } } } diff --git a/crates/tek_sequencer/src/transport_cmd.rs b/crates/tek_sequencer/src/transport_cmd.rs index c8afc30d..b1169151 100644 --- a/crates/tek_sequencer/src/transport_cmd.rs +++ b/crates/tek_sequencer/src/transport_cmd.rs @@ -1,15 +1,4 @@ use crate::*; - -impl Handle for TransportToolbar { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let TuiEvent::Input(crossterm::event::Event::Key(event)) = from.event() { - let _undo = self.handle_key(event)?; - return Ok(Some(true)) - } - Ok(None) - } -} - #[derive(Clone, PartialEq)] pub enum TransportCommand { FocusNext, @@ -27,20 +16,6 @@ pub enum TransportCommand { SetQuant(f64), SetSync(f64), } - -impl HandleKey for TransportToolbar { - const HANDLE_KEY_MAP: &'static [(KeyEvent, TransportCommand)] = &[ - ( key(KeyCode::Char(' ')), TransportCommand::FocusPrev), - (shift(key(KeyCode::Char(' '))), TransportCommand::FocusPrev), - ( key(KeyCode::Left), TransportCommand::FocusPrev), - ( key(KeyCode::Right), TransportCommand::FocusNext), - ( key(KeyCode::Char('.')), TransportCommand::Increment), - ( key(KeyCode::Char(',')), TransportCommand::Decrement), - ( key(KeyCode::Char('>')), TransportCommand::FineIncrement), - ( key(KeyCode::Char('<')), TransportCommand::FineDecrement), - ]; -} - impl Command> for TransportCommand { fn run (&self, state: &mut TransportToolbar) -> Perhaps { match self { diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index 2da303e1..0d17c7fb 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -45,3 +45,27 @@ impl TransportToolbarFocus { lay!(corners, highlight, *widget) } } +impl Handle for TransportToolbar { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if let Some(command) = TransportCommand::match_input(self, from) { + let _undo = command.run(self)?; + return Ok(Some(true)) + } + Ok(None) + } +} +impl MatchInput> for TransportCommand { + fn match_input (_: &TransportToolbar, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), + key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev), + key!(KeyCode::Left) => Some(Self::FocusPrev), + key!(KeyCode::Right) => Some(Self::FocusNext), + key!(KeyCode::Char('.')) => Some(Self::Increment), + key!(KeyCode::Char(',')) => Some(Self::Decrement), + key!(KeyCode::Char('>')) => Some(Self::FineIncrement), + key!(KeyCode::Char('<')) => Some(Self::FineDecrement), + _ => None + } + } +}