cmdsys: HandleKey -> MatchInput

This commit is contained in:
🪞👃🪞 2024-11-08 19:10:24 +01:00
parent 1aaad23691
commit 2b163e9e27
7 changed files with 389 additions and 348 deletions

View file

@ -3,56 +3,18 @@ use crate::*;
pub trait Command<S>: Sized {
fn run (&self, state: &mut S) -> Perhaps<Self>;
}
pub trait HandleKey<C: Command<Self> + Clone + 'static>: Sized {
const HANDLE_KEY_MAP: &'static [(KeyEvent, C)] = &[]; // FIXME: needs to be method
#[inline] fn match_input_static (from: &TuiInput) -> Option<C> {
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<C> {
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<C> {
Self::match_key_static(key)
}
#[inline] fn match_input (&self, input: &TuiInput) -> Option<C> {
Self::match_input_static(input)
}
#[inline] fn handle_input (&mut self, input: &TuiInput) -> Perhaps<C> {
if let Some(command) = self.match_input(input) {
command.run(self)
} else {
Ok(None)
}
}
#[inline] fn handle_key (&mut self, key: &KeyEvent) -> Perhaps<C> {
if let Some(command) = self.match_key(key) {
command.run(self)
} else {
Ok(None)
}
}
pub trait MatchInput<E: Engine, S>: Sized {
fn match_input (state: &S, input: &E::Input) -> Option<Self>;
}
pub struct Menu<E: Engine, S: Handle<E>, C: Command<S>> {
pub items: Vec<MenuItem<E, S, C>>,
pub index: usize,
}
impl<E: Engine, S: Handle<E>, C: Command<S>> Menu<E, S, C> {
pub const fn item (command: C, name: &'static str, key: &'static str) -> MenuItem<E, S, C> {
MenuItem::Command(command, name, key)
}
}
pub enum MenuItem<E: Engine, S: Handle<E>, C: Command<S>> {
/// Unused.
__(PhantomData<E>, PhantomData<S>),
@ -61,3 +23,11 @@ pub enum MenuItem<E: Engine, S: Handle<E>, C: Command<S>> {
/// A menu item with command, description and hotkey.
Command(C, &'static str, &'static str)
}
impl<E: Engine, S: Handle<E>, C: Command<S>> MenuItem<E, S, C> {
pub fn sep () -> Self {
Self::Separator
}
pub fn cmd (command: C, text: &'static str, hotkey: &'static str) -> Self {
Self::Command(command, text, hotkey)
}
}

View file

@ -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<Tui> for Arranger<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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 <E: Engine> Command<Arranger<E>> for ArrangerCommand {
fn run (&self, state: &mut Arranger<E>) -> Perhaps<Self> {
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 <E: Engine> Command<Arrangement<E>> for ArrangementCommand {
fn run (&self, state: &mut Arrangement<E>) -> Perhaps<Self> {
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<Tui> {
/// Helper for event passthru to focused component
fn handle_focused (&mut self, from: &TuiInput) -> Perhaps<bool> {
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<bool> {
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<bool> {
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<Tui> for Arrangement<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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)
}
}

View file

@ -502,3 +502,132 @@ impl<'a> Content for HorizontalArranger<'a, Tui> {
)
}
}
/// Handle top-level events in standalone arranger.
impl Handle<Tui> for Arranger<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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<Tui> for Arrangement<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = ArrangementCommand::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl MatchInput<Tui, Arranger<Tui>> for ArrangerCommand {
fn match_input (state: &Arranger<Tui>, input: &TuiInput) -> Option<Self> {
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<Tui, Arrangement<Tui>> for ArrangementCommand {
fn match_input (_: &Arrangement<Tui>, input: &TuiInput) -> Option<Self> {
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<Tui> {
///// Helper for event passthru to focused component
//fn handle_focused (&mut self, from: &TuiInput) -> Perhaps<bool> {
//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<bool> {
//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<bool> {
//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))
//}
//}

View file

@ -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<SequencerCommand> for Sequencer<Tui> {
fn match_input (&self, from: &TuiInput) -> Option<SequencerCommand> {
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<PhrasePoolCommand> for PhrasePool<Tui> {
fn match_input (&self, from: &TuiInput) -> Option<PhrasePoolCommand> {
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::<PhraseRenameCommand>
::match_input(self, from).map(PhrasePoolCommand::Rename),
Some(PhrasePoolMode::Length(..)) => HandleKey::<PhraseLengthCommand>
::match_input(self, from).map(PhrasePoolCommand::Length),
_ => None
}
}
}
}
impl HandleKey<PhraseRenameCommand> for PhrasePool<Tui> {
fn match_input (&self, from: &TuiInput) -> Option<PhraseRenameCommand> {
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<PhraseLengthCommand> for PhrasePool<Tui> {
fn match_input (&self, from: &TuiInput) -> Option<PhraseLengthCommand> {
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<PhraseEditorCommand> for PhraseEditor<Tui> {
fn match_input (&self, from: &TuiInput) -> Option<PhraseEditorCommand> {
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<Tui> for Sequencer<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = self.match_input(from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = HandleKey::<PhrasePoolCommand>::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl Handle<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = self.match_input(from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl<E: Engine> Command<Sequencer<E>> for SequencerCommand {
fn run (&self, state: &mut Sequencer<E>) -> Perhaps<Self> {
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)

View file

@ -10,6 +10,42 @@ impl Content for Sequencer<Tui> {
})
}
}
impl Handle<Tui> for Sequencer<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = SequencerCommand::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl MatchInput<Tui, Sequencer<Tui>> for SequencerCommand {
fn match_input (state: &Sequencer<Tui>, input: &TuiInput) -> Option<Self> {
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<Tui> {
type Engine = Tui;
@ -45,6 +81,96 @@ impl Content for PhrasePool<Tui> {
Layers::new(move|add|{ add(&content)?; Ok(add(&title)?) })
}
}
impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhrasePoolCommand::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl MatchInput<Tui, PhrasePool<Tui>> for PhrasePoolCommand {
fn match_input (state: &PhrasePool<Tui>, input: &TuiInput) -> Option<Self> {
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<Tui, PhrasePool<Tui>> for PhraseRenameCommand {
fn match_input (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
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<Tui, PhrasePool<Tui>> for PhraseLengthCommand {
fn match_input (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
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<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
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<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
@ -307,36 +433,36 @@ pub(crate) fn keys_vert () -> Buffer {
});
buffer
}
impl Content for PhraseLength<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
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<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhraseEditorCommand::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl MatchInput<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
fn match_input (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
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
}
}
}

View file

@ -1,15 +1,4 @@
use crate::*;
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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<TransportCommand> for TransportToolbar<Tui> {
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<E: Engine> Command<TransportToolbar<E>> for TransportCommand {
fn run (&self, state: &mut TransportToolbar<E>) -> Perhaps<Self> {
match self {

View file

@ -45,3 +45,27 @@ impl TransportToolbarFocus {
lay!(corners, highlight, *widget)
}
}
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = TransportCommand::match_input(self, from) {
let _undo = command.run(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl MatchInput<Tui, TransportToolbar<Tui>> for TransportCommand {
fn match_input (_: &TransportToolbar<Tui>, input: &TuiInput) -> Option<Self> {
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
}
}
}