keymap macros

This commit is contained in:
🪞👃🪞 2024-06-18 04:37:58 +03:00
parent d39cce271f
commit a50e022ab6
8 changed files with 233 additions and 199 deletions

View file

@ -12,54 +12,14 @@ impl Launcher {
}
}
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
pub fn process (_: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
pub fn render (_: &Launcher, _: &mut Buffer, _: Rect) -> Usually<Rect> {
Ok(Rect::default())
}
pub fn handle (state: &mut Launcher, event: &Event) -> Result<(), Box<dyn Error>> {
pub fn handle (_: &mut Launcher, _: &AppEvent) -> Result<(), Box<dyn Error>> {
Ok(())
}
//let mut x = areas[1].x;
//for (index, track) in [
//"Track 1",
//"Track 2",
//"Track 3",
//"Track 4",
//"Track 5",
//"Bus 1",
//"Bus 2",
//"Mix",
//].iter().enumerate() {
//buffer.set_string(
//x + 10 * (index + 1) as u16, areas[1].y,
//"┬", Style::default().not_bold().dim()
//);
//buffer.set_string(
//x + 10 * (index + 1) as u16, areas[1].y + areas[1].height - 1,
//"┴", Style::default().not_bold().dim()
//);
//for y in areas[1].y+1..areas[1].y+areas[1].height - 1 {
//buffer.set_string(
//x + 10 * (index + 1) as u16, y,
//"│", Style::default().not_bold().gray().dim()
//);
//}
//for y in areas[1].y+2..areas[1].y+areas[1].height - 1 {
//buffer.set_string(
//x + 10 * index as u16 + 1, y,
//"--------", Style::default().not_bold().gray().dim()
//);
//}
//buffer.set_string(
//x + 10 * index as u16 + 1, areas[1].y + 1,
//track, Style::default().bold().not_dim()
//);
//}

View file

@ -12,28 +12,15 @@ impl Looper {
}
}
pub fn process (
state: &mut Looper,
client: &Client,
scope: &ProcessScope
) -> Control {
pub fn process (_: &mut Looper, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
pub fn render (state: &Looper, buf: &mut Buffer, area: Rect)
-> Usually<Rect>
{
//let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row);
//stdout
//.queue(move_to(0, 0))?.queue(Print(" Name Input Length Route"))?
//.queue(move_to(0, 1))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))?
//.queue(move_to(0, 2))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))?
//.queue(move_to(0, 3))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))?
//.queue(move_to(0, 4))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))?;
pub fn render (_: &Looper, _: &mut Buffer, _: Rect) -> Usually<Rect> {
Ok(Rect::default())
}
pub fn handle (state: &mut Looper, event: &AppEvent) -> Result<(), Box<dyn Error>> {
pub fn handle (_: &mut Looper, _: &AppEvent) -> Result<(), Box<dyn Error>> {
Ok(())
}

View file

@ -29,9 +29,9 @@ impl Mixer {
}
pub fn process (
mixer: &mut Mixer,
client: &Client,
scope: &ProcessScope
_: &mut Mixer,
_: &Client,
_: &ProcessScope
) -> Control {
Control::Continue
}

View file

@ -12,7 +12,7 @@ impl Plugin {
}
}
pub fn process (state: &mut Plugin, client: &Client, scope: &ProcessScope) -> Control {
pub fn process (_: &mut Plugin, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
@ -31,6 +31,6 @@ pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: R
Ok(Rect { x, y, width: 40, height: 7 })
}
pub fn handle (state: &mut Plugin, event: &AppEvent) -> Result<(), Box<dyn Error>> {
pub fn handle (_: &mut Plugin, _: &AppEvent) -> Result<(), Box<dyn Error>> {
Ok(())
}

View file

@ -5,37 +5,47 @@ type Sequence = std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>;
pub struct Sequencer {
name: String,
mode: SequencerView,
note_axis: (u16, u16),
note_cursor: u16,
time_axis: (u16, u16),
time_cursor: u16,
rate: Hz,
tempo: Tempo,
transport: ::jack::Transport,
input_port: Port<MidiIn>,
input_connect: Vec<String>,
output_port: Port<MidiOut>,
output_connect: Vec<String>,
/// Play sequence to output.
playing: bool,
/// Write input to sequence.
recording: bool,
/// Don't delete when recording.
overdub: bool,
/// Red keys on piano roll.
notes_on: Vec<bool>,
/// Samples per second
rate: Hz,
/// Beats per minute
tempo: Tempo,
/// MIDI resolution (a.k.a. PPQ)
ticks_per_beat: u64,
/// Sequencer resolution, e.g. 16 steps per beat.
steps_per_beat: u64,
/// Steps in sequence, e.g. 64 16ths = 4 beat loop.
steps: u64,
input_port: Port<MidiIn>,
input_connect: Vec<String>,
/// Play input through output.
monitoring: bool,
/// Red keys on piano roll.
notes_on: Vec<bool>,
/// Write input to sequence.
recording: bool,
/// Map: tick -> MIDI events at tick
sequence: Sequence,
/// Don't delete when recording.
overdub: bool,
/// Play sequence to output.
playing: bool,
output_port: Port<MidiOut>,
output_connect: Vec<String>,
/// Display mode
mode: SequencerView,
/// Range of notes to display
note_axis: (u16, u16),
/// Position of cursor within note range
note_cursor: u16,
/// Range of time steps to display
time_axis: (u16, u16),
/// Position of cursor within time range
time_cursor: u16,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
enum SequencerView {
Tiny,
Compact,
@ -55,19 +65,20 @@ impl Sequencer {
time_cursor: 0,
rate: Hz(client.sample_rate() as u32),
tempo: Tempo(120000),
transport: client.transport(),
input_port: client.register_port("in", MidiIn::default())?,
input_connect: vec!["nanoKEY Studio * (capture): *".into()],
output_port: client.register_port("out", MidiOut::default())?,
output_connect: vec![],
sequence: std::collections::BTreeMap::new(),
playing: true,
recording: true,
overdub: true,
notes_on: vec![false;128],
ticks_per_beat: 96,
steps_per_beat: 8,
steps: 64,
transport: client.transport(),
input_port: client.register_port("in", MidiIn::default())?,
input_connect: vec!["nanoKEY Studio * (capture): *".into()],
monitoring: true,
notes_on: vec![false;128],
playing: true,
recording: true,
sequence: std::collections::BTreeMap::new(),
overdub: true,
output_port: client.register_port("out", MidiOut::default())?,
output_connect: vec![],
}).activate(client)
}
@ -101,47 +112,6 @@ impl Sequencer {
#[inline] fn tps (&self) -> f64 {
self.bps() * self.tpb()
}
/// Ticks per loop яснота
#[inline] fn tpl (&self) -> f64 {
self.tpb() * self.steps as f64 / self.steps_per_beat as f64
}
/// Length of sequence in beat notes and remainder.
#[inline] fn beats (&self) -> (u64, u64) {
(self.steps / self.steps_per_beat, self.steps % self.steps_per_beat)
}
/// Length of sequence in ticks, rounded down.
#[inline] fn ticks (&self) -> u64 {
self.steps * self.ticks_per_beat / self.steps_per_beat
}
/// Length of sequence in frames.
#[inline] fn frames (&self) -> u64 {
self.steps * self.usec_per_step().0
}
/// Ticks per step, rounded down.
#[inline] fn ticks_per_step (&self) -> u64 {
self.ticks_per_beat / self.steps_per_beat
}
/// Microseconds per step for current tempo.
#[inline] fn usec_per_step (&self) -> Usec {
self.tempo.usec_per_step(self.steps_per_beat as u64)
}
/// Microseconds per tick for current tempo.
#[inline] fn usec_per_tick (&self) -> Usec {
Usec(self.tempo.usec_per_beat().0 / self.ticks_per_beat)
}
/// Convert frame to microsecond for current sample rate.
#[inline] fn frame_to_usec (&self, frame: u32) -> Usec {
Frame(frame).to_usec(&self.rate)
}
/// Convert frame to tick for current sample rate, tempo, and PPQ.
#[inline] fn frame_to_tick (&self, frame: Frames) -> u32 {
(self.frame_to_usec(frame).0 / self.usec_per_tick().0) as u32
}
/// Convert tick to usec for current sample rate, tempo, and PPQ.
#[inline] fn tick_to_usec (&self, tick: u32) -> u32 {
(tick as u64 * self.usec_per_tick().0) as u32
}
fn frames_to_ticks (&self, start: u64, end: u64) -> Vec<(u64, u64)> {
let fpl = self.fpl() as u64;
@ -254,9 +224,7 @@ fn process_out (s: &mut Sequencer, scope: &ProcessScope) {
}
}
fn render (s: &Sequencer, buf: &mut Buffer, area: Rect)
-> Usually<Rect>
{
fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, .. } = area;
let (time0, time1) = s.time_axis;
let (note0, note1) = s.note_axis;
@ -289,7 +257,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect)
x,
y,
width: header.width.max(piano.width),
height: header.height + piano.height + 1
height: header.height + piano.height + 3
}))
}
@ -398,7 +366,7 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
}
}
if beat % s.steps == step as u64 {
buf.set_string(x + 39 - 2, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow());
buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow());
for key in note0..note1 {
let color = if s.notes_on[key as usize] {
Style::default().red()
@ -408,13 +376,24 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
}
}
}
let height = (time1-time0)/2;
buf.set_string(x + 2, y + height + 1, format!(
"Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
4 * s.steps_per_beat,
s.note_axis.0 + s.note_cursor,
s.note_axis.0,
s.note_axis.1 - 1,
s.time_axis.0 + s.time_cursor + 1,
s.time_axis.0 + 1,
s.time_axis.1,
), Style::default().dim());
buf.set_string(
x + 5 + s.note_cursor,
y + s.time_cursor / 2,
if s.time_cursor % 2 == 0 { "" } else { "" },
Style::default()
);
Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 })
Ok(Rect { x, y, width: area.width, height })
}
fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
@ -462,50 +441,55 @@ fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect
}
}
}
let height = 32.max(note1 - note0) / 2;
buf.set_string(x + 2, y + height + 1, format!(
" Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
4 * s.steps_per_beat,
s.note_axis.0 + s.note_cursor,
s.note_axis.0,
s.note_axis.1 - 1,
s.time_axis.0 + s.time_cursor + 1,
s.time_axis.0 + 1,
s.time_axis.1,
), Style::default().dim());
buf.set_string(
x + 5 + s.time_cursor,
y + s.note_cursor / 2,
if s.note_cursor % 2 == 0 { "" } else { "" },
Style::default()
);
Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 })
Ok(Rect { x, y, width: time1 - time0 + 6, height})
}
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
match event {
AppEvent::Input(Event::Key(event)) => {
for (code, _, _, command) in COMMANDS.iter() {
if *code == event.code {
command(s);
break
}
}
},
_ => {}
};
Ok(())
handle_keymap(COMMANDS, s, event)
}
const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequencer));18] = [
(KeyCode::Up, "cursor_up", "move cursor up", &cursor_up),
(KeyCode::Down, "cursor_down", "move cursor down", &cursor_down),
(KeyCode::Left, "cursor_left", "move cursor left", &cursor_left),
(KeyCode::Right, "cursor_right", "move cursor right", &cursor_right),
(KeyCode::Char(']'), "cursor_inc", "increase note duration", &cursor_inc),
(KeyCode::Char('['), "cursor_dec", "decrease note duration", &cursor_dec),
(KeyCode::Char('`'), "mode_next", "next view mode", &mode_next),
(KeyCode::Tab, "mode_prev", "previous view mode", &mode_prev),
(KeyCode::Char('+'), "zoom_in", "Zoom in", &nop),
(KeyCode::Char('-'), "zoom_out", "Zoom out", &nop),
(KeyCode::Char('a'), "note_add", "Add note", &note_add),
(KeyCode::Char('d'), "note_del", "Delete note", &note_del),
(KeyCode::CapsLock, "advance", "Toggle auto advance", &nop),
(KeyCode::Char('w'), "rest", "Advance by note duration", &nop),
(KeyCode::Char('r'), "record", "Toggle recodring", &toggle_record),
(KeyCode::Char('o'), "overdub", "Toggle overdub", &toggle_overdub),
(KeyCode::Char('p'), "play", "Toggle play/pause", &toggle_play),
(KeyCode::Char('s'), "stop", "Stop and rewind", &nop),
];
pub const COMMANDS: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char(']'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc],
[Char('['), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec],
[Char('`'), NONE, "mode_next", "Next view mode", mode_next],
[Char('+'), NONE, "zoom_in", "Zoom in", nop],
[Char('-'), NONE, "zoom_out", "Zoom out", nop],
[Char('a'), NONE, "note_add", "Add note", note_add],
[Char('d'), NONE, "note_del", "Delete note", note_del],
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
[Char('w'), NONE, "rest", "Advance by note duration", nop],
[Char('r'), NONE, "record", "Toggle recodring", toggle_record],
[Char('o'), NONE, "overdub", "Toggle overdub", toggle_overdub],
[Char('p'), NONE, "play", "Toggle play/pause", toggle_play],
[Char('s'), NONE, "stop", "Stop and rewind", stop_and_rewind],
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
[Char('n'), NONE, "note_axis", "Focus note axis", nop],
[Char('t'), NONE, "time_axis", "Focus time axis", nop],
[Char('v'), NONE, "variations", "Focus variation selector", nop],
[Char('s'), SHIFT, "sync", "Focus sync selector", nop]
});
fn nop (_: &mut Sequencer) {
}
@ -532,21 +516,33 @@ fn note_add (s: &mut Sequencer) {
}
fn note_del (_: &mut Sequencer) {
}
fn cursor_up (s: &mut Sequencer) {
fn time_cursor_inc (s: &mut Sequencer) {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) + 1) % time
}
fn time_cursor_dec (s: &mut Sequencer) {
let time = s.time_axis.1 - s.time_axis.0;
s.time_cursor = ((time + s.time_cursor) - 1) % time
}
fn note_cursor_inc (s: &mut Sequencer) {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) + 1) % note
}
fn note_cursor_dec (s: &mut Sequencer) {
let note = s.note_axis.1 - s.note_axis.0;
s.note_cursor = ((note + s.note_cursor) - 1) % note
}
fn cursor_up (s: &mut Sequencer) {
match s.mode {
SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) - 1) % time },
SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) - 1) % note },
SequencerView::Vertical => time_cursor_dec(s),
SequencerView::Horizontal => note_cursor_dec(s),
_ => unimplemented!()
}
}
fn cursor_down (s: &mut Sequencer) {
let time = s.time_axis.1 - s.time_axis.0;
let note = s.note_axis.1 - s.note_axis.0;
match s.mode {
SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) + 1) % time },
SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) + 1) % note },
SequencerView::Vertical => time_cursor_inc(s),
SequencerView::Horizontal => note_cursor_inc(s),
_ => unimplemented!()
}
}
@ -554,8 +550,8 @@ fn cursor_left (s: &mut Sequencer) {
let time = s.time_axis.1 - s.time_axis.0;
let note = s.note_axis.1 - s.note_axis.0;
match s.mode {
SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) - 1) % note },
SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) - 1) % time },
SequencerView::Vertical => note_cursor_dec(s),
SequencerView::Horizontal => time_cursor_dec(s),
_ => unimplemented!()
}
}
@ -563,22 +559,31 @@ fn cursor_right (s: &mut Sequencer) {
let time = s.time_axis.1 - s.time_axis.0;
let note = s.note_axis.1 - s.note_axis.0;
match s.mode {
SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) + 1) % note },
SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) + 1) % time },
SequencerView::Vertical => note_cursor_inc(s),
SequencerView::Horizontal => time_cursor_inc(s),
_ => unimplemented!()
}
}
fn cursor_inc (s: &mut Sequencer) {
fn cursor_duration_inc (s: &mut Sequencer) {
//s.cursor.2 = s.cursor.2 + 1
}
fn cursor_dec (s: &mut Sequencer) {
fn cursor_duration_dec (s: &mut Sequencer) {
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
}
fn mode_next (s: &mut Sequencer) {
s.mode = SequencerView::Horizontal
s.mode = s.mode.next()
}
fn mode_prev (s: &mut Sequencer) {
s.mode = SequencerView::Vertical
impl SequencerView {
fn next (&self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
_ => self.clone()
}
}
}
fn stop_and_rewind (s: &mut Sequencer) {
s.playing = false
}
fn toggle_play (s: &mut Sequencer) {
s.playing = !s.playing
@ -589,6 +594,16 @@ fn toggle_record (s: &mut Sequencer) {
fn toggle_overdub (s: &mut Sequencer) {
s.overdub = !s.overdub
}
fn quantize_next (s: &mut Sequencer) {
if s.steps_per_beat < 64 {
s.steps_per_beat = s.steps_per_beat * 2
}
}
fn quantize_prev (s: &mut Sequencer) {
if s.steps_per_beat > 1 {
s.steps_per_beat = s.steps_per_beat / 2
}
}
#[cfg(test)] mod test {
use super::*;

View file

@ -46,9 +46,7 @@ impl Device for Rows {
self.focused = true;
self.items[self.focus].handle(&AppEvent::Blur)?;
},
_ => {
println!("{event:?}");
}
_ => {}
},
_ => {}
}
@ -94,9 +92,42 @@ impl Columns {
impl Device for Columns {
fn handle (&mut self, event: &AppEvent) -> Usually<()> {
if self.focused {
self.items[self.focus].handle(event)
if !self.focused {
match event {
AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
self.focused = true;
self.items[self.focus].handle(&AppEvent::Blur)?;
Ok(())
},
_ => self.items[self.focus].handle(event)
}
} else {
match event {
AppEvent::Input(event) => match event {
Event::Key(KeyEvent { code: KeyCode::Left, .. }) => {
if self.focus == 0 {
self.focus = self.items.len();
}
self.focus = self.focus - 1;
},
Event::Key(KeyEvent { code: KeyCode::Right, .. }) => {
self.focus = self.focus + 1;
if self.focus >= self.items.len() {
self.focus = 0;
}
},
Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => {
self.focused = false;
self.items[self.focus].handle(&AppEvent::Focus)?;
},
Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => {
self.focused = true;
self.items[self.focus].handle(&AppEvent::Blur)?;
},
_ => {}
},
_ => {}
}
Ok(())
}
}
@ -111,7 +142,7 @@ impl Device for Columns {
height: area.height
})?;
if self.focused && i == self.focus {
draw_box_styled(buf, result, Some(Style::default().yellow()));
draw_box_styled(buf, result, Some(Style::default().green()));
}
w = w + result.width;
h = h.max(result.height);

View file

@ -13,8 +13,8 @@ pub mod config;
pub mod layout;
pub mod time;
use crate::device::{Sequencer, Transport};
use crate::layout::Rows;
use crate::device::*;
use crate::layout::*;
fn main () -> Result<(), Box<dyn Error>> {
let _cli = cli::Cli::parse();
@ -33,8 +33,12 @@ fn main () -> Result<(), Box<dyn Error>> {
//])?),
//])),
//Box::new(Mixer::new("Mixer#000")?),
Box::new(Sequencer::new("Melody#000")?),
Box::new(Transport::new("Transport")?),
Box::new(Columns::new(true, vec![
Box::new(Sequencer::new("Melody#000")?),
Box::new(Sequencer::new("Melody#001")?),
Box::new(Sequencer::new("Rhythm#000")?),
])),
//Box::new(Sequencer::new("Rhythm#000")?),
]))
}

View file

@ -85,3 +85,40 @@ pub type BoxedNotificationHandler = Box<dyn Fn(AppEvent) + Send>;
pub type BoxedProcessHandler = Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
pub type Jack<N> = AsyncClient<N, ClosureProcessHandler<BoxedProcessHandler>>;
pub type KeyBinding<T> = (
KeyCode, KeyModifiers, &'static str, &'static str, &'static dyn Fn(&mut T)
);
#[macro_export] macro_rules! key {
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: ident) => {
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f)
};
}
#[macro_export] macro_rules! keymap {
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: ident]),* }) => {
&[
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &'static dyn Fn(&mut $T))),*
] as &'static [KeyBinding<$T>]
}
}
pub use crate::{key, keymap};
pub fn handle_keymap <T> (
commands: &[KeyBinding<T>], state: &mut T, event: &AppEvent
) -> Result<(), Box<dyn Error>> {
match event {
AppEvent::Input(Event::Key(event)) => {
for (code, modifiers, _, _, command) in commands.iter() {
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
command(state);
break
}
}
},
_ => {}
};
Ok(())
}