mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
split key macro into key_pat and key_expr
This commit is contained in:
parent
d003af85ca
commit
29abe29504
13 changed files with 550 additions and 551 deletions
|
|
@ -1,5 +1,3 @@
|
|||
use crate::*;
|
||||
|
||||
mod phrase; pub(crate) use phrase::*;
|
||||
mod jack; pub(crate) use self::jack::*;
|
||||
mod clip; pub(crate) use clip::*;
|
||||
|
|
|
|||
|
|
@ -259,16 +259,16 @@ pub trait FocusWrap<T> {
|
|||
pub fn to_focus_command <T: Send + Sync> (input: &TuiInput) -> Option<FocusCommand<T>> {
|
||||
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
|
||||
Some(match input.event() {
|
||||
key!(Tab) => FocusCommand::Next,
|
||||
key!(Shift-Tab) => FocusCommand::Prev,
|
||||
key!(BackTab) => FocusCommand::Prev,
|
||||
key!(Shift-BackTab) => FocusCommand::Prev,
|
||||
key!(Up) => FocusCommand::Up,
|
||||
key!(Down) => FocusCommand::Down,
|
||||
key!(Left) => FocusCommand::Left,
|
||||
key!(Right) => FocusCommand::Right,
|
||||
key!(Enter) => FocusCommand::Enter,
|
||||
key!(Esc) => FocusCommand::Exit,
|
||||
key_pat!(Tab) => FocusCommand::Next,
|
||||
key_pat!(Shift-Tab) => FocusCommand::Prev,
|
||||
key_pat!(BackTab) => FocusCommand::Prev,
|
||||
key_pat!(Shift-BackTab) => FocusCommand::Prev,
|
||||
key_pat!(Up) => FocusCommand::Up,
|
||||
key_pat!(Down) => FocusCommand::Down,
|
||||
key_pat!(Left) => FocusCommand::Left,
|
||||
key_pat!(Right) => FocusCommand::Right,
|
||||
key_pat!(Enter) => FocusCommand::Enter,
|
||||
key_pat!(Esc) => FocusCommand::Exit,
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use crate::*;
|
||||
|
||||
pub fn to_note_name (n: usize) -> &'static str {
|
||||
if n > 127 {
|
||||
panic!("to_note_name({n}): must be 0-127");
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ impl Tui {
|
|||
if ::crossterm::event::poll(poll).is_ok() {
|
||||
let event = TuiEvent::Input(::crossterm::event::read().unwrap());
|
||||
match event {
|
||||
key!(Ctrl-KeyCode::Char('c')) => {
|
||||
key_pat!(Ctrl-KeyCode::Char('c')) => {
|
||||
exited.store(true, Ordering::Relaxed);
|
||||
},
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
use crate::{
|
||||
*,
|
||||
api::{
|
||||
ArrangerTrackCommand,
|
||||
ArrangerSceneCommand,
|
||||
ArrangerClipCommand
|
||||
}
|
||||
};
|
||||
use crate::*;
|
||||
use crate::api::ArrangerTrackCommand;
|
||||
use crate::api::ArrangerSceneCommand;
|
||||
use crate::api::ArrangerClipCommand;
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
|
@ -61,6 +57,273 @@ pub struct ArrangerTui {
|
|||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
impl Handle<Tui> for ArrangerTui {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
ArrangerCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerCommand {
|
||||
Focus(FocusCommand<ArrangerFocus>),
|
||||
Undo,
|
||||
Redo,
|
||||
Clear,
|
||||
Color(ItemColor),
|
||||
Clock(ClockCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
Track(ArrangerTrackCommand),
|
||||
Clip(ArrangerClipCommand),
|
||||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Phrases(PhrasesCommand),
|
||||
Editor(PhraseCommand),
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
use ArrangerCommand::*;
|
||||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Scene(cmd) => cmd.execute(state)?.map(Scene),
|
||||
Track(cmd) => cmd.execute(state)?.map(Track),
|
||||
Clip(cmd) => cmd.execute(state)?.map(Clip),
|
||||
Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(&mut state.editor)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Zoom(_) => { todo!(); },
|
||||
Select(selected) => {
|
||||
*state.selected_mut() = selected;
|
||||
None
|
||||
},
|
||||
_ => { todo!() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerClipCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArrangerControl: TransportControl<ArrangerFocus> {
|
||||
fn selected (&self) -> ArrangerSelection;
|
||||
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||
fn activate (&mut self) -> Usually<()>;
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||
fn toggle_loop (&mut self);
|
||||
fn randomize_color (&mut self);
|
||||
}
|
||||
|
||||
impl ArrangerControl for ArrangerTui {
|
||||
fn selected (&self) -> ArrangerSelection {
|
||||
self.selected
|
||||
}
|
||||
fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
||||
&mut self.selected
|
||||
}
|
||||
fn activate (&mut self) -> Usually<()> {
|
||||
if let ArrangerSelection::Scene(s) = self.selected {
|
||||
for (t, track) in self.tracks.iter_mut().enumerate() {
|
||||
let phrase = self.scenes[s].clips[t].clone();
|
||||
if track.player.play_phrase.is_some() || phrase.is_some() {
|
||||
track.player.enqueue_next(phrase.as_ref());
|
||||
}
|
||||
}
|
||||
if self.clock().is_stopped() {
|
||||
self.clock().play_from(Some(0))?;
|
||||
}
|
||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||
let phrase = self.scenes()[s].clips[t].clone();
|
||||
self.tracks_mut()[t].player.enqueue_next(phrase.as_ref());
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||
}
|
||||
fn toggle_loop (&mut self) {
|
||||
if let Some(phrase) = self.selected_phrase() {
|
||||
phrase.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
fn randomize_color (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerSelection::Mix => {
|
||||
self.color = ItemColor::random_dark()
|
||||
},
|
||||
ArrangerSelection::Track(t) => {
|
||||
self.tracks_mut()[t].color = ItemColor::random()
|
||||
},
|
||||
ArrangerSelection::Scene(s) => {
|
||||
self.scenes_mut()[s].color = ItemColor::random()
|
||||
},
|
||||
ArrangerSelection::Clip(t, s) => {
|
||||
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
||||
phrase.write().unwrap().color = ItemPalette::random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
|
||||
fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option<Self> {
|
||||
to_arranger_command(state, input)
|
||||
.or_else(||to_focus_command(input).map(ArrangerCommand::Focus))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use ArrangerCommand as Cmd;
|
||||
use KeyCode::Char;
|
||||
if !state.entered() {
|
||||
return None
|
||||
}
|
||||
Some(match input.event() {
|
||||
key_pat!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(
|
||||
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
|
||||
))),
|
||||
// WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor
|
||||
_ => match state.focused() {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
match to_transport_command(state, input)? {
|
||||
TransportCommand::Clock(command) => Cmd::Clock(command),
|
||||
_ => return None,
|
||||
}
|
||||
},
|
||||
ArrangerFocus::PhraseEditor => {
|
||||
Cmd::Editor(PhraseCommand::input_to_command(&state.editor, input)?)
|
||||
},
|
||||
ArrangerFocus::Phrases => {
|
||||
Cmd::Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?)
|
||||
},
|
||||
ArrangerFocus::Arranger => {
|
||||
use ArrangerSelection::*;
|
||||
match input.event() {
|
||||
key_pat!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key_pat!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||
key_pat!(Char('`')) => { todo!("toggle state mode") },
|
||||
key_pat!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
key_pat!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
_ => match state.selected() {
|
||||
Mix => to_arranger_mix_command(input)?,
|
||||
Track(t) => to_arranger_track_command(input, t)?,
|
||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
Some(match input.event() {
|
||||
key_pat!(Down) => Cmd::Select(Select::Scene(0)),
|
||||
key_pat!(Right) => Cmd::Select(Select::Track(0)),
|
||||
key_pat!(Char(',')) => Cmd::Zoom(0),
|
||||
key_pat!(Char('.')) => Cmd::Zoom(0),
|
||||
key_pat!(Char('<')) => Cmd::Zoom(0),
|
||||
key_pat!(Char('>')) => Cmd::Zoom(0),
|
||||
key_pat!(Delete) => Cmd::Clear,
|
||||
key_pat!(Char('c')) => Cmd::Color(ItemColor::random()),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Left, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerTrackCommand as Track;
|
||||
Some(match input.event() {
|
||||
key_pat!(Down) => Cmd::Select(Select::Clip(t, 0)),
|
||||
key_pat!(Left) => Cmd::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }),
|
||||
key_pat!(Right) => Cmd::Select(Select::Track(t + 1)),
|
||||
key_pat!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key_pat!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key_pat!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key_pat!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key_pat!(Delete) => Cmd::Track(Track::Delete(t)),
|
||||
//key_pat!(Char('c')) => Cmd::Track(Track::Color(t, ItemColor::random())),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Up, Down, Right, Enter, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
Some(match input.event() {
|
||||
key_pat!(Up) => Cmd::Select(if s > 0 { Select::Scene(s - 1) } else { Select::Mix }),
|
||||
key_pat!(Down) => Cmd::Select(Select::Scene(s + 1)),
|
||||
key_pat!(Right) => Cmd::Select(Select::Clip(0, s)),
|
||||
key_pat!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||
key_pat!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
||||
key_pat!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||
key_pat!(Char('>')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
||||
key_pat!(Enter) => Cmd::Scene(Scene::Play(s)),
|
||||
key_pat!(Delete) => Cmd::Scene(Scene::Delete(s)),
|
||||
//key_pat!(Char('c')) => Cmd::Track(Scene::Color(s, ItemColor::random())),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Up, Down, Left, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerClipCommand as Clip;
|
||||
Some(match input.event() {
|
||||
key_pat!(Up) => Cmd::Select(if s > 0 { Select::Clip(t, s - 1) } else { Select::Track(t) }),
|
||||
key_pat!(Down) => Cmd::Select(Select::Clip(t, s + 1)),
|
||||
key_pat!(Left) => Cmd::Select(if t > 0 { Select::Clip(t - 1, s) } else { Select::Scene(s) }),
|
||||
key_pat!(Right) => Cmd::Select(Select::Clip(t + 1, s)),
|
||||
key_pat!(Char(',')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key_pat!(Char('.')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key_pat!(Char('<')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key_pat!(Char('>')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key_pat!(Delete) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
//key_pat!(Char('c')) => Cmd::Clip(Clip::Color(t, s, ItemColor::random())),
|
||||
//key_pat!(Char('g')) => Cmd::Clip(Clip(Clip::Get(t, s))),
|
||||
//key_pat!(Char('s')) => Cmd::Clip(Clip(Clip::Set(t, s))),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
impl TransportControl<ArrangerFocus> for ArrangerTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
match self.focus.inner() {
|
||||
ArrangerFocus::Transport(focus) => Some(focus),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for ArrangerTui {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
// Start profiling cycle
|
||||
|
|
@ -1084,270 +1347,3 @@ impl ArrangerSelection {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle<Tui> for ArrangerTui {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
ArrangerCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerCommand {
|
||||
Focus(FocusCommand<ArrangerFocus>),
|
||||
Undo,
|
||||
Redo,
|
||||
Clear,
|
||||
Color(ItemColor),
|
||||
Clock(ClockCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
Track(ArrangerTrackCommand),
|
||||
Clip(ArrangerClipCommand),
|
||||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Phrases(PhrasesCommand),
|
||||
Editor(PhraseCommand),
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
use ArrangerCommand::*;
|
||||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Scene(cmd) => cmd.execute(state)?.map(Scene),
|
||||
Track(cmd) => cmd.execute(state)?.map(Track),
|
||||
Clip(cmd) => cmd.execute(state)?.map(Clip),
|
||||
Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(&mut state.editor)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Zoom(_) => { todo!(); },
|
||||
Select(selected) => {
|
||||
*state.selected_mut() = selected;
|
||||
None
|
||||
},
|
||||
_ => { todo!() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerClipCommand {
|
||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
//todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArrangerControl: TransportControl<ArrangerFocus> {
|
||||
fn selected (&self) -> ArrangerSelection;
|
||||
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||
fn activate (&mut self) -> Usually<()>;
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||
fn toggle_loop (&mut self);
|
||||
fn randomize_color (&mut self);
|
||||
}
|
||||
|
||||
impl ArrangerControl for ArrangerTui {
|
||||
fn selected (&self) -> ArrangerSelection {
|
||||
self.selected
|
||||
}
|
||||
fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
||||
&mut self.selected
|
||||
}
|
||||
fn activate (&mut self) -> Usually<()> {
|
||||
if let ArrangerSelection::Scene(s) = self.selected {
|
||||
for (t, track) in self.tracks.iter_mut().enumerate() {
|
||||
let phrase = self.scenes[s].clips[t].clone();
|
||||
if track.player.play_phrase.is_some() || phrase.is_some() {
|
||||
track.player.enqueue_next(phrase.as_ref());
|
||||
}
|
||||
}
|
||||
if self.clock().is_stopped() {
|
||||
self.clock().play_from(Some(0))?;
|
||||
}
|
||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||
let phrase = self.scenes()[s].clips[t].clone();
|
||||
self.tracks_mut()[t].player.enqueue_next(phrase.as_ref());
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||
}
|
||||
fn toggle_loop (&mut self) {
|
||||
if let Some(phrase) = self.selected_phrase() {
|
||||
phrase.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
fn randomize_color (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerSelection::Mix => {
|
||||
self.color = ItemColor::random_dark()
|
||||
},
|
||||
ArrangerSelection::Track(t) => {
|
||||
self.tracks_mut()[t].color = ItemColor::random()
|
||||
},
|
||||
ArrangerSelection::Scene(s) => {
|
||||
self.scenes_mut()[s].color = ItemColor::random()
|
||||
},
|
||||
ArrangerSelection::Clip(t, s) => {
|
||||
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
||||
phrase.write().unwrap().color = ItemPalette::random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
|
||||
fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option<Self> {
|
||||
to_arranger_command(state, input)
|
||||
.or_else(||to_focus_command(input).map(ArrangerCommand::Focus))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use ArrangerCommand as Cmd;
|
||||
use KeyCode::Char;
|
||||
if !state.entered() {
|
||||
return None
|
||||
}
|
||||
Some(match input.event() {
|
||||
key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(
|
||||
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
|
||||
))),
|
||||
// WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor
|
||||
_ => match state.focused() {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
match to_transport_command(state, input)? {
|
||||
TransportCommand::Clock(command) => Cmd::Clock(command),
|
||||
_ => return None,
|
||||
}
|
||||
},
|
||||
ArrangerFocus::PhraseEditor => {
|
||||
Cmd::Editor(PhraseCommand::input_to_command(&state.editor, input)?)
|
||||
},
|
||||
ArrangerFocus::Phrases => {
|
||||
Cmd::Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?)
|
||||
},
|
||||
ArrangerFocus::Arranger => {
|
||||
use ArrangerSelection::*;
|
||||
match input.event() {
|
||||
key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('`')) => { todo!("toggle state mode") },
|
||||
key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
_ => match state.selected() {
|
||||
Mix => to_arranger_mix_command(input)?,
|
||||
Track(t) => to_arranger_track_command(input, t)?,
|
||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
Some(match input.event() {
|
||||
key!(Down) => Cmd::Select(Select::Scene(0)),
|
||||
key!(Right) => Cmd::Select(Select::Track(0)),
|
||||
key!(Char(',')) => Cmd::Zoom(0),
|
||||
key!(Char('.')) => Cmd::Zoom(0),
|
||||
key!(Char('<')) => Cmd::Zoom(0),
|
||||
key!(Char('>')) => Cmd::Zoom(0),
|
||||
key!(Delete) => Cmd::Clear,
|
||||
key!(Char('c')) => Cmd::Color(ItemColor::random()),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Down, Left, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerTrackCommand as Track;
|
||||
Some(match input.event() {
|
||||
key!(Down) => Cmd::Select(Select::Clip(t, 0)),
|
||||
key!(Left) => Cmd::Select(if t > 0 { Select::Track(t - 1) } else { Select::Mix }),
|
||||
key!(Right) => Cmd::Select(Select::Track(t + 1)),
|
||||
key!(Char(',')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key!(Char('.')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key!(Char('<')) => Cmd::Track(Track::Swap(t, t - 1)),
|
||||
key!(Char('>')) => Cmd::Track(Track::Swap(t, t + 1)),
|
||||
key!(Delete) => Cmd::Track(Track::Delete(t)),
|
||||
//key!(Char('c')) => Cmd::Track(Track::Color(t, ItemColor::random())),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Up, Down, Right, Enter, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
Some(match input.event() {
|
||||
key!(Up) => Cmd::Select(if s > 0 { Select::Scene(s - 1) } else { Select::Mix }),
|
||||
key!(Down) => Cmd::Select(Select::Scene(s + 1)),
|
||||
key!(Right) => Cmd::Select(Select::Clip(0, s)),
|
||||
key!(Char(',')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||
key!(Char('.')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
||||
key!(Char('<')) => Cmd::Scene(Scene::Swap(s, s - 1)),
|
||||
key!(Char('>')) => Cmd::Scene(Scene::Swap(s, s + 1)),
|
||||
key!(Enter) => Cmd::Scene(Scene::Play(s)),
|
||||
key!(Delete) => Cmd::Scene(Scene::Delete(s)),
|
||||
//key!(Char('c')) => Cmd::Track(Scene::Color(s, ItemColor::random())),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||
use KeyCode::{Char, Up, Down, Left, Right, Delete};
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Select;
|
||||
use ArrangerClipCommand as Clip;
|
||||
Some(match input.event() {
|
||||
key!(Up) => Cmd::Select(if s > 0 { Select::Clip(t, s - 1) } else { Select::Track(t) }),
|
||||
key!(Down) => Cmd::Select(Select::Clip(t, s + 1)),
|
||||
key!(Left) => Cmd::Select(if t > 0 { Select::Clip(t - 1, s) } else { Select::Scene(s) }),
|
||||
key!(Right) => Cmd::Select(Select::Clip(t + 1, s)),
|
||||
key!(Char(',')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key!(Char('.')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key!(Char('<')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key!(Char('>')) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
key!(Delete) => Cmd::Clip(Clip::Set(t, s, None)),
|
||||
//key!(Char('c')) => Cmd::Clip(Clip::Color(t, s, ItemColor::random())),
|
||||
//key!(Char('g')) => Cmd::Clip(Clip(Clip::Get(t, s))),
|
||||
//key!(Char('s')) => Cmd::Clip(Clip(Clip::Set(t, s))),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
impl TransportControl<ArrangerFocus> for ArrangerTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
match self.focus.inner() {
|
||||
ArrangerFocus::Transport(focus) => Some(focus),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,26 +232,26 @@ impl Handle<Tui> for SamplerTui {
|
|||
let mapped = &self.state.mapped;
|
||||
let voices = &self.state.voices;
|
||||
match from.event() {
|
||||
key!(KeyCode::Up) => cursor.0 = if cursor.0 == 0 {
|
||||
key_pat!(KeyCode::Up) => cursor.0 = if cursor.0 == 0 {
|
||||
mapped.len() + unmapped.len() - 1
|
||||
} else {
|
||||
cursor.0 - 1
|
||||
},
|
||||
key!(KeyCode::Down) => {
|
||||
key_pat!(KeyCode::Down) => {
|
||||
cursor.0 = (cursor.0 + 1) % (mapped.len() + unmapped.len());
|
||||
},
|
||||
key!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
||||
key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
||||
voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
|
||||
},
|
||||
key!(KeyCode::Char('a')) => {
|
||||
key_pat!(KeyCode::Char('a')) => {
|
||||
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
unmapped.push(sample);
|
||||
},
|
||||
key!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
|
||||
key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
|
||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||
},
|
||||
key!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||
key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||
self.editing = Some(sample.clone());
|
||||
},
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -126,21 +126,21 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
|||
Some(match input.event() {
|
||||
|
||||
// Switch between editor and list
|
||||
key!(Tab) | key!(BackTab) | key!(Shift-Tab) | key!(Shift-BackTab) => match state.focus {
|
||||
key_pat!(Tab) | key_pat!(BackTab) | key_pat!(Shift-Tab) | key_pat!(Shift-BackTab) => match state.focus {
|
||||
PhraseEditor => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
|
||||
_ => SequencerCommand::Focus(FocusCommand::Set(PhraseEditor)),
|
||||
}
|
||||
|
||||
// Enqueue currently edited phrase
|
||||
key!(Char('q')) =>
|
||||
key_pat!(Char('q')) =>
|
||||
Enqueue(Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())),
|
||||
|
||||
// 0: Enqueue phrase 0 (stop all)
|
||||
key!(Char('0')) =>
|
||||
key_pat!(Char('0')) =>
|
||||
Enqueue(Some(state.phrases.phrases[0].clone())),
|
||||
|
||||
// E: Toggle between editing currently playing or other phrase
|
||||
key!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_phrase() {
|
||||
key_pat!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_phrase() {
|
||||
let editing_phrase = state.editor.phrase()
|
||||
.read().unwrap().as_ref()
|
||||
.map(|p|p.read().unwrap().clone());
|
||||
|
|
@ -155,19 +155,19 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
|||
},
|
||||
|
||||
// Transport: Play/pause
|
||||
key!(Char(' ')) =>
|
||||
key_pat!(Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
|
||||
// Transport: Play from start or rewind to start
|
||||
key!(Shift-Char(' ')) =>
|
||||
key_pat!(Shift-Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
|
||||
// Editor: zoom
|
||||
key!(Char('z')) | key!(Char('-')) | key!(Char('_'))| key!(Char('=')) | key!(Char('+')) =>
|
||||
key_pat!(Char('z')) | key_pat!(Char('-')) | key_pat!(Char('_'))| key_pat!(Char('=')) | key_pat!(Char('+')) =>
|
||||
Editor(PhraseCommand::input_to_command(&state.editor, input)?),
|
||||
|
||||
// List: select phrase to edit, change color
|
||||
key!(Char('[')) | key!(Char(']')) | key!(Char('c')) | key!(Shift-Char('A')) | key!(Shift-Char('D')) =>
|
||||
key_pat!(Char('[')) | key_pat!(Char(']')) | key_pat!(Char('c')) | key_pat!(Shift-Char('A')) | key_pat!(Shift-Char('D')) =>
|
||||
Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?),
|
||||
|
||||
// Delegate to focused control:
|
||||
|
|
|
|||
|
|
@ -265,56 +265,56 @@ where
|
|||
U: Into<Option<TransportFocus>>,
|
||||
{
|
||||
Some(match input.event() {
|
||||
key!(Left) => Focus(Prev),
|
||||
key!(Right) => Focus(Next),
|
||||
key!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
key_pat!(Left) => Focus(Prev),
|
||||
key_pat!(Right) => Focus(Next),
|
||||
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
Play(None)
|
||||
} else {
|
||||
Pause(None)
|
||||
}),
|
||||
key!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
key_pat!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
Play(Some(0))
|
||||
} else {
|
||||
Pause(Some(0))
|
||||
}),
|
||||
_ => match state.transport_focused().unwrap() {
|
||||
TransportFocus::Bpm => match input.event() {
|
||||
key!(Char(',')) => Clock(SetBpm(state.clock().bpm().get() - 1.0)),
|
||||
key!(Char('.')) => Clock(SetBpm(state.clock().bpm().get() + 1.0)),
|
||||
key!(Char('<')) => Clock(SetBpm(state.clock().bpm().get() - 0.001)),
|
||||
key!(Char('>')) => Clock(SetBpm(state.clock().bpm().get() + 0.001)),
|
||||
key_pat!(Char(',')) => Clock(SetBpm(state.clock().bpm().get() - 1.0)),
|
||||
key_pat!(Char('.')) => Clock(SetBpm(state.clock().bpm().get() + 1.0)),
|
||||
key_pat!(Char('<')) => Clock(SetBpm(state.clock().bpm().get() - 0.001)),
|
||||
key_pat!(Char('>')) => Clock(SetBpm(state.clock().bpm().get() + 0.001)),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::Quant => match input.event() {
|
||||
key!(Char(',')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||
key!(Char('.')) => Clock(SetQuant(state.clock().quant.next())),
|
||||
key!(Char('<')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||
key!(Char('>')) => Clock(SetQuant(state.clock().quant.next())),
|
||||
key_pat!(Char(',')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||
key_pat!(Char('.')) => Clock(SetQuant(state.clock().quant.next())),
|
||||
key_pat!(Char('<')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||
key_pat!(Char('>')) => Clock(SetQuant(state.clock().quant.next())),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::Sync => match input.event() {
|
||||
key!(Char(',')) => Clock(SetSync(state.clock().sync.prev())),
|
||||
key!(Char('.')) => Clock(SetSync(state.clock().sync.next())),
|
||||
key!(Char('<')) => Clock(SetSync(state.clock().sync.prev())),
|
||||
key!(Char('>')) => Clock(SetSync(state.clock().sync.next())),
|
||||
key_pat!(Char(',')) => Clock(SetSync(state.clock().sync.prev())),
|
||||
key_pat!(Char('.')) => Clock(SetSync(state.clock().sync.next())),
|
||||
key_pat!(Char('<')) => Clock(SetSync(state.clock().sync.prev())),
|
||||
key_pat!(Char('>')) => Clock(SetSync(state.clock().sync.next())),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::Clock => match input.event() {
|
||||
key!(Char(',')) => todo!("transport seek bar"),
|
||||
key!(Char('.')) => todo!("transport seek bar"),
|
||||
key!(Char('<')) => todo!("transport seek beat"),
|
||||
key!(Char('>')) => todo!("transport seek beat"),
|
||||
key_pat!(Char(',')) => todo!("transport seek bar"),
|
||||
key_pat!(Char('.')) => todo!("transport seek bar"),
|
||||
key_pat!(Char('<')) => todo!("transport seek beat"),
|
||||
key_pat!(Char('>')) => todo!("transport seek beat"),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::PlayPause => match input.event() {
|
||||
key!(Enter) => Clock(
|
||||
key_pat!(Enter) => Clock(
|
||||
if state.clock().is_stopped() {
|
||||
Play(None)
|
||||
} else {
|
||||
Pause(None)
|
||||
}
|
||||
),
|
||||
key!(Shift-Enter) => Clock(
|
||||
key_pat!(Shift-Enter) => Clock(
|
||||
if state.clock().is_stopped() {
|
||||
Play(Some(0))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -143,30 +143,30 @@ impl InputToCommand<Tui, PhraseListModel> for FileBrowserCommand {
|
|||
fn input_to_command (state: &PhraseListModel, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhraseListMode::Import(_index, browser)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Up) => Select(
|
||||
key_pat!(Up) => Select(
|
||||
browser.index.overflowing_sub(1).0.min(browser.len().saturating_sub(1))
|
||||
),
|
||||
key!(Down) => Select(
|
||||
key_pat!(Down) => Select(
|
||||
browser.index.saturating_add(1) % browser.len()
|
||||
),
|
||||
key!(Right) => Chdir(browser.cwd.clone()),
|
||||
key!(Left) => Chdir(browser.cwd.clone()),
|
||||
key!(Enter) => Confirm,
|
||||
key!(Char(_)) => { todo!() },
|
||||
key!(Backspace) => { todo!() },
|
||||
key!(Esc) => Self::Cancel,
|
||||
key_pat!(Right) => Chdir(browser.cwd.clone()),
|
||||
key_pat!(Left) => Chdir(browser.cwd.clone()),
|
||||
key_pat!(Enter) => Confirm,
|
||||
key_pat!(Char(_)) => { todo!() },
|
||||
key_pat!(Backspace) => { todo!() },
|
||||
key_pat!(Esc) => Self::Cancel,
|
||||
_ => return None
|
||||
})
|
||||
} else if let Some(PhraseListMode::Export(_index, browser)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
|
||||
key!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
|
||||
key!(Right) => Chdir(browser.cwd.clone()),
|
||||
key!(Left) => Chdir(browser.cwd.clone()),
|
||||
key!(Enter) => Confirm,
|
||||
key!(Char(_)) => { todo!() },
|
||||
key!(Backspace) => { todo!() },
|
||||
key!(Esc) => Self::Cancel,
|
||||
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0.min(browser.len())),
|
||||
key_pat!(Down) => Select(browser.index.saturating_add(1) % browser.len()),
|
||||
key_pat!(Right) => Chdir(browser.cwd.clone()),
|
||||
key_pat!(Left) => Chdir(browser.cwd.clone()),
|
||||
key_pat!(Enter) => Confirm,
|
||||
key_pat!(Char(_)) => { todo!() },
|
||||
key_pat!(Backspace) => { todo!() },
|
||||
key_pat!(Esc) => Self::Cancel,
|
||||
_ => return None
|
||||
})
|
||||
} else {
|
||||
|
|
@ -179,12 +179,12 @@ impl InputToCommand<Tui, PhraseListModel> for PhraseLengthCommand {
|
|||
fn input_to_command (state: &PhraseListModel, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Up) => Self::Inc,
|
||||
key!(Down) => Self::Dec,
|
||||
key!(Right) => Self::Next,
|
||||
key!(Left) => Self::Prev,
|
||||
key!(Enter) => Self::Set(*length),
|
||||
key!(Esc) => Self::Cancel,
|
||||
key_pat!(Up) => Self::Inc,
|
||||
key_pat!(Down) => Self::Dec,
|
||||
key_pat!(Right) => Self::Next,
|
||||
key_pat!(Left) => Self::Prev,
|
||||
key_pat!(Enter) => Self::Set(*length),
|
||||
key_pat!(Esc) => Self::Cancel,
|
||||
_ => return None
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
|||
let length = state.phrase().read().unwrap().as_ref()
|
||||
.map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
Some(match from.event() {
|
||||
key!(Char('`')) => ToggleDirection,
|
||||
key!(Char('z')) => SetTimeZoomLock(!state.range().time_lock()),
|
||||
key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key!(Char('a')) => AppendNote,
|
||||
key!(Char('s')) => PutNote,
|
||||
key_pat!(Char('`')) => ToggleDirection,
|
||||
key_pat!(Char('z')) => SetTimeZoomLock(!state.range().time_lock()),
|
||||
key_pat!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key_pat!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key_pat!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key_pat!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key_pat!(Char('a')) => AppendNote,
|
||||
key_pat!(Char('s')) => PutNote,
|
||||
// TODO: no triplet/dotted
|
||||
key!(Char(',')) => SetNoteLength(prev_note_length(note_len)),
|
||||
key!(Char('.')) => SetNoteLength(next_note_length(note_len)),
|
||||
key_pat!(Char(',')) => SetNoteLength(prev_note_length(note_len)),
|
||||
key_pat!(Char('.')) => SetNoteLength(next_note_length(note_len)),
|
||||
// TODO: with triplet/dotted
|
||||
key!(Char('<')) => SetNoteLength(prev_note_length(note_len)),
|
||||
key!(Char('>')) => SetNoteLength(next_note_length(note_len)),
|
||||
key_pat!(Char('<')) => SetNoteLength(prev_note_length(note_len)),
|
||||
key_pat!(Char('>')) => SetNoteLength(next_note_length(note_len)),
|
||||
// TODO: '/' set triplet, '?' set dotted
|
||||
_ => match from.event() {
|
||||
key!(Up) => SetNoteCursor(note_point + 1),
|
||||
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
||||
key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
|
||||
key!(Right) => SetTimeCursor((time_point + note_len) % length),
|
||||
key!(Alt-Up) => SetNoteCursor(note_point + 3),
|
||||
key!(Alt-Down) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||
key!(Alt-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||
key!(Alt-Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||
key_pat!(Up) => SetNoteCursor(note_point + 1),
|
||||
key_pat!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
||||
key_pat!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
|
||||
key_pat!(Right) => SetTimeCursor((time_point + note_len) % length),
|
||||
key_pat!(Alt-Up) => SetNoteCursor(note_point + 3),
|
||||
key_pat!(Alt-Down) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||
key_pat!(Alt-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||
key_pat!(Alt-Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||
|
||||
key!(Ctrl-Up) => SetNoteScroll(note_lo + 1),
|
||||
key!(Ctrl-Down) => SetNoteScroll(note_lo.saturating_sub(1)),
|
||||
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
||||
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
||||
key!(Ctrl-Alt-Up) => SetNoteScroll(note_point + 3),
|
||||
key!(Ctrl-Alt-Down) => SetNoteScroll(note_point.saturating_sub(3)),
|
||||
key!(Ctrl-Alt-Left) => SetTimeScroll(time_point.saturating_sub(time_zoom)),
|
||||
key!(Ctrl-Alt-Right) => SetTimeScroll((time_point + time_zoom) % length),
|
||||
key_pat!(Ctrl-Up) => SetNoteScroll(note_lo + 1),
|
||||
key_pat!(Ctrl-Down) => SetNoteScroll(note_lo.saturating_sub(1)),
|
||||
key_pat!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
||||
key_pat!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
||||
key_pat!(Ctrl-Alt-Up) => SetNoteScroll(note_point + 3),
|
||||
key_pat!(Ctrl-Alt-Down) => SetNoteScroll(note_point.saturating_sub(3)),
|
||||
key_pat!(Ctrl-Alt-Left) => SetTimeScroll(time_point.saturating_sub(time_zoom)),
|
||||
key_pat!(Ctrl-Alt-Right) => SetTimeScroll((time_point + time_zoom) % length),
|
||||
_ => return None
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,6 +31,135 @@ pub enum PhraseListMode {
|
|||
Export(usize, FileBrowser),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhrasesCommand {
|
||||
/// Update the contents of the phrase pool
|
||||
Phrase(Pool),
|
||||
/// Select a phrase from the phrase pool
|
||||
Select(usize),
|
||||
/// Rename a phrase
|
||||
Rename(Rename),
|
||||
/// Change the length of a phrase
|
||||
Length(Length),
|
||||
/// Import from file
|
||||
Import(Browse),
|
||||
/// Export to file
|
||||
Export(Browse),
|
||||
}
|
||||
|
||||
impl Command<PhraseListModel> for PhrasesCommand {
|
||||
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> {
|
||||
use PhrasesCommand::*;
|
||||
Ok(match self {
|
||||
Phrase(command) => command.execute(state)?.map(Phrase),
|
||||
Rename(command) => match command {
|
||||
PhraseRenameCommand::Begin => {
|
||||
let length = state.phrases()[state.phrase_index()].read().unwrap().length;
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Rename)
|
||||
},
|
||||
Length(command) => match command {
|
||||
PhraseLengthCommand::Begin => {
|
||||
let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone();
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Rename(state.phrase_index(), name)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Length)
|
||||
},
|
||||
Import(command) => match command {
|
||||
FileBrowserCommand::Begin => {
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Import(state.phrase_index(), FileBrowser::new(None)?)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Import)
|
||||
},
|
||||
Export(command) => match command {
|
||||
FileBrowserCommand::Begin => {
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Export(state.phrase_index(), FileBrowser::new(None)?)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Export)
|
||||
},
|
||||
Select(phrase) => {
|
||||
state.set_phrase_index(phrase);
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhraseListModel> for PhrasesCommand {
|
||||
fn input_to_command (state: &PhraseListModel, input: &TuiInput) -> Option<Self> {
|
||||
Some(match state.phrases_mode() {
|
||||
Some(PhraseListMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Export(..)) => Self::Export(Browse::input_to_command(state, input)?),
|
||||
_ => to_phrases_command(state, input)?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<PhrasesCommand> {
|
||||
use KeyCode::{Up, Down, Delete, Char};
|
||||
use PhrasesCommand as Cmd;
|
||||
let index = state.phrase_index();
|
||||
let count = state.phrases().len();
|
||||
Some(match input.event() {
|
||||
key_pat!(Char('n')) => Cmd::Rename(Rename::Begin),
|
||||
key_pat!(Char('t')) => Cmd::Length(Length::Begin),
|
||||
key_pat!(Char('m')) => Cmd::Import(Browse::Begin),
|
||||
key_pat!(Char('x')) => Cmd::Export(Browse::Begin),
|
||||
key_pat!(Char('c')) => Cmd::Phrase(Pool::SetColor(index, ItemColor::random())),
|
||||
key_pat!(Char('[')) | key_pat!(Up) => Cmd::Select(
|
||||
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
|
||||
),
|
||||
key_pat!(Char(']')) | key_pat!(Down) => Cmd::Select(
|
||||
index.saturating_add(1) % state.phrases().len()
|
||||
),
|
||||
key_pat!(Char('<')) => if index > 1 {
|
||||
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
||||
Cmd::Phrase(Pool::Swap(index - 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key_pat!(Char('>')) => if index < count.saturating_sub(1) {
|
||||
state.set_phrase_index(state.phrase_index() + 1);
|
||||
Cmd::Phrase(Pool::Swap(index + 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key_pat!(Delete) => if index > 0 {
|
||||
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
||||
Cmd::Phrase(Pool::Delete(index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, Phrase::new(
|
||||
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, Phrase::new(
|
||||
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => {
|
||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
|
||||
Cmd::Phrase(Pool::Add(index + 1, phrase))
|
||||
},
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
impl Default for PhraseListModel {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
|
|
@ -158,132 +287,3 @@ render!(|self: PhraseListView<'a>|{
|
|||
add(&Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))
|
||||
}))
|
||||
});
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhrasesCommand {
|
||||
/// Update the contents of the phrase pool
|
||||
Phrase(Pool),
|
||||
/// Select a phrase from the phrase pool
|
||||
Select(usize),
|
||||
/// Rename a phrase
|
||||
Rename(Rename),
|
||||
/// Change the length of a phrase
|
||||
Length(Length),
|
||||
/// Import from file
|
||||
Import(Browse),
|
||||
/// Export to file
|
||||
Export(Browse),
|
||||
}
|
||||
|
||||
impl Command<PhraseListModel> for PhrasesCommand {
|
||||
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> {
|
||||
use PhrasesCommand::*;
|
||||
Ok(match self {
|
||||
Phrase(command) => command.execute(state)?.map(Phrase),
|
||||
Rename(command) => match command {
|
||||
PhraseRenameCommand::Begin => {
|
||||
let length = state.phrases()[state.phrase_index()].read().unwrap().length;
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Rename)
|
||||
},
|
||||
Length(command) => match command {
|
||||
PhraseLengthCommand::Begin => {
|
||||
let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone();
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Rename(state.phrase_index(), name)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Length)
|
||||
},
|
||||
Import(command) => match command {
|
||||
FileBrowserCommand::Begin => {
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Import(state.phrase_index(), FileBrowser::new(None)?)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Import)
|
||||
},
|
||||
Export(command) => match command {
|
||||
FileBrowserCommand::Begin => {
|
||||
*state.phrases_mode_mut() = Some(
|
||||
PhraseListMode::Export(state.phrase_index(), FileBrowser::new(None)?)
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => command.execute(state)?.map(Export)
|
||||
},
|
||||
Select(phrase) => {
|
||||
state.set_phrase_index(phrase);
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhraseListModel> for PhrasesCommand {
|
||||
fn input_to_command (state: &PhraseListModel, input: &TuiInput) -> Option<Self> {
|
||||
Some(match state.phrases_mode() {
|
||||
Some(PhraseListMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?),
|
||||
Some(PhraseListMode::Export(..)) => Self::Export(Browse::input_to_command(state, input)?),
|
||||
_ => to_phrases_command(state, input)?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<PhrasesCommand> {
|
||||
use KeyCode::{Up, Down, Delete, Char};
|
||||
use PhrasesCommand as Cmd;
|
||||
let index = state.phrase_index();
|
||||
let count = state.phrases().len();
|
||||
Some(match input.event() {
|
||||
key!(Char('n')) => Cmd::Rename(Rename::Begin),
|
||||
key!(Char('t')) => Cmd::Length(Length::Begin),
|
||||
key!(Char('m')) => Cmd::Import(Browse::Begin),
|
||||
key!(Char('x')) => Cmd::Export(Browse::Begin),
|
||||
key!(Char('c')) => Cmd::Phrase(Pool::SetColor(index, ItemColor::random())),
|
||||
key!(Char('[')) | key!(Up) => Cmd::Select(
|
||||
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
|
||||
),
|
||||
key!(Char(']')) | key!(Down) => Cmd::Select(
|
||||
index.saturating_add(1) % state.phrases().len()
|
||||
),
|
||||
key!(Char('<')) => if index > 1 {
|
||||
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
||||
Cmd::Phrase(Pool::Swap(index - 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key!(Char('>')) => if index < count.saturating_sub(1) {
|
||||
state.set_phrase_index(state.phrase_index() + 1);
|
||||
Cmd::Phrase(Pool::Swap(index + 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key!(Delete) => if index > 0 {
|
||||
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
||||
Cmd::Phrase(Pool::Delete(index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
key!(Char('a')) | key!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, Phrase::new(
|
||||
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
key!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, Phrase::new(
|
||||
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
key!(Char('d')) | key!(Shift-Char('D')) => {
|
||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
|
||||
Cmd::Phrase(Pool::Add(index + 1, phrase))
|
||||
},
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,18 +38,18 @@ impl InputToCommand<Tui, PhraseListModel> for PhraseRenameCommand {
|
|||
use KeyCode::{Char, Backspace, Enter, Esc};
|
||||
if let Some(PhraseListMode::Rename(_, ref old_name)) = state.phrases_mode() {
|
||||
Some(match from.event() {
|
||||
key!(Char(c)) => {
|
||||
key_pat!(Char(c)) => {
|
||||
let mut new_name = old_name.clone();
|
||||
new_name.push(*c);
|
||||
Self::Set(new_name)
|
||||
},
|
||||
key!(Backspace) => {
|
||||
key_pat!(Backspace) => {
|
||||
let mut new_name = old_name.clone();
|
||||
new_name.pop();
|
||||
Self::Set(new_name)
|
||||
},
|
||||
key!(Enter) => Self::Confirm,
|
||||
key!(Esc) => Self::Cancel,
|
||||
key_pat!(Enter) => Self::Confirm,
|
||||
key_pat!(Esc) => Self::Cancel,
|
||||
_ => return None
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -28,51 +28,58 @@ impl Input<Tui> for TuiInput {
|
|||
}
|
||||
}
|
||||
|
||||
//#[macro_export] macro_rules! key_pat {
|
||||
//}
|
||||
//#[macro_export] macro_rules! key_expr {
|
||||
//}
|
||||
#[macro_export] macro_rules! key_event_pat {
|
||||
($code:pat, $modifiers: pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: $modifiers,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
($code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/// Define key pattern in key match statement
|
||||
#[macro_export] macro_rules! key {
|
||||
(Ctrl-Alt-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
})) };
|
||||
(Ctrl-Alt-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
})) };
|
||||
#[macro_export] macro_rules! key_event_expr {
|
||||
($code:expr, $modifiers: expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: $modifiers,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
($code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
(Ctrl-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
#[macro_export] macro_rules! key_pat {
|
||||
(Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
|
||||
(Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) };
|
||||
(Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) };
|
||||
(Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) };
|
||||
($code:pat) => { key_event_pat!($code) };
|
||||
}
|
||||
|
||||
(Ctrl-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Alt-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Alt-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Shift-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Shift-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
($code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
($code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
#[macro_export] macro_rules! key_expr {
|
||||
(Ctrl-Alt-$code:expr) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
|
||||
(Ctrl-$code:expr) => { key_event_expr!($code, KeyModifiers::CONTROL) };
|
||||
(Alt-$code:expr) => { key_event_expr!($code, KeyModifiers::ALT) };
|
||||
(Shift-$code:expr) => { key_event_expr!($code, KeyModifiers::SHIFT) };
|
||||
($code:expr) => { key_event_expr!($code) };
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue