mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-10 21:56:42 +01:00
wip: refactor: cli, snd, tui
This commit is contained in:
parent
70fc3c97d1
commit
84aacfea82
58 changed files with 416 additions and 191 deletions
494
crates/tek_tui/src/sequencer_tui.rs
Normal file
494
crates/tek_tui/src/sequencer_tui.rs
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
use crate::*;
|
||||
impl Content for Sequencer<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Stack::down(move|add|{
|
||||
add(&self.transport)?;
|
||||
add(&self.phrases.clone()
|
||||
.split(Direction::Right, 20, &self.editor as &dyn Widget<Engine = Tui>)
|
||||
.min_y(20))
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for Sequencer<Tui> {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
if let Some(entered) = self.entered() {
|
||||
use SequencerFocus::*;
|
||||
if let Some(true) = match entered {
|
||||
Transport => self.transport.as_mut().map(|t|t.handle(i)).transpose()?.flatten(),
|
||||
PhrasePool => self.phrases.write().unwrap().handle(i)?,
|
||||
PhraseEditor => self.editor.handle(i)?,
|
||||
} {
|
||||
return Ok(Some(true))
|
||||
}
|
||||
}
|
||||
if let Some(command) = SequencerCommand::input_to_command(self, i) {
|
||||
let _undo = command.execute(self)?;
|
||||
return Ok(Some(true))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, Sequencer<Tui>> for SequencerCommand {
|
||||
fn input_to_command (state: &Sequencer<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use SequencerCommand::*;
|
||||
use FocusCommand::*;
|
||||
match input.event() {
|
||||
key!(KeyCode::Tab) => Some(Focus(Next)),
|
||||
key!(Shift-KeyCode::Tab) => Some(Focus(Prev)),
|
||||
key!(KeyCode::BackTab) => Some(Focus(Prev)),
|
||||
key!(Shift-KeyCode::BackTab) => Some(Focus(Prev)),
|
||||
key!(KeyCode::Up) => Some(Focus(Up)),
|
||||
key!(KeyCode::Down) => Some(Focus(Down)),
|
||||
key!(KeyCode::Left) => Some(Focus(Left)),
|
||||
key!(KeyCode::Right) => Some(Focus(Right)),
|
||||
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
|
||||
_ => match state.focused() {
|
||||
SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() {
|
||||
TransportCommand::input_to_command(&*t.read().unwrap(), input).map(Transport)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
SequencerFocus::PhrasePool =>
|
||||
PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input)
|
||||
.map(Phrases),
|
||||
SequencerFocus::PhraseEditor =>
|
||||
PhraseEditorCommand::input_to_command(&state.editor, input)
|
||||
.map(Editor),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Display phrases always in order of appearance
|
||||
impl Content for PhrasePool<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { focused, phrases, mode, .. } = self;
|
||||
let content = col!(
|
||||
(i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{
|
||||
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
|
||||
let mut length = PhraseLength::new(length, None);
|
||||
if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode {
|
||||
if *focused && i == *phrase {
|
||||
length.pulses = *new_length;
|
||||
length.focus = Some(*focus);
|
||||
}
|
||||
}
|
||||
let length = length.align_e().fill_x();
|
||||
let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x();
|
||||
let mut row2 = format!(" {name}");
|
||||
if let Some(PhrasePoolMode::Rename(phrase, _)) = mode {
|
||||
if *focused && i == *phrase { row2 = format!("{row2}▄"); }
|
||||
};
|
||||
let row2 = TuiStyle::bold(row2, true);
|
||||
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
|
||||
Ok(if *focused && i == self.phrase { add(&CORNERS)?; })
|
||||
})
|
||||
);
|
||||
let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
|
||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
|
||||
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
||||
let upper_left = format!("[{}] Phrases", if self.entered {"■"} else {" "});
|
||||
let upper_right = format!("({})", phrases.len());
|
||||
lay!(
|
||||
content,
|
||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
||||
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for PhrasePool<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
if let Some(command) = PhrasePoolCommand::input_to_command(self, from) {
|
||||
let _undo = command.execute(self)?;
|
||||
return Ok(Some(true))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, PhrasePool<Tui>> for PhrasePoolCommand {
|
||||
fn input_to_command (state: &PhrasePool<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
match input.event() {
|
||||
key!(KeyCode::Up) => Some(Self::Prev),
|
||||
key!(KeyCode::Down) => Some(Self::Next),
|
||||
key!(KeyCode::Char(',')) => Some(Self::MoveUp),
|
||||
key!(KeyCode::Char('.')) => Some(Self::MoveDown),
|
||||
key!(KeyCode::Delete) => Some(Self::Delete),
|
||||
key!(KeyCode::Char('a')) => Some(Self::Append),
|
||||
key!(KeyCode::Char('i')) => Some(Self::Insert),
|
||||
key!(KeyCode::Char('d')) => Some(Self::Duplicate),
|
||||
key!(KeyCode::Char('c')) => Some(Self::RandomColor),
|
||||
key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)),
|
||||
key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)),
|
||||
_ => match state.mode {
|
||||
Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::input_to_command(state, input)
|
||||
.map(Self::Rename),
|
||||
Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::input_to_command(state, input)
|
||||
.map(Self::Length),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseRenameCommand {
|
||||
fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Backspace) => Some(Self::Backspace),
|
||||
key!(KeyCode::Enter) => Some(Self::Confirm),
|
||||
key!(KeyCode::Esc) => Some(Self::Cancel),
|
||||
key!(KeyCode::Char(c)) => Some(Self::Append(*c)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseLengthCommand {
|
||||
fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Up) => Some(Self::Inc),
|
||||
key!(KeyCode::Down) => Some(Self::Dec),
|
||||
key!(KeyCode::Right) => Some(Self::Next),
|
||||
key!(KeyCode::Left) => Some(Self::Prev),
|
||||
key!(KeyCode::Enter) => Some(Self::Confirm),
|
||||
key!(KeyCode::Esc) => Some(Self::Cancel),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Content for PhraseLength<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Layers::new(move|add|{
|
||||
match self.focus {
|
||||
None => add(&row!(
|
||||
" ", self.bars_string(),
|
||||
".", self.beats_string(),
|
||||
".", self.ticks_string(),
|
||||
" "
|
||||
)),
|
||||
Some(PhraseLengthFocus::Bar) => add(&row!(
|
||||
"[", self.bars_string(),
|
||||
"]", self.beats_string(),
|
||||
".", self.ticks_string(),
|
||||
" "
|
||||
)),
|
||||
Some(PhraseLengthFocus::Beat) => add(&row!(
|
||||
" ", self.bars_string(),
|
||||
"[", self.beats_string(),
|
||||
"]", self.ticks_string(),
|
||||
" "
|
||||
)),
|
||||
Some(PhraseLengthFocus::Tick) => add(&row!(
|
||||
" ", self.bars_string(),
|
||||
".", self.beats_string(),
|
||||
"[", self.ticks_string(),
|
||||
"]"
|
||||
)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Content for PhraseEditor<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self;
|
||||
let FixedAxis {
|
||||
start: note_start, point: note_point, clamp: note_clamp
|
||||
} = *self.note_axis.read().unwrap();
|
||||
let ScaledAxis {
|
||||
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
|
||||
} = *self.time_axis.read().unwrap();
|
||||
//let color = Color::Rgb(0,255,0);
|
||||
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||
Ok(if to.area().h() >= 2 {
|
||||
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
|
||||
let y = y + (note_start / 2) as u16;
|
||||
if x < keys.area.width && y < keys.area.height {
|
||||
*cell = keys.get(x, y).clone()
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_y();
|
||||
let notes_bg_null = Color::Rgb(28, 35, 25);
|
||||
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
let h = area.h() as usize;
|
||||
self.height.store(h, Ordering::Relaxed);
|
||||
self.width.store(area.w() as usize, Ordering::Relaxed);
|
||||
let mut axis = self.note_axis.write().unwrap();
|
||||
if let Some(point) = axis.point {
|
||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||
axis.start += 2;
|
||||
}
|
||||
}
|
||||
Ok(if to.area().h() >= 2 {
|
||||
let area = to.area();
|
||||
to.buffer_update(area, &move |cell, x, y|{
|
||||
cell.set_bg(notes_bg_null);
|
||||
let src_x = (x as usize + time_start) * time_scale;
|
||||
let src_y = y as usize + note_start / 2;
|
||||
if src_x < buffer.width && src_y < buffer.height - 1 {
|
||||
buffer.get(src_x, buffer.height - src_y - 2).map(|src|{
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
cell.set_bg(src.bg);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_x();
|
||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
Ok(if *focused && *entered {
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||
let x1 = area.x() + (time / time_scale) as u16;
|
||||
let x2 = x1 + (self.note_len / time_scale) as u16;
|
||||
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
|
||||
let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
|
||||
let playhead = CustomWidget::new(
|
||||
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
||||
move|to: &mut TuiOutput|{
|
||||
if let Some(_) = phrase {
|
||||
let now = self.now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||
let time_clamp = time_clamp
|
||||
.expect("time_axis of sequencer expected to be clamped");
|
||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||
let this_step = time_start + (x + 0) * time_scale;
|
||||
let next_step = time_start + (x + 1) * time_scale;
|
||||
let x = to.area().x() + x as u16;
|
||||
let active = this_step <= now && now < next_step;
|
||||
let character = if active { "|" } else { "·" };
|
||||
let style = if active { playhead_active } else { playhead_inactive };
|
||||
to.blit(&character, x, to.area.y(), Some(style));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).push_x(6).align_sw();
|
||||
let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
|
||||
let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let note_area = lay!(notes, cursor).fill_x();
|
||||
let piano_roll = row!(keys, note_area).fill_x();
|
||||
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
|
||||
let content = lay!(content, playhead);
|
||||
let mut upper_left = format!("[{}] Sequencer", if *entered {"■"} else {" "});
|
||||
if let Some(phrase) = phrase {
|
||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||
}
|
||||
let mut lower_right = format!(
|
||||
"┤{}x{}├",
|
||||
self.width.load(Ordering::Relaxed),
|
||||
self.height.load(Ordering::Relaxed),
|
||||
);
|
||||
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
||||
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||
//pulses_to_name(time_scale),
|
||||
//time_start, time_point.unwrap_or(0),
|
||||
//time_scale, time_clamp.unwrap_or(0),
|
||||
//);
|
||||
if *focused && *entered {
|
||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||
self.note_axis.read().unwrap().point.unwrap(),
|
||||
pulses_to_name(*note_len));
|
||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
//pulses_to_name(*note_len),
|
||||
//note_start,
|
||||
//note_point.unwrap_or(0),
|
||||
//note_clamp.unwrap_or(0),
|
||||
//);
|
||||
}
|
||||
let upper_right = if let Some(phrase) = phrase {
|
||||
format!("┤Length: {}├", phrase.read().unwrap().length)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
lay!(
|
||||
content,
|
||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
||||
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
|
||||
TuiStyle::fg(lower_right.to_string(), title_color).pull_x(1).align_se().fill_xy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<E: Engine> PhraseEditor<E> {
|
||||
pub fn put (&mut self) {
|
||||
if let (Some(phrase), Some(time), Some(note)) = (
|
||||
&self.phrase,
|
||||
self.time_axis.read().unwrap().point,
|
||||
self.note_axis.read().unwrap().point,
|
||||
) {
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let key: u7 = u7::from((127 - note) as u8);
|
||||
let vel: u7 = 100.into();
|
||||
let start = time;
|
||||
let end = (start + self.note_len) % phrase.length;
|
||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||
self.buffer = Self::redraw(&phrase);
|
||||
}
|
||||
}
|
||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||
if let Some(phrase) = phrase {
|
||||
self.phrase = Some(phrase.clone());
|
||||
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
||||
} else {
|
||||
self.phrase = None;
|
||||
self.time_axis.write().unwrap().clamp = Some(0);
|
||||
self.buffer = Default::default();
|
||||
}
|
||||
}
|
||||
fn redraw (phrase: &Phrase) -> BigBuffer {
|
||||
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
|
||||
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
|
||||
Self::fill_seq_fg(&mut buf, &phrase);
|
||||
buf
|
||||
}
|
||||
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
|
||||
for x in 0..buf.width {
|
||||
// Only fill as far as phrase length
|
||||
if x as usize >= length { break }
|
||||
// Fill each row with background characters
|
||||
for y in 0 .. buf.height {
|
||||
buf.get_mut(x, y).map(|cell|{
|
||||
cell.set_char(if ppq == 0 {
|
||||
'·'
|
||||
} else if x % (4 * ppq) == 0 {
|
||||
'│'
|
||||
} else if x % ppq == 0 {
|
||||
'╎'
|
||||
} else {
|
||||
'·'
|
||||
});
|
||||
cell.set_fg(Color::Rgb(48, 64, 56));
|
||||
cell.modifier = Modifier::DIM;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
|
||||
let mut notes_on = [false;128];
|
||||
for x in 0..buf.width {
|
||||
if x as usize >= phrase.length {
|
||||
break
|
||||
}
|
||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||
if phrase.percussive {
|
||||
for note in notes {
|
||||
match note {
|
||||
MidiMessage::NoteOn { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for note in notes {
|
||||
match note {
|
||||
MidiMessage::NoteOn { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = true,
|
||||
MidiMessage::NoteOff { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
for y in 0..buf.height {
|
||||
if y >= 64 {
|
||||
break
|
||||
}
|
||||
if let Some(block) = half_block(
|
||||
notes_on[y as usize * 2],
|
||||
notes_on[y as usize * 2 + 1],
|
||||
) {
|
||||
buf.get_mut(x, y).map(|cell|{
|
||||
cell.set_char(block);
|
||||
cell.set_fg(Color::White);
|
||||
});
|
||||
}
|
||||
}
|
||||
if phrase.percussive {
|
||||
notes_on.fill(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Colors of piano keys
|
||||
const KEY_COLORS: [(Color, Color);6] = [
|
||||
(Color::Rgb(255, 255, 255), Color::Rgb(255, 255, 255)),
|
||||
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
|
||||
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
|
||||
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
|
||||
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
|
||||
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
|
||||
];
|
||||
pub(crate) fn keys_vert () -> Buffer {
|
||||
let area = [0, 0, 5, 64];
|
||||
let mut buffer = Buffer::empty(Rect {
|
||||
x: area.x(), y: area.y(), width: area.w(), height: area.h()
|
||||
});
|
||||
buffer_update(&mut buffer, area, &|cell, x, y| {
|
||||
let y = 63 - y;
|
||||
match x {
|
||||
0 => {
|
||||
cell.set_char('▀');
|
||||
let (fg, bg) = KEY_COLORS[((6 - y % 6) % 6) as usize];
|
||||
cell.set_fg(fg);
|
||||
cell.set_bg(bg);
|
||||
},
|
||||
1 => {
|
||||
cell.set_char('▀');
|
||||
cell.set_fg(Color::White);
|
||||
cell.set_bg(Color::White);
|
||||
},
|
||||
2 => if y % 6 == 0 { cell.set_char('C'); },
|
||||
3 => if y % 6 == 0 { cell.set_symbol(NTH_OCTAVE[(y / 6) as usize]); },
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
buffer
|
||||
}
|
||||
impl Handle<Tui> for PhraseEditor<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
if let Some(command) = PhraseEditorCommand::input_to_command(self, from) {
|
||||
let _undo = command.execute(self)?;
|
||||
return Ok(Some(true))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
|
||||
fn input_to_command (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char('`')) => Some(Self::ToggleDirection),
|
||||
key!(KeyCode::Enter) => Some(Self::EnterEditMode),
|
||||
key!(KeyCode::Esc) => Some(Self::ExitEditMode),
|
||||
key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec),
|
||||
key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc),
|
||||
key!(KeyCode::Char('a')) => Some(Self::NoteAppend),
|
||||
key!(KeyCode::Char('s')) => Some(Self::NoteSet),
|
||||
key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut),
|
||||
key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut),
|
||||
key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn),
|
||||
key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn),
|
||||
key!(KeyCode::PageUp) => Some(Self::NotePageUp),
|
||||
key!(KeyCode::PageDown) => Some(Self::NotePageDown),
|
||||
key!(KeyCode::Up) => Some(Self::GoUp),
|
||||
key!(KeyCode::Down) => Some(Self::GoDown),
|
||||
key!(KeyCode::Left) => Some(Self::GoLeft),
|
||||
key!(KeyCode::Right) => Some(Self::GoRight),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue