tek/crates/tek_tui/src/tui_input.rs
2024-11-21 16:34:44 +01:00

393 lines
19 KiB
Rust

use crate::*;
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use KeyCode::Char;
use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportFocus as Focused;
use TransportCommand::{Focus, Clock};
let focused = state.transport_focused();
Some(match input.event() {
key!(Left) => Focus(FocusCommand::Prev),
key!(Right) => Focus(FocusCommand::Next),
key!(Char('.')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)),
Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Clock(todo!()),
Focused::Clock => Clock(todo!()),
_ => {todo!()}
},
key!(Char(',')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)),
Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Clock(todo!()),
Focused::Clock => Clock(todo!()),
_ => {todo!()}
},
key!(Char('>')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)),
Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Clock(todo!()),
Focused::Clock => Clock(todo!()),
_ => {todo!()}
},
key!(Char('<')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)),
Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Clock(todo!()),
Focused::Clock => Clock(todo!()),
_ => {todo!()}
},
_ => return None
})
}
}
impl<T> InputToCommand<Tui, T> for SequencerCommand
where
T: SequencerControl + TransportControl + PhrasesControl + PhraseEditorControl
+ HasFocus<Item = SequencerFocus>
+ FocusGrid<Item = SequencerFocus>
{
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use FocusCommand::*;
use SequencerCommand::*;
match input.event() {
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)),
key!(KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(KeyCode::Up) => Some(Self::Focus(Up)),
key!(KeyCode::Down) => Some(Self::Focus(Down)),
key!(KeyCode::Left) => Some(Self::Focus(Left)),
key!(KeyCode::Right) => Some(Self::Focus(Right)),
_ => Some(match state.focused() {
SequencerFocus::Transport => {
use TransportCommand::{Clock, Focus};
match TransportCommand::input_to_command(state, input)? {
Clock(command) => { todo!() },
Focus(command) => { todo!() },
}
},
SequencerFocus::Phrases =>
Phrases(PhrasesCommand::input_to_command(state, input)?),
SequencerFocus::PhraseEditor =>
Editor(PhraseCommand::input_to_command(state, input)?),
_ => return None,
})
}
}
}
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
fn input_to_command (state: &ArrangerTui, input: &TuiInput) -> Option<Self> {
use FocusCommand::*;
use ArrangerCommand::*;
Some(match input.event() {
key!(KeyCode::Tab) => Self::Focus(Next),
key!(Shift-KeyCode::Tab) => Self::Focus(Prev),
key!(KeyCode::BackTab) => Self::Focus(Prev),
key!(Shift-KeyCode::BackTab) => Self::Focus(Prev),
key!(KeyCode::Up) => Self::Focus(Up),
key!(KeyCode::Down) => Self::Focus(Down),
key!(KeyCode::Left) => Self::Focus(Left),
key!(KeyCode::Right) => Self::Focus(Right),
key!(KeyCode::Enter) => Self::Focus(Enter),
key!(KeyCode::Esc) => Self::Focus(Exit),
key!(KeyCode::Char(' ')) => Self::Clock(ClockCommand::Play(None)),
_ => match state.focused() {
ArrangerFocus::Menu => { todo!() },
ArrangerFocus::Transport => {
use TransportCommand::{Clock, Focus};
match TransportCommand::input_to_command(state, input)? {
Clock(command) => { todo!() },
Focus(command) => { todo!() }
}
},
ArrangerFocus::PhraseEditor => Editor(
PhraseCommand::input_to_command(state, input)?
),
ArrangerFocus::Phrases => match input.event() {
key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()),
_ => Phrases(PhrasesCommand::input_to_command(state, input)?)
},
ArrangerFocus::Arranger => {
use ArrangerSelection as Select;
use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip;
use ArrangerSceneCommand as Scene;
match input.event() {
key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()),
_ => match input.event() {
// FIXME: boundary conditions
key!(KeyCode::Up) => match state.selected() {
Select::Mix => return None,
Select::Track(t) => return None,
Select::Scene(s) => if s > 0 {
Select(Select::Scene(s - 1))
} else {
Select(Select::Mix)
},
Select::Clip(t, s) => if s > 0 {
Select(Select::Clip(t, s - 1))
} else {
Select(Select::Track(t))
},
},
key!(KeyCode::Down) => match state.selected() {
Select::Mix => Select(Select::Scene(0)),
Select::Track(t) => Select(Select::Clip(t, 0)),
Select::Scene(s) => Select(Select::Scene(s + 1)),
Select::Clip(t, s) => Select(Select::Clip(t, s + 1)),
},
key!(KeyCode::Left) => match state.selected() {
Select::Mix => return None,
Select::Track(t) => if t > 0 {
Select(Select::Track(t - 1))
} else {
Select(Select::Mix)
},
Select::Scene(s) => return None,
Select::Clip(t, s) => if t > 0 {
Select(Select::Clip(t - 1, s))
} else {
Select(Select::Scene(s))
},
},
key!(KeyCode::Right) => match state.selected() {
Select::Mix => return None,
Select::Track(t) => Select(Select::Track(t + 1)),
Select::Scene(s) => Select(Select::Clip(0, s)),
Select::Clip(t, s) => Select(Select::Clip(t, s - 1)),
},
key!(KeyCode::Char('+')) => Zoom(0),
key!(KeyCode::Char('=')) => Zoom(0),
key!(KeyCode::Char('_')) => Zoom(0),
key!(KeyCode::Char('-')) => Zoom(0),
key!(KeyCode::Char('`')) => { todo!("toggle state mode") },
key!(KeyCode::Char(',')) => match state.selected() {
Select::Mix => Zoom(0),
Select::Track(t) => Track(Track::Swap(t, t - 1)),
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Char('.')) => match state.selected() {
Select::Mix => Zoom(0),
Select::Track(t) => Track(Track::Swap(t, t + 1)),
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Char('<')) => match state.selected() {
Select::Mix => Zoom(0),
Select::Track(t) => Track(Track::Swap(t, t - 1)),
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Char('>')) => match state.selected() {
Select::Mix => Zoom(0),
Select::Track(t) => Track(Track::Swap(t, t + 1)),
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Enter) => match state.selected() {
Select::Mix => return None,
Select::Track(t) => return None,
Select::Scene(s) => Scene(Scene::Play(s)),
Select::Clip(t, s) => return None,
},
key!(KeyCode::Delete) => match state.selected() {
Select::Mix => Clear,
Select::Track(t) => Track(Track::Delete(t)),
Select::Scene(s) => Scene(Scene::Delete(s)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Char('c')) => match state.selected() {
Select::Mix => Color(ItemColor::random()),
Select::Track(t) => Track(Track::Delete(t)),
Select::Scene(s) => Scene(Scene::Delete(s)),
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
},
key!(KeyCode::Char('s')) => match state.selected() {
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
_ => return None,
},
key!(KeyCode::Char('g')) => match state.selected() {
Select::Clip(t, s) => Clip(Clip::Get(t, s)),
_ => return None,
},
key!(Ctrl-KeyCode::Char('a')) => Scene(Scene::Add),
key!(Ctrl-KeyCode::Char('t')) => Track(Track::Add),
key!(KeyCode::Char('l')) => Clip(Clip::SetLoop(false)),
_ => return None
}
}
}
}
})
}
}
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use PhrasePoolCommand as Phrase;
use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length;
let index = state.phrase_index();
let count = state.phrases().len();
match input.event() {
key!(KeyCode::Up) => Some(Self::Select(0)),
key!(KeyCode::Down) => Some(Self::Select(0)),
key!(KeyCode::Char(',')) => {
if index > 1 {
state.set_phrase_index(state.phrase_index().saturating_sub(1));
Some(Self::Phrase(Phrase::Swap(index - 1, index)))
} else {
None
}
},
key!(KeyCode::Char('.')) => {
if index < count.saturating_sub(1) {
state.set_phrase_index(state.phrase_index() + 1);
Some(Self::Phrase(Phrase::Swap(index + 1, index)))
} else {
None
}
},
key!(KeyCode::Delete) => {
if index > 0 {
state.set_phrase_index(index.min(count.saturating_sub(1)));
Some(Self::Phrase(Phrase::Delete(index)))
} else {
None
}
},
key!(KeyCode::Char('a')) => Some(Self::Phrase(Phrase::Add(count))),
key!(KeyCode::Char('i')) => Some(Self::Phrase(Phrase::Add(index + 1))),
key!(KeyCode::Char('d')) => Some(Self::Phrase(Phrase::Duplicate(index))),
key!(KeyCode::Char('c')) => Some(Self::Phrase(Phrase::Color(index, ItemColor::random()))),
key!(KeyCode::Char('n')) => Some(Self::Rename(Rename::Begin)),
key!(KeyCode::Char('t')) => Some(Self::Length(Length::Begin)),
_ => match state.phrases_mode() {
Some(PhrasesMode::Rename(..)) => {
Rename::input_to_command(state, input).map(Self::Rename)
},
Some(PhrasesMode::Length(..)) => {
Length::input_to_command(state, input).map(Self::Length)
},
_ => None
}
}
}
}
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhraseLengthCommand {
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
if let Some(PhrasesMode::Length(_, length, _)) = state.phrases_mode() {
Some(match from.event() {
key!(KeyCode::Up) => Self::Inc,
key!(KeyCode::Down) => Self::Dec,
key!(KeyCode::Right) => Self::Next,
key!(KeyCode::Left) => Self::Prev,
key!(KeyCode::Enter) => Self::Set(*length),
key!(KeyCode::Esc) => Self::Cancel,
_ => return None
})
} else {
unreachable!()
}
}
}
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhraseRenameCommand {
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
if let Some(PhrasesMode::Rename(_, ref old_name)) = state.phrases_mode() {
Some(match from.event() {
key!(KeyCode::Char(c)) => {
let mut new_name = old_name.clone();
new_name.push(*c);
Self::Set(new_name)
},
key!(KeyCode::Backspace) => {
let mut new_name = old_name.clone();
new_name.pop();
Self::Set(new_name)
},
key!(KeyCode::Enter) => Self::Confirm,
key!(KeyCode::Esc) => Self::Cancel,
_ => return None
})
} else {
unreachable!()
}
}
}
impl<T: PhraseEditorControl> InputToCommand<Tui, T> for PhraseCommand {
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
use PhraseCommand::*;
Some(match from.event() {
key!(KeyCode::Char('`')) => ToggleDirection,
key!(KeyCode::Enter) => EnterEditMode,
key!(KeyCode::Esc) => ExitEditMode,
key!(KeyCode::Char('a')) => NoteAppend,
key!(KeyCode::Char('s')) => NoteSet,
key!(KeyCode::Char('[')) => NoteLengthSet(prev_note_length(state.note_len())),
key!(KeyCode::Char(']')) => NoteLengthSet(next_note_length(state.note_len())),
key!(KeyCode::Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
key!(KeyCode::Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
key!(KeyCode::Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
key!(KeyCode::Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
key!(KeyCode::Up) => match state.phrase_editor_entered() {
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)),
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)),
},
key!(KeyCode::Down) => match state.phrase_editor_entered() {
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)),
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)),
},
key!(KeyCode::PageUp) => match state.phrase_editor_entered() {
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)),
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)),
},
key!(KeyCode::PageDown) => match state.phrase_editor_entered() {
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)),
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(3)),
},
key!(KeyCode::Left) => match state.phrase_editor_entered() {
true => TimeCursorSet(state.note_axis().write().unwrap().point_minus(1)),
false => TimeScrollSet(state.note_axis().write().unwrap().start_minus(1)),
},
key!(KeyCode::Right) => match state.phrase_editor_entered() {
true => TimeCursorSet(state.note_axis().write().unwrap().point_plus(1)),
false => TimeScrollSet(state.note_axis().write().unwrap().start_plus(1)),
},
_ => return None
})
}
}