mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +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),
|
SetColor(usize, usize, ItemPalette),
|
||||||
}
|
}
|
||||||
|
|
||||||
input_to_command!(ArrangerCommand: |state: ArrangerTui, input: Event|match input {
|
use ArrangerCommand as Cmd;
|
||||||
key_pat!(Char('u')) => Self::History(-1),
|
keymap!(KEYS_ARRANGER = |state: ArrangerTui, input: Event| ArrangerCommand {
|
||||||
key_pat!(Char('U')) => Self::History(1),
|
key(Char('u')) => Cmd::History(-1),
|
||||||
|
key(Char('U')) => Cmd::History(1),
|
||||||
// TODO: k: toggle on-screen keyboard
|
// TODO: k: toggle on-screen keyboard
|
||||||
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
ctrl(key(Char('k'))) => { todo!("keyboard") },
|
||||||
// Transport: Play/pause
|
// Transport: Play/pause
|
||||||
key_pat!(Char(' ')) =>
|
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||||
Self::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
|
||||||
// Transport: Play from start or rewind to start
|
// Transport: Play from start or rewind to start
|
||||||
key_pat!(Shift-Char(' ')) =>
|
shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||||
Self::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()))),
|
||||||
key_pat!(Char('e')) =>
|
ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||||
Self::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))),
|
ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||||
key_pat!(Ctrl-Left) =>
|
|
||||||
Self::Scene(ArrangerSceneCommand::Add),
|
|
||||||
key_pat!(Ctrl-Char('t')) =>
|
|
||||||
Self::Track(ArrangerTrackCommand::Add),
|
|
||||||
// Tab: Toggle visibility of phrase pool column
|
// Tab: Toggle visibility of phrase pool column
|
||||||
key_pat!(Tab) =>
|
key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)),
|
||||||
Self::Phrases(PoolCommand::Show(!state.pool.visible)),
|
}, {
|
||||||
_ => {
|
use ArrangerSelection as Selected;
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerSceneCommand as Scene;
|
||||||
use ArrangerSelection as Selected;
|
use ArrangerTrackCommand as Track;
|
||||||
use ArrangerSceneCommand as Scene;
|
use ArrangerClipCommand as Clip;
|
||||||
use ArrangerTrackCommand as Track;
|
let t_len = state.tracks.len();
|
||||||
use ArrangerClipCommand as Clip;
|
let s_len = state.scenes.len();
|
||||||
let t_len = state.tracks.len();
|
match state.selected() {
|
||||||
let s_len = state.scenes.len();
|
Selected::Clip(t, s) => match input {
|
||||||
match state.selected() {
|
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
|
||||||
Selected::Clip(t, s) => match input {
|
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
|
||||||
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
|
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||||
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('>')) => 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('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
|
||||||
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))),
|
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
|
||||||
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(
|
key_pat!(Up) => Some(Cmd::Select(
|
||||||
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
|
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
|
||||||
key_pat!(Down) => Some(Cmd::Select(
|
key_pat!(Down) => Some(Cmd::Select(
|
||||||
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
|
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
|
||||||
key_pat!(Left) => Some(Cmd::Select(
|
key_pat!(Left) => Some(Cmd::Select(
|
||||||
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
|
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
|
||||||
key_pat!(Right) => Some(Cmd::Select(
|
key_pat!(Right) => Some(Cmd::Select(
|
||||||
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
|
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
|
||||||
|
|
||||||
_ => None
|
_ => None
|
||||||
},
|
},
|
||||||
Selected::Scene(s) => match input {
|
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('<')) => 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!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
|
||||||
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
|
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
|
||||||
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
|
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
|
||||||
|
|
||||||
key_pat!(Up) => Some(
|
key_pat!(Up) => Some(
|
||||||
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
|
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
|
||||||
key_pat!(Down) => Some(
|
key_pat!(Down) => Some(
|
||||||
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
|
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
|
||||||
key_pat!(Left) =>
|
key_pat!(Left) =>
|
||||||
return None,
|
return None,
|
||||||
key_pat!(Right) => Some(
|
key_pat!(Right) => Some(
|
||||||
Cmd::Select(Selected::Clip(0, s))),
|
Cmd::Select(Selected::Clip(0, s))),
|
||||||
|
|
||||||
_ => None
|
_ => None
|
||||||
},
|
},
|
||||||
Selected::Track(t) => match input {
|
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!(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!(Delete) => Some(Cmd::Track(Track::Delete(t))),
|
||||||
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
|
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
|
||||||
|
|
||||||
key_pat!(Up) =>
|
key_pat!(Up) =>
|
||||||
return None,
|
return None,
|
||||||
key_pat!(Down) => Some(
|
key_pat!(Down) => Some(
|
||||||
Cmd::Select(Selected::Clip(t, 0))),
|
Cmd::Select(Selected::Clip(t, 0))),
|
||||||
key_pat!(Left) => Some(
|
key_pat!(Left) => Some(
|
||||||
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
|
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
|
||||||
key_pat!(Right) => Some(
|
key_pat!(Right) => Some(
|
||||||
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
|
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
|
||||||
|
|
||||||
_ => None
|
_ => None
|
||||||
},
|
},
|
||||||
Selected::Mix => match input {
|
Selected::Mix => match input {
|
||||||
key_pat!(Delete) => Some(Cmd::Clear),
|
key_pat!(Delete) => Some(Cmd::Clear),
|
||||||
key_pat!(Char('0')) => Some(Cmd::StopAll),
|
key_pat!(Char('0')) => Some(Cmd::StopAll),
|
||||||
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
|
||||||
|
|
||||||
key_pat!(Up) =>
|
key_pat!(Up) =>
|
||||||
return None,
|
return None,
|
||||||
key_pat!(Down) => Some(
|
key_pat!(Down) => Some(
|
||||||
Cmd::Select(Selected::Scene(0))),
|
Cmd::Select(Selected::Scene(0))),
|
||||||
key_pat!(Left) =>
|
key_pat!(Left) =>
|
||||||
return None,
|
return None,
|
||||||
key_pat!(Right) => Some(
|
key_pat!(Right) => Some(
|
||||||
Cmd::Select(Selected::Track(0))),
|
Cmd::Select(Selected::Track(0))),
|
||||||
|
|
||||||
_ => None
|
_ => None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
||||||
Some(Self::Editor(command))
|
Some(Cmd::Editor(command))
|
||||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||||
Some(Self::Phrases(command))
|
Some(Cmd::Phrases(command))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})?
|
})?);
|
||||||
});
|
|
||||||
|
|
||||||
command!(|self: ArrangerCommand, state: ArrangerTui|match self {
|
command!(|self: ArrangerCommand, state: ArrangerTui|match self {
|
||||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,38 @@ pub struct TransportTui {
|
||||||
}
|
}
|
||||||
has_clock!(|self: TransportTui|&self.clock);
|
has_clock!(|self: TransportTui|&self.clock);
|
||||||
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
|
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 {
|
render!(Tui: (self: TransportTui) => TransportView {
|
||||||
compact: false,
|
compact: false,
|
||||||
clock: &self.clock
|
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"),
|
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::*;
|
use crate::*;
|
||||||
|
|
||||||
#[macro_export] macro_rules! command {
|
#[macro_export] macro_rules! keymap {
|
||||||
(|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
(
|
||||||
impl Command<$State> for $Command {
|
$KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
|
||||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
|
||||||
Ok($handler)
|
) => {
|
||||||
|
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];
|
#[macro_export] macro_rules! command {
|
||||||
|
(|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||||
pub struct EventMap<'a, S, I: PartialEq, C> {
|
impl Command<$State> for $Command {
|
||||||
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
|
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||||
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
|
Ok($handler)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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,
|
player,
|
||||||
sampler,
|
sampler,
|
||||||
_jack: jack.clone(),
|
_jack: jack.clone(),
|
||||||
|
|
||||||
pool: crate::pool::PoolModel::from(&phrase),
|
pool: crate::pool::PoolModel::from(&phrase),
|
||||||
editor: crate::midi::MidiEditor::from(&phrase),
|
editor: crate::midi::MidiEditor::from(&phrase),
|
||||||
|
|
||||||
|
compact: true,
|
||||||
|
status: true,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
note_buf: vec![],
|
note_buf: vec![],
|
||||||
perf: PerfModel::default(),
|
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()));
|
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
|
// Tab: Toggle compact mode
|
||||||
key(Tab) => Cmd::Compact(!state.compact),
|
key(Tab) => Cmd::Compact(!state.compact),
|
||||||
// q: Enqueue currently edited phrase
|
// q: Enqueue currently edited phrase
|
||||||
|
|
@ -179,13 +181,13 @@ keymap!(KEYS_GROOVEBOX: |state: Groovebox, input: Event| GrooveboxCommand {
|
||||||
} else {
|
} else {
|
||||||
return None
|
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)
|
Cmd::Editor(command)
|
||||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||||
Cmd::Pool(command)
|
Cmd::Pool(command)
|
||||||
} else {
|
} else {
|
||||||
return None
|
return None
|
||||||
}));
|
});
|
||||||
|
|
||||||
command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||||
Self::Enqueue(phrase) => {
|
Self::Enqueue(phrase) => {
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ pub enum MidiEditCommand {
|
||||||
|
|
||||||
handle!(<Tui>|self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event()));
|
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(Up) => SetNoteCursor(s.note_point() + 1),
|
||||||
key(Char('w')) => SetNoteCursor(s.note_point() + 1),
|
key(Char('w')) => SetNoteCursor(s.note_point() + 1),
|
||||||
key(Down) => SetNoteCursor(s.note_point().saturating_sub(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),
|
Editor(MidiEditCommand),
|
||||||
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
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
|
// TODO: k: toggle on-screen keyboard
|
||||||
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
ctrl(key(Char('k'))) => { todo!("keyboard") },
|
||||||
// Transport: Play/pause
|
// Transport: Play/pause
|
||||||
key_pat!(Char(' ')) => Cmd::Clock(
|
key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||||
if state.clock().is_stopped() { Play(None) } else { Pause(None) }
|
|
||||||
),
|
|
||||||
// Transport: Play from start or rewind to start
|
// Transport: Play from start or rewind to start
|
||||||
key_pat!(Shift-Char(' ')) => Cmd::Clock(
|
shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
|
||||||
),
|
|
||||||
// u: undo
|
// u: undo
|
||||||
key_pat!(Char('u')) => Cmd::History(-1),
|
key(Char('u')) => Cmd::History(-1),
|
||||||
// Shift-U: redo
|
// Shift-U: redo
|
||||||
key_pat!(Char('U')) => Cmd::History( 1),
|
key(Char('U')) => Cmd::History( 1),
|
||||||
// Tab: Toggle compact mode
|
// Tab: Toggle compact mode
|
||||||
key_pat!(Tab) => Cmd::Compact(!state.compact),
|
key(Tab) => Cmd::Compact(!state.compact),
|
||||||
// q: Enqueue currently edited phrase
|
// 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)
|
// 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
|
// 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 editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone());
|
||||||
let selected = state.pool.phrase().clone();
|
let selected = state.pool.phrase().clone();
|
||||||
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
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 {
|
} else {
|
||||||
return None
|
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 {
|
command!(|self: SequencerCommand, state: SequencerTui|match self {
|
||||||
Self::Enqueue(phrase) => {
|
Self::Enqueue(phrase) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue