mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: command system: then for sequencer
This commit is contained in:
parent
524e283075
commit
abbe0dc8f7
3 changed files with 378 additions and 111 deletions
|
|
@ -27,15 +27,16 @@ pub trait Command<T>: Sized {
|
||||||
|
|
||||||
pub trait HandleKey<C: Command<Self> + 'static>: Sized {
|
pub trait HandleKey<C: Command<Self> + 'static>: Sized {
|
||||||
const HANDLE_KEY_MAP: &'static [(KeyEvent, C)];
|
const HANDLE_KEY_MAP: &'static [(KeyEvent, C)];
|
||||||
fn handle_key (&mut self, key: &KeyEvent) -> Perhaps<C> {
|
fn match_key (key: &KeyEvent) -> Option<&'static C> {
|
||||||
let mut run_command: Option<&'static C> = None;
|
|
||||||
for (binding, command) in Self::HANDLE_KEY_MAP.iter() {
|
for (binding, command) in Self::HANDLE_KEY_MAP.iter() {
|
||||||
if key == binding {
|
if key == binding {
|
||||||
run_command = Some(command);
|
return Some(command);
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(command) = run_command {
|
None
|
||||||
|
}
|
||||||
|
fn handle_key (&mut self, key: &KeyEvent) -> Perhaps<C> {
|
||||||
|
if let Some(command) = Self::match_key(key) {
|
||||||
command.run(self)
|
command.run(self)
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
enum SequencerCommand {
|
enum SequencerCommand {
|
||||||
FocusNext,
|
FocusNext,
|
||||||
FocusPrev,
|
FocusPrev,
|
||||||
|
|
@ -10,132 +11,397 @@ enum SequencerCommand {
|
||||||
Transport(TransportCommand),
|
Transport(TransportCommand),
|
||||||
Phrase(PhrasePoolCommand),
|
Phrase(PhrasePoolCommand),
|
||||||
Editor(PhraseEditorCommand),
|
Editor(PhraseEditorCommand),
|
||||||
|
// TODO: 1-8 seek markers that by default start every 8th of the phrase
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
enum PhrasePoolCommand {
|
pub enum PhrasePoolCommand {
|
||||||
|
Previous,
|
||||||
|
Next,
|
||||||
|
MoveUp,
|
||||||
|
MoveDown,
|
||||||
|
Delete,
|
||||||
|
Append,
|
||||||
|
Insert,
|
||||||
|
Duplicate,
|
||||||
|
RandomColor,
|
||||||
|
Edit,
|
||||||
|
Name(PhraseNameCommand),
|
||||||
|
Length(PhraseLengthCommand),
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
enum PhraseLengthCommand {
|
pub enum PhraseNameCommand {
|
||||||
|
Begin,
|
||||||
|
Backspace,
|
||||||
|
Append(char),
|
||||||
|
Set(String),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
enum PhraseEditorCommand {
|
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<SequencerCommand> for Sequencer<Tui> {
|
||||||
|
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<PhrasePoolCommand> for PhrasePool<Tui> {
|
||||||
|
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<PhraseNameCommand> for PhrasePool<Tui> {
|
||||||
|
const HANDLE_KEY_MAP: &'static [(KeyEvent, PhraseNameCommand)] = &[
|
||||||
|
(key(KeyCode::Backspace), PhraseNameCommand::Backspace),
|
||||||
|
(key(KeyCode::Enter), PhraseNameCommand::Confirm),
|
||||||
|
(key(KeyCode::Esc), PhraseNameCommand::Cancel),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
impl HandleKey<PhraseLengthCommand> for PhrasePool<Tui> {
|
||||||
|
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<PhraseEditorCommand> for PhraseEditor<Tui> {
|
||||||
|
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.
|
/// 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> {
|
||||||
if !match self.focused() {
|
let handled = match self.focused() {
|
||||||
SequencerFocus::Transport => self.transport.handle(from)?,
|
SequencerFocus::Transport => self.transport.handle(from)?,
|
||||||
SequencerFocus::PhrasePool => self.phrases.handle(from)?,
|
SequencerFocus::PhrasePool => self.phrases.handle(from)?,
|
||||||
SequencerFocus::PhraseEditor => self.editor.handle(from)?
|
SequencerFocus::PhraseEditor => self.editor.handle(from)?
|
||||||
}.unwrap_or(false) {
|
}.unwrap_or(false);
|
||||||
match from.event() {
|
if handled {
|
||||||
// Tab navigation
|
return Ok(Some(true))
|
||||||
key!(KeyCode::Tab) => { self.focus_next(); },
|
}
|
||||||
key!(Shift-KeyCode::Tab) => { self.focus_prev(); },
|
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() {
|
||||||
key!(KeyCode::BackTab) => { self.focus_prev(); },
|
let _undo = self.handle_key(key)?;
|
||||||
key!(Shift-KeyCode::BackTab) => { self.focus_prev(); },
|
return Ok(Some(true))
|
||||||
// Directional navigation
|
}
|
||||||
key!(KeyCode::Up) => { self.focus_up(); },
|
Ok(None)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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> {
|
||||||
match self.mode {
|
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() {
|
||||||
Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) => {
|
match self.mode {
|
||||||
let mut phrase = self.phrases[phrase].write().unwrap();
|
Some(PhrasePoolMode::Rename(..)) => {
|
||||||
match from.event() {
|
if HandleKey::<PhraseNameCommand>::match_key(key).is_some() {
|
||||||
key!(KeyCode::Backspace) => { phrase.name.pop(); },
|
let _undo = HandleKey::<PhraseNameCommand>::handle_key(self, key)?;
|
||||||
key!(KeyCode::Char(c)) => { phrase.name.push(*c); },
|
return Ok(Some(true))
|
||||||
key!(Shift-KeyCode::Char(c)) => { phrase.name.push(*c); },
|
} else if let KeyEvent { code: KeyCode::Char(c), .. } = key {
|
||||||
key!(KeyCode::Esc) => { phrase.name = old_name.clone(); self.mode = None; },
|
PhraseNameCommand::Append(*c).run(self)?;
|
||||||
key!(KeyCode::Enter) => { self.mode = None; },
|
return Ok(Some(true))
|
||||||
_ => 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))
|
||||||
}
|
}
|
||||||
},
|
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(None)
|
||||||
return Ok(Some(true))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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> {
|
||||||
match from.event() {
|
if let TuiEvent::Input(crossterm::event::Event::Key(key)) = from.event() {
|
||||||
key!(KeyCode::Char('`')) => { self.mode = !self.mode; },
|
let _undo = self.handle_key(key)?;
|
||||||
key!(KeyCode::Enter) => { self.entered = true; },
|
return Ok(Some(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) }
|
|
||||||
}
|
}
|
||||||
return Ok(Some(true))
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E: Engine> Command<Sequencer<E>> for SequencerCommand {
|
||||||
|
fn run (&self, state: &mut Sequencer<E>) -> Perhaps<Self> {
|
||||||
|
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<E: Engine> Command<PhrasePool<E>> for PhrasePoolCommand {
|
||||||
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
||||||
|
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<E: Engine> Command<PhrasePool<E>> for PhraseNameCommand {
|
||||||
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
||||||
|
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<E: Engine> Command<PhrasePool<E>> for PhraseLengthCommand {
|
||||||
|
fn run (&self, state: &mut PhrasePool<E>) -> Perhaps<Self> {
|
||||||
|
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<E: Engine> Command<PhraseEditor<E>> for PhraseEditorCommand {
|
||||||
|
fn run (&self, state: &mut PhraseEditor<E>) -> Perhaps<Self> {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ impl Handle<Tui> for TransportToolbar<Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum TransportCommand {
|
pub enum TransportCommand {
|
||||||
FocusNext,
|
FocusNext,
|
||||||
FocusPrev,
|
FocusPrev,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue