mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
use keymap! in more places
This commit is contained in:
parent
8dedc8fd5f
commit
a82f73d475
6 changed files with 200 additions and 268 deletions
|
|
@ -45,121 +45,114 @@ pub enum ArrangerClipCommand {
|
|||
SetColor(usize, usize, ItemPalette),
|
||||
}
|
||||
|
||||
input_to_command!(ArrangerCommand: |state: ArrangerTui, input: Event|match input {
|
||||
key_pat!(Char('u')) => Self::History(-1),
|
||||
key_pat!(Char('U')) => Self::History(1),
|
||||
use ArrangerCommand as Cmd;
|
||||
keymap!(KEYS_ARRANGER = |state: ArrangerTui, input: Event| ArrangerCommand {
|
||||
key(Char('u')) => Cmd::History(-1),
|
||||
key(Char('U')) => Cmd::History(1),
|
||||
// TODO: k: toggle on-screen keyboard
|
||||
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
||||
ctrl(key(Char('k'))) => { todo!("keyboard") },
|
||||
// Transport: Play/pause
|
||||
key_pat!(Char(' ')) =>
|
||||
Self::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
// Transport: Play from start or rewind to start
|
||||
key_pat!(Shift-Char(' ')) =>
|
||||
Self::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
key_pat!(Char('e')) =>
|
||||
Self::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))),
|
||||
key_pat!(Ctrl-Left) =>
|
||||
Self::Scene(ArrangerSceneCommand::Add),
|
||||
key_pat!(Ctrl-Char('t')) =>
|
||||
Self::Track(ArrangerTrackCommand::Add),
|
||||
shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))),
|
||||
ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
// Tab: Toggle visibility of phrase pool column
|
||||
key_pat!(Tab) =>
|
||||
Self::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||
_ => {
|
||||
use ArrangerCommand as Cmd;
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
match state.selected() {
|
||||
Selected::Clip(t, s) => match input {
|
||||
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
|
||||
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))),
|
||||
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
|
||||
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||
}, {
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
match state.selected() {
|
||||
Selected::Clip(t, s) => match input {
|
||||
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
|
||||
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))),
|
||||
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
|
||||
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||
|
||||
key_pat!(Up) => Some(Cmd::Select(
|
||||
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
|
||||
key_pat!(Down) => Some(Cmd::Select(
|
||||
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) => Some(Cmd::Select(
|
||||
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
|
||||
key_pat!(Right) => Some(Cmd::Select(
|
||||
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
|
||||
key_pat!(Up) => Some(Cmd::Select(
|
||||
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
|
||||
key_pat!(Down) => Some(Cmd::Select(
|
||||
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) => Some(Cmd::Select(
|
||||
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
|
||||
key_pat!(Right) => Some(Cmd::Select(
|
||||
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Scene(s) => match input {
|
||||
key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
|
||||
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
|
||||
_ => None
|
||||
},
|
||||
Selected::Scene(s) => match input {
|
||||
key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
|
||||
key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
|
||||
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
|
||||
|
||||
key_pat!(Up) => Some(
|
||||
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) =>
|
||||
return None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Clip(0, s))),
|
||||
key_pat!(Up) => Some(
|
||||
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
|
||||
key_pat!(Left) =>
|
||||
return None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Clip(0, s))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Track(t) => match input {
|
||||
key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
|
||||
_ => None
|
||||
},
|
||||
Selected::Track(t) => match input {
|
||||
key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
|
||||
key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
|
||||
key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
|
||||
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
|
||||
|
||||
key_pat!(Up) =>
|
||||
return None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Clip(t, 0))),
|
||||
key_pat!(Left) => Some(
|
||||
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
|
||||
key_pat!(Up) =>
|
||||
return None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Clip(t, 0))),
|
||||
key_pat!(Left) => Some(
|
||||
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
Selected::Mix => match input {
|
||||
key_pat!(Delete) => Some(Cmd::Clear),
|
||||
key_pat!(Char('0')) => Some(Cmd::StopAll),
|
||||
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
||||
_ => None
|
||||
},
|
||||
Selected::Mix => match input {
|
||||
key_pat!(Delete) => Some(Cmd::Clear),
|
||||
key_pat!(Char('0')) => Some(Cmd::StopAll),
|
||||
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
||||
|
||||
key_pat!(Up) =>
|
||||
return None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene(0))),
|
||||
key_pat!(Left) =>
|
||||
return None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track(0))),
|
||||
key_pat!(Up) =>
|
||||
return None,
|
||||
key_pat!(Down) => Some(
|
||||
Cmd::Select(Selected::Scene(0))),
|
||||
key_pat!(Left) =>
|
||||
return None,
|
||||
key_pat!(Right) => Some(
|
||||
Cmd::Select(Selected::Track(0))),
|
||||
|
||||
_ => None
|
||||
},
|
||||
}
|
||||
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Some(Self::Editor(command))
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Some(Self::Phrases(command))
|
||||
} else {
|
||||
None
|
||||
})?
|
||||
});
|
||||
_ => None
|
||||
},
|
||||
}
|
||||
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Some(Cmd::Editor(command))
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Some(Cmd::Phrases(command))
|
||||
} else {
|
||||
None
|
||||
})?);
|
||||
|
||||
command!(|self: ArrangerCommand, state: ArrangerTui|match self {
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,38 @@ pub struct TransportTui {
|
|||
}
|
||||
has_clock!(|self: TransportTui|&self.clock);
|
||||
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
|
||||
handle!(<Tui>|self: TransportTui, input|TransportCommand::execute_with_state(self, input.event()));
|
||||
handle!(<Tui>|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event()));
|
||||
keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand {
|
||||
key(Char(' ')) =>
|
||||
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
|
||||
shift(key(Char(' '))) =>
|
||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||
});
|
||||
// TODO:
|
||||
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||
//key(Char(',')) => SetBpm(state.bpm().get() - 1.0),
|
||||
//key(Char('.')) => SetBpm(state.bpm().get() + 1.0),
|
||||
//key(Char('<')) => SetBpm(state.bpm().get() - 0.001),
|
||||
//key(Char('>')) => SetBpm(state.bpm().get() + 0.001),
|
||||
//});
|
||||
//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||
//key(Char(',')) => SetQuant(state.quant.prev()),
|
||||
//key(Char('.')) => SetQuant(state.quant.next()),
|
||||
//key(Char('<')) => SetQuant(state.quant.prev()),
|
||||
//key(Char('>')) => SetQuant(state.quant.next()),
|
||||
//});
|
||||
//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand {
|
||||
//key(Char(',')) => SetSync(state.sync.prev()),
|
||||
//key(Char('.')) => SetSync(state.sync.next()),
|
||||
//key(Char('<')) => SetSync(state.sync.prev()),
|
||||
//key(Char('>')) => SetSync(state.sync.next()),
|
||||
//});
|
||||
//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||
//key(Char(',')) => todo!("transport seek bar"),
|
||||
//key(Char('.')) => todo!("transport seek bar"),
|
||||
//key(Char('<')) => todo!("transport seek beat"),
|
||||
//key(Char('>')) => todo!("transport seek beat"),
|
||||
//});
|
||||
render!(Tui: (self: TransportTui) => TransportView {
|
||||
compact: false,
|
||||
clock: &self.clock
|
||||
|
|
@ -105,71 +136,3 @@ render!(Tui: (self: OutputStats) => Tui::either(self.compact,
|
|||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"),
|
||||
)));
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TransportCommand {
|
||||
Clock(ClockCommand),
|
||||
}
|
||||
|
||||
command!(|self:TransportCommand,state:TransportTui|match self {
|
||||
//Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus),
|
||||
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
impl InputToCommand<Event, TransportTui> for TransportCommand {
|
||||
fn input_to_command (state: &TransportTui, input: &Event) -> Option<Self> {
|
||||
use TransportCommand::*;
|
||||
Some(match input {
|
||||
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
Play(None)
|
||||
} else {
|
||||
Pause(None)
|
||||
}),
|
||||
key_pat!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||
Play(Some(0))
|
||||
} else {
|
||||
Pause(Some(0))
|
||||
}),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
fn to_bpm_command (input: &TuiIn, bpm: f64) -> Option<TransportCommand> {
|
||||
use TransportCommand::*;
|
||||
Some(match input.event() {
|
||||
key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)),
|
||||
key_pat!(Char('.')) => Clock(SetBpm(bpm + 1.0)),
|
||||
key_pat!(Char('<')) => Clock(SetBpm(bpm - 0.001)),
|
||||
key_pat!(Char('>')) => Clock(SetBpm(bpm + 0.001)),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
fn to_quant_command (input: &TuiIn, quant: &Quantize) -> Option<TransportCommand> {
|
||||
use TransportCommand::*;
|
||||
Some(match input.event() {
|
||||
key_pat!(Char(',')) => Clock(SetQuant(quant.prev())),
|
||||
key_pat!(Char('.')) => Clock(SetQuant(quant.next())),
|
||||
key_pat!(Char('<')) => Clock(SetQuant(quant.prev())),
|
||||
key_pat!(Char('>')) => Clock(SetQuant(quant.next())),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
fn to_sync_command (input: &TuiIn, sync: &LaunchSync) -> Option<TransportCommand> {
|
||||
use TransportCommand::*;
|
||||
Some(match input.event() {
|
||||
key_pat!(Char(',')) => Clock(SetSync(sync.prev())),
|
||||
key_pat!(Char('.')) => Clock(SetSync(sync.next())),
|
||||
key_pat!(Char('<')) => Clock(SetSync(sync.prev())),
|
||||
key_pat!(Char('>')) => Clock(SetSync(sync.next())),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
fn to_seek_command (input: &TuiIn) -> Option<TransportCommand> {
|
||||
use TransportCommand::*;
|
||||
Some(match input.event() {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
115
src/command.rs
115
src/command.rs
|
|
@ -1,12 +1,50 @@
|
|||
use crate::*;
|
||||
|
||||
#[macro_export] macro_rules! command {
|
||||
(|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl Command<$State> for $Command {
|
||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
#[macro_export] macro_rules! keymap {
|
||||
(
|
||||
$KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: None,
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($Command: |state: $State, input: $Input|{
|
||||
$KEYS.handle(state, input)?
|
||||
});
|
||||
};
|
||||
(
|
||||
$KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? },
|
||||
$default:expr
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: Some(&|$state, $input|Some($default)),
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($Command: |state: $State, input: $Input|{
|
||||
$KEYS.handle(state, input)?
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
pub struct EventMap<'a, S, I: PartialEq, C> {
|
||||
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
|
||||
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
|
||||
}
|
||||
|
||||
impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
|
||||
pub fn handle (&self, state: &S, input: &I) -> Option<C> {
|
||||
for (binding, handler) in self.bindings.iter() {
|
||||
if input == binding {
|
||||
return handler(state)
|
||||
}
|
||||
}
|
||||
if let Some(fallback) = self.fallback {
|
||||
fallback(state, input)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,69 +77,12 @@ pub trait InputToCommand<I, S>: Command<S> + Sized {
|
|||
}
|
||||
}
|
||||
|
||||
pub type KeyMapping<const N: usize, E, T, U> = [(E, &'static dyn Fn(&T)->U);N];
|
||||
|
||||
pub struct EventMap<'a, S, I: PartialEq, C> {
|
||||
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
|
||||
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
|
||||
}
|
||||
|
||||
impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
|
||||
pub fn handle (&self, state: &S, input: &I) -> Option<C> {
|
||||
for (binding, handler) in self.bindings.iter() {
|
||||
if input == binding {
|
||||
return handler(state)
|
||||
#[macro_export] macro_rules! command {
|
||||
(|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl Command<$State> for $Command {
|
||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
if let Some(fallback) = self.fallback {
|
||||
fallback(state, input)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! event_map {
|
||||
($events:expr) => {
|
||||
EventMap { bindings: $events, fallback: None }
|
||||
};
|
||||
($events:expr, $default: expr) => {
|
||||
EventMap { bindings: $events, fallback: Some($default) }
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! event_map_input_to_command {
|
||||
($Input:ty: $Model:ty: $Command:ty: $EventMap:expr) => {
|
||||
input_to_command!($Command: |state: $Model, input: $Input|{
|
||||
event_map!($EventMap).handle(state, input)?
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! keymap {
|
||||
(
|
||||
$KEYS:ident: |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: None,
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($Command: |state: $State, input: $Input|{
|
||||
$KEYS.handle(state, input)?
|
||||
});
|
||||
};
|
||||
(
|
||||
$KEYS:ident: |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||
{ $($key:expr => $handler:expr),* $(,)? },
|
||||
$default:expr
|
||||
) => {
|
||||
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
|
||||
fallback: Some(&|$state, $input|$default),
|
||||
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
|
||||
};
|
||||
input_to_command!($Command: |state: $State, input: $Input|{
|
||||
$KEYS.handle(state, input)?
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,14 +41,16 @@ impl Groovebox {
|
|||
player,
|
||||
sampler,
|
||||
_jack: jack.clone(),
|
||||
|
||||
pool: crate::pool::PoolModel::from(&phrase),
|
||||
editor: crate::midi::MidiEditor::from(&phrase),
|
||||
|
||||
compact: true,
|
||||
status: true,
|
||||
size: Measure::new(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
perf: PerfModel::default(),
|
||||
status: true,
|
||||
compact: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +146,7 @@ pub enum GrooveboxCommand {
|
|||
}
|
||||
|
||||
handle!(<Tui>|self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
|
||||
keymap!(KEYS_GROOVEBOX: |state: Groovebox, input: Event| GrooveboxCommand {
|
||||
keymap!(KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand {
|
||||
// Tab: Toggle compact mode
|
||||
key(Tab) => Cmd::Compact(!state.compact),
|
||||
// q: Enqueue currently edited phrase
|
||||
|
|
@ -179,13 +181,13 @@ keymap!(KEYS_GROOVEBOX: |state: Groovebox, input: Event| GrooveboxCommand {
|
|||
} else {
|
||||
return None
|
||||
},
|
||||
}, Some(if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Cmd::Editor(command)
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Cmd::Pool(command)
|
||||
} else {
|
||||
return None
|
||||
}));
|
||||
});
|
||||
|
||||
command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||
Self::Enqueue(phrase) => {
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ pub enum MidiEditCommand {
|
|||
|
||||
handle!(<Tui>|self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event()));
|
||||
|
||||
keymap!(KEYS_MIDI_EDITOR: |s: MidiEditor, _input: Event| MidiEditCommand {
|
||||
keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand {
|
||||
key(Up) => SetNoteCursor(s.note_point() + 1),
|
||||
key(Char('w')) => SetNoteCursor(s.note_point() + 1),
|
||||
key(Down) => SetNoteCursor(s.note_point().saturating_sub(1)),
|
||||
|
|
|
|||
|
|
@ -109,29 +109,25 @@ handle!(<Tui>|self:SequencerTui,input|SequencerCommand::execute_with_state(self,
|
|||
Editor(MidiEditCommand),
|
||||
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
||||
}
|
||||
input_to_command!(SequencerCommand: |state: SequencerTui, input: Event|match input {
|
||||
keymap!(KEYS_SEQUENCER = |state: SequencerTui, input: Event| SequencerCommand {
|
||||
// TODO: k: toggle on-screen keyboard
|
||||
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
||||
ctrl(key(Char('k'))) => { todo!("keyboard") },
|
||||
// Transport: Play/pause
|
||||
key_pat!(Char(' ')) => Cmd::Clock(
|
||||
if state.clock().is_stopped() { Play(None) } else { Pause(None) }
|
||||
),
|
||||
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
// Transport: Play from start or rewind to start
|
||||
key_pat!(Shift-Char(' ')) => Cmd::Clock(
|
||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||
),
|
||||
shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
// u: undo
|
||||
key_pat!(Char('u')) => Cmd::History(-1),
|
||||
key(Char('u')) => Cmd::History(-1),
|
||||
// Shift-U: redo
|
||||
key_pat!(Char('U')) => Cmd::History( 1),
|
||||
key(Char('U')) => Cmd::History( 1),
|
||||
// Tab: Toggle compact mode
|
||||
key_pat!(Tab) => Cmd::Compact(!state.compact),
|
||||
key(Tab) => Cmd::Compact(!state.compact),
|
||||
// q: Enqueue currently edited phrase
|
||||
key_pat!(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())),
|
||||
key(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())),
|
||||
// 0: Enqueue phrase 0 (stop all)
|
||||
key_pat!(Char('0')) => Cmd::Enqueue(Some(state.phrases()[0].clone())),
|
||||
key(Char('0')) => Cmd::Enqueue(Some(state.phrases()[0].clone())),
|
||||
// e: Toggle between editing currently playing or other phrase
|
||||
key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() {
|
||||
key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() {
|
||||
let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone());
|
||||
let selected = state.pool.phrase().clone();
|
||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||
|
|
@ -141,16 +137,13 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input: Event|match inp
|
|||
})))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
// For the rest, use the default keybindings of the components.
|
||||
// The ones defined above supersede them.
|
||||
_ => if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Cmd::Editor(command)
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Cmd::Pool(command)
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||
Cmd::Editor(command)
|
||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||
Cmd::Pool(command)
|
||||
} else {
|
||||
return None
|
||||
});
|
||||
command!(|self: SequencerCommand, state: SequencerTui|match self {
|
||||
Self::Enqueue(phrase) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue