cmdsys: separate match and handle phases

This commit is contained in:
🪞👃🪞 2024-11-07 02:13:43 +01:00
parent a4925082ca
commit dd72cea679
2 changed files with 130 additions and 126 deletions

View file

@ -25,25 +25,35 @@ pub const fn shift (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key } KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
} }
pub trait HandleKey<C: Command<Self> + 'static>: Sized { pub trait HandleKey<C: Command<Self> + Clone + 'static>: Sized {
const HANDLE_KEY_MAP: &'static [(KeyEvent, C)] = &[]; // FIXME: needs to be method const HANDLE_KEY_MAP: &'static [(KeyEvent, C)] = &[]; // FIXME: needs to be method
#[inline] fn match_input (from: &TuiInput) -> Option<&'static C> { #[inline] fn match_input_static (from: &TuiInput) -> Option<C> {
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() {
return Self::match_key_static(&key) return Self::match_key_static(&key)
} }
None None
} }
#[inline] fn match_key_static (key: &KeyEvent) -> Option<&'static C> { #[inline] fn match_key_static (key: &KeyEvent) -> Option<C> {
for (binding, command) in Self::HANDLE_KEY_MAP.iter() { for (binding, command) in Self::HANDLE_KEY_MAP.iter() {
if key == binding { if key == binding {
return Some(command); return Some(command.clone());
} }
} }
None None
} }
#[inline] fn match_key (&self, key: &KeyEvent) -> Option<&'static C> { #[inline] fn match_key (&self, key: &KeyEvent) -> Option<C> {
Self::match_key_static(key) 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> { #[inline] fn handle_key (&mut self, key: &KeyEvent) -> Perhaps<C> {
if let Some(command) = self.match_key(key) { if let Some(command) = self.match_key(key) {
command.run(self) command.run(self)

View file

@ -9,7 +9,7 @@ enum SequencerCommand {
FocusLeft, FocusLeft,
FocusRight, FocusRight,
Transport(TransportCommand), Transport(TransportCommand),
Phrase(PhrasePoolCommand), Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand), Editor(PhraseEditorCommand),
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -24,11 +24,11 @@ pub enum PhrasePoolCommand {
Duplicate, Duplicate,
RandomColor, RandomColor,
Edit, Edit,
Name(PhraseNameCommand), Rename(PhraseRenameCommand),
Length(PhraseLengthCommand), Length(PhraseLengthCommand),
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum PhraseNameCommand { pub enum PhraseRenameCommand {
Begin, Begin,
Backspace, Backspace,
Append(char), Append(char),
@ -67,84 +67,109 @@ pub enum PhraseEditorCommand {
GoRight, GoRight,
} }
impl HandleKey<SequencerCommand> for Sequencer<Tui> { impl HandleKey<SequencerCommand> for Sequencer<Tui> {
const HANDLE_KEY_MAP: &'static [(KeyEvent, SequencerCommand)] = &[ fn match_input (&self, from: &TuiInput) -> Option<SequencerCommand> {
(key(KeyCode::Tab), SequencerCommand::FocusNext), match from.event() {
(shift(key(KeyCode::Tab)), SequencerCommand::FocusPrev), key!(KeyCode::Tab) => Some(SequencerCommand::FocusNext),
(key(KeyCode::BackTab), SequencerCommand::FocusPrev), key!(Shift-KeyCode::Tab) => Some(SequencerCommand::FocusPrev),
(shift(key(KeyCode::BackTab)), SequencerCommand::FocusPrev), key!(KeyCode::BackTab) => Some(SequencerCommand::FocusPrev),
(key(KeyCode::Up), SequencerCommand::FocusUp), key!(Shift-KeyCode::BackTab) => Some(SequencerCommand::FocusPrev),
(key(KeyCode::Down), SequencerCommand::FocusDown), key!(KeyCode::Up) => Some(SequencerCommand::FocusUp),
(key(KeyCode::Left), SequencerCommand::FocusLeft), key!(KeyCode::Down) => Some(SequencerCommand::FocusDown),
(key(KeyCode::Right), SequencerCommand::FocusRight), key!(KeyCode::Left) => Some(SequencerCommand::FocusLeft),
(key(KeyCode::Char(' ')), SequencerCommand::Transport(TransportCommand::TogglePlay)), // FIXME go through transport 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> { impl HandleKey<PhrasePoolCommand> for PhrasePool<Tui> {
const HANDLE_KEY_MAP: &'static [(KeyEvent, PhrasePoolCommand)] = &[ fn match_input (&self, from: &TuiInput) -> Option<PhrasePoolCommand> {
(key(KeyCode::Up), PhrasePoolCommand::Previous), match from.event() {
(key(KeyCode::Down), PhrasePoolCommand::Next), key!(KeyCode::Up) => Some(PhrasePoolCommand::Previous),
(key(KeyCode::Char(',')), PhrasePoolCommand::MoveUp), key!(KeyCode::Down) => Some(PhrasePoolCommand::Next),
(key(KeyCode::Char('.')), PhrasePoolCommand::MoveDown), key!(KeyCode::Char(',')) => Some(PhrasePoolCommand::MoveUp),
(key(KeyCode::Delete), PhrasePoolCommand::Delete), key!(KeyCode::Char('.')) => Some(PhrasePoolCommand::MoveDown),
(key(KeyCode::Char('a')), PhrasePoolCommand::Append), key!(KeyCode::Delete) => Some(PhrasePoolCommand::Delete),
(key(KeyCode::Char('i')), PhrasePoolCommand::Insert), key!(KeyCode::Char('a')) => Some(PhrasePoolCommand::Append),
(key(KeyCode::Char('d')), PhrasePoolCommand::Duplicate), key!(KeyCode::Char('i')) => Some(PhrasePoolCommand::Insert),
(key(KeyCode::Char('c')), PhrasePoolCommand::RandomColor), key!(KeyCode::Char('d')) => Some(PhrasePoolCommand::Duplicate),
(key(KeyCode::Char('n')), PhrasePoolCommand::Name(PhraseNameCommand::Begin)), key!(KeyCode::Char('c')) => Some(PhrasePoolCommand::RandomColor),
(key(KeyCode::Char('t')), PhrasePoolCommand::Length(PhraseLengthCommand::Begin)), 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<PhraseNameCommand> for PhrasePool<Tui> { impl HandleKey<PhraseRenameCommand> for PhrasePool<Tui> {
const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseNameCommand)] = &[ fn match_input (&self, from: &TuiInput) -> Option<PhraseRenameCommand> {
(key(KeyCode::Backspace), PhraseNameCommand::Backspace), match from.event() {
(key(KeyCode::Enter), PhraseNameCommand::Confirm), key!(KeyCode::Backspace) => Some(PhraseRenameCommand::Backspace),
(key(KeyCode::Esc), PhraseNameCommand::Cancel), 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> { impl HandleKey<PhraseLengthCommand> for PhrasePool<Tui> {
const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseLengthCommand)] = &[ fn match_input (&self, from: &TuiInput) -> Option<PhraseLengthCommand> {
(key(KeyCode::Up), PhraseLengthCommand::Increment), match from.event() {
(key(KeyCode::Down), PhraseLengthCommand::Decrement), key!(KeyCode::Up) => Some(PhraseLengthCommand::Increment),
(key(KeyCode::Right), PhraseLengthCommand::Next), key!(KeyCode::Down) => Some(PhraseLengthCommand::Decrement),
(key(KeyCode::Left), PhraseLengthCommand::Previous), key!(KeyCode::Right) => Some(PhraseLengthCommand::Next),
(key(KeyCode::Enter), PhraseLengthCommand::Confirm), key!(KeyCode::Left) => Some(PhraseLengthCommand::Previous),
(key(KeyCode::Esc), PhraseLengthCommand::Cancel), key!(KeyCode::Enter) => Some(PhraseLengthCommand::Confirm),
]; key!(KeyCode::Esc) => Some(PhraseLengthCommand::Cancel),
_ => None
}
}
} }
impl HandleKey<PhraseEditorCommand> for PhraseEditor<Tui> { impl HandleKey<PhraseEditorCommand> for PhraseEditor<Tui> {
const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseEditorCommand)] = &[ fn match_input (&self, from: &TuiInput) -> Option<PhraseEditorCommand> {
(key(KeyCode::Char('`')), PhraseEditorCommand::ToggleDirection), match from.event() {
(key(KeyCode::Enter), PhraseEditorCommand::EnterEditMode), key!(KeyCode::Char('`')) => Some(PhraseEditorCommand::ToggleDirection),
(key(KeyCode::Esc), PhraseEditorCommand::ExitEditMode), key!(KeyCode::Enter) => Some(PhraseEditorCommand::EnterEditMode),
(key(KeyCode::Char('[')), PhraseEditorCommand::NoteLengthDecrement), key!(KeyCode::Esc) => Some(PhraseEditorCommand::ExitEditMode),
(key(KeyCode::Char(']')), PhraseEditorCommand::NoteLengthIncrement), key!(KeyCode::Char('[')) => Some(PhraseEditorCommand::NoteLengthDecrement),
(key(KeyCode::Char('a')), PhraseEditorCommand::NoteAppend), key!(KeyCode::Char(']')) => Some(PhraseEditorCommand::NoteLengthIncrement),
(key(KeyCode::Char('s')), PhraseEditorCommand::NoteSet), key!(KeyCode::Char('a')) => Some(PhraseEditorCommand::NoteAppend),
(key(KeyCode::Char('-')), PhraseEditorCommand::TimeZoomOut), key!(KeyCode::Char('s')) => Some(PhraseEditorCommand::NoteSet),
(key(KeyCode::Char('_')), PhraseEditorCommand::TimeZoomOut), key!(KeyCode::Char('-')) => Some(PhraseEditorCommand::TimeZoomOut),
(key(KeyCode::Char('=')), PhraseEditorCommand::TimeZoomIn), key!(KeyCode::Char('_')) => Some(PhraseEditorCommand::TimeZoomOut),
(key(KeyCode::Char('+')), PhraseEditorCommand::TimeZoomIn), key!(KeyCode::Char('=')) => Some(PhraseEditorCommand::TimeZoomIn),
(key(KeyCode::PageUp), PhraseEditorCommand::NotePageUp), key!(KeyCode::Char('+')) => Some(PhraseEditorCommand::TimeZoomIn),
(key(KeyCode::PageDown), PhraseEditorCommand::NotePageDown), key!(KeyCode::PageUp) => Some(PhraseEditorCommand::NotePageUp),
(key(KeyCode::Up), PhraseEditorCommand::GoUp), key!(KeyCode::PageDown) => Some(PhraseEditorCommand::NotePageDown),
(key(KeyCode::Down), PhraseEditorCommand::GoDown), key!(KeyCode::Up) => Some(PhraseEditorCommand::GoUp),
(key(KeyCode::Left), PhraseEditorCommand::GoLeft), key!(KeyCode::Down) => Some(PhraseEditorCommand::GoDown),
(key(KeyCode::Right), PhraseEditorCommand::GoRight), key!(KeyCode::Left) => Some(PhraseEditorCommand::GoLeft),
]; key!(KeyCode::Right) => Some(PhraseEditorCommand::GoRight),
_ => None
}
}
} }
/// Handle top-level events in standalone sequencer. /// Handle top-level events in standalone sequencer.
impl Handle<Tui> for Sequencer<Tui> { impl Handle<Tui> for Sequencer<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
let handled = match self.focused() { if let Some(command) = self.match_input(from) {
SequencerFocus::Transport => self.transport.handle(from)?, let _undo = command.run(self)?;
SequencerFocus::PhrasePool => self.phrases.handle(from)?,
SequencerFocus::PhraseEditor => self.editor.handle(from)?
}.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)) return Ok(Some(true))
} }
Ok(None) Ok(None)
@ -152,34 +177,17 @@ impl Handle<Tui> for Sequencer<Tui> {
} }
impl Handle<Tui> for PhrasePool<Tui> { impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { if let Some(command) = HandleKey::<PhrasePoolCommand>::match_input(self, from) {
match self.mode { let _undo = command.run(self)?;
Some(PhrasePoolMode::Rename(..)) => { return Ok(Some(true))
if HandleKey::<PhraseNameCommand>::match_key_static(key).is_some() {
let _undo = HandleKey::<PhraseNameCommand>::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::<PhraseLengthCommand>::handle_key(self, key)?;
return Ok(Some(true))
},
None => {
let _undo = HandleKey::<PhrasePoolCommand>::handle_key(self, key)?;
return Ok(Some(true))
}
}
} }
Ok(None) Ok(None)
} }
} }
impl Handle<Tui> for PhraseEditor<Tui> { impl Handle<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() { if let Some(command) = self.match_input(from) {
let _undo = self.handle_key(key)?; let _undo = command.run(self)?;
return Ok(Some(true)) return Ok(Some(true))
} }
Ok(None) Ok(None)
@ -188,35 +196,21 @@ impl Handle<Tui> for PhraseEditor<Tui> {
impl<E: Engine> Command<Sequencer<E>> for SequencerCommand { impl<E: Engine> Command<Sequencer<E>> for SequencerCommand {
fn run (&self, state: &mut Sequencer<E>) -> Perhaps<Self> { fn run (&self, state: &mut Sequencer<E>) -> Perhaps<Self> {
match self { match self {
Self::FocusNext => { Self::FocusNext => { state.focus_next(); },
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::FocusPrev => { Self::Phrases(command) => {
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 return command
.run(&mut*state.phrases.write().unwrap()) .run(&mut*state.phrases.write().unwrap())
.map(|x|x.map(SequencerCommand::Phrase)) .map(|x|x.map(SequencerCommand::Phrases))
}, },
Self::Editor(command) => { Self::Editor(command) => {
return command return command
@ -260,10 +254,10 @@ impl<E: Engine> Command<PhrasePool<E>> for PhrasePoolCommand {
Self::MoveDown => { Self::MoveDown => {
state.move_down() state.move_down()
}, },
Self::Name(PhraseNameCommand::Begin) => { Self::Rename(PhraseRenameCommand::Begin) => {
state.begin_rename() state.begin_rename()
}, },
Self::Name(_) => { Self::Rename(_) => {
unreachable!() unreachable!()
}, },
Self::Length(PhraseLengthCommand::Begin) => { Self::Length(PhraseLengthCommand::Begin) => {
@ -276,7 +270,7 @@ impl<E: Engine> Command<PhrasePool<E>> for PhrasePoolCommand {
Ok(None) Ok(None)
} }
} }
impl<E: Engine> Command<PhrasePool<E>> for PhraseNameCommand { impl<E: Engine> Command<PhrasePool<E>> for PhraseRenameCommand {
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> { fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = state.mode { if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = state.mode {
match self { match self {