All checks were successful
Deploy Website / deploy (push) Has been skipped
228 lines
9.3 KiB
Rust
228 lines
9.3 KiB
Rust
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use super::{InputContext, InputResult};
|
|
use crate::commands::AppCommand;
|
|
use crate::state::{
|
|
ConfirmAction, CyclicEnum, EuclideanField, Modal, PanelFocus, PatternField, RenameTarget,
|
|
SampleBrowserState, SidePanel,
|
|
};
|
|
|
|
pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputResult {
|
|
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
|
|
|
|
match key.code {
|
|
KeyCode::Tab => {
|
|
if ctx.app.panel.visible {
|
|
ctx.app.panel.visible = false;
|
|
ctx.app.panel.focus = PanelFocus::Main;
|
|
} else {
|
|
let state = SampleBrowserState::new(&ctx.app.audio.config.sample_paths);
|
|
ctx.app.panel.side = Some(SidePanel::SampleBrowser(state));
|
|
ctx.app.panel.visible = true;
|
|
ctx.app.panel.focus = PanelFocus::Side;
|
|
}
|
|
}
|
|
KeyCode::Char('q') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
|
action: ConfirmAction::Quit,
|
|
selected: false,
|
|
}));
|
|
}
|
|
KeyCode::Char(' ') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::TogglePlaying);
|
|
ctx.playing
|
|
.store(ctx.app.playback.playing, Ordering::Relaxed);
|
|
}
|
|
KeyCode::Left if shift && !ctrl => shift_navigate(ctx, AppCommand::PrevStep),
|
|
KeyCode::Right if shift && !ctrl => shift_navigate(ctx, AppCommand::NextStep),
|
|
KeyCode::Up if shift && !ctrl => shift_navigate(ctx, AppCommand::StepUp),
|
|
KeyCode::Down if shift && !ctrl => shift_navigate(ctx, AppCommand::StepDown),
|
|
KeyCode::Left => navigate(ctx, AppCommand::PrevStep),
|
|
KeyCode::Right => navigate(ctx, AppCommand::NextStep),
|
|
KeyCode::Up => navigate(ctx, AppCommand::StepUp),
|
|
KeyCode::Down => navigate(ctx, AppCommand::StepDown),
|
|
KeyCode::Esc => {
|
|
ctx.app.editor_ctx.clear_selection();
|
|
}
|
|
KeyCode::Enter => {
|
|
ctx.app.editor_ctx.clear_selection();
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Editor));
|
|
}
|
|
KeyCode::Char('t') => ctx.dispatch(AppCommand::ToggleSteps),
|
|
KeyCode::Char('s') => super::open_save(ctx),
|
|
KeyCode::Char('z') if ctrl && !shift => {
|
|
ctx.dispatch(AppCommand::Undo);
|
|
}
|
|
KeyCode::Char('Z') if ctrl => {
|
|
ctx.dispatch(AppCommand::Redo);
|
|
}
|
|
KeyCode::Char('c') if ctrl => {
|
|
ctx.dispatch(AppCommand::CopySteps);
|
|
}
|
|
KeyCode::Char('v') if ctrl => {
|
|
ctx.dispatch(AppCommand::PasteSteps);
|
|
}
|
|
KeyCode::Char('b') if ctrl => {
|
|
ctx.dispatch(AppCommand::LinkPasteSteps);
|
|
}
|
|
KeyCode::Char('d') if ctrl => {
|
|
ctx.dispatch(AppCommand::DuplicateSteps);
|
|
}
|
|
KeyCode::Char('h') if ctrl => ctx.dispatch(AppCommand::HardenSteps),
|
|
KeyCode::Char('l') => super::open_load(ctx),
|
|
KeyCode::Char('+') | KeyCode::Char('=') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::TempoUp);
|
|
}
|
|
KeyCode::Char('-') if !ctx.app.plugin_mode => ctx.dispatch(AppCommand::TempoDown),
|
|
KeyCode::Char('T') if !ctx.app.plugin_mode => {
|
|
let current = format!("{:.1}", ctx.link.tempo());
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::SetTempo(current)));
|
|
}
|
|
KeyCode::Char(':') => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::JumpToStep(String::new())));
|
|
}
|
|
KeyCode::Char('<') | KeyCode::Char(',') => ctx.dispatch(AppCommand::LengthDecrease),
|
|
KeyCode::Char('>') | KeyCode::Char('.') => ctx.dispatch(AppCommand::LengthIncrease),
|
|
KeyCode::Char('[') => ctx.dispatch(AppCommand::SpeedDecrease),
|
|
KeyCode::Char(']') => ctx.dispatch(AppCommand::SpeedIncrease),
|
|
KeyCode::Char('L') => ctx.dispatch(AppCommand::OpenPatternModal(PatternField::Length)),
|
|
KeyCode::Char('S') => ctx.dispatch(AppCommand::OpenPatternModal(PatternField::Speed)),
|
|
KeyCode::Char('p') => ctx.dispatch(AppCommand::OpenPreludeEditor),
|
|
KeyCode::Delete | KeyCode::Backspace => {
|
|
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
|
|
if let Some(range) = ctx.app.editor_ctx.selection_range() {
|
|
let steps: Vec<usize> = range.collect();
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
|
action: ConfirmAction::DeleteSteps {
|
|
bank,
|
|
pattern,
|
|
steps,
|
|
},
|
|
selected: false,
|
|
}));
|
|
} else {
|
|
let step = ctx.app.editor_ctx.step;
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
|
action: ConfirmAction::DeleteStep {
|
|
bank,
|
|
pattern,
|
|
step,
|
|
},
|
|
selected: false,
|
|
}));
|
|
}
|
|
}
|
|
KeyCode::Char('r') if ctrl => {
|
|
let pattern = ctx.app.current_edit_pattern();
|
|
if let Some(script) = pattern.resolve_script(ctx.app.editor_ctx.step) {
|
|
if !script.trim().is_empty() {
|
|
match ctx
|
|
.app
|
|
.execute_script_oneshot(script, ctx.link, ctx.audio_tx)
|
|
{
|
|
Ok(()) => ctx
|
|
.app
|
|
.ui
|
|
.flash("Executed", 100, crate::state::FlashKind::Info),
|
|
Err(e) => ctx.app.ui.flash(
|
|
&format!("Error: {e}"),
|
|
200,
|
|
crate::state::FlashKind::Error,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
KeyCode::Char('r') => {
|
|
let (bank, pattern, step) = (
|
|
ctx.app.editor_ctx.bank,
|
|
ctx.app.editor_ctx.pattern,
|
|
ctx.app.editor_ctx.step,
|
|
);
|
|
let current_name = ctx
|
|
.app
|
|
.current_edit_pattern()
|
|
.step(step)
|
|
.and_then(|s| s.name.clone())
|
|
.unwrap_or_default();
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Rename {
|
|
target: RenameTarget::Step {
|
|
bank,
|
|
pattern,
|
|
step,
|
|
},
|
|
name: current_name,
|
|
}));
|
|
}
|
|
KeyCode::Char('o') => {
|
|
ctx.app.audio.config.layout = ctx.app.audio.config.layout.next();
|
|
}
|
|
KeyCode::Char('?') => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
|
}
|
|
KeyCode::Char('e') | KeyCode::Char('E') => {
|
|
let (bank, pattern, step) = (
|
|
ctx.app.editor_ctx.bank,
|
|
ctx.app.editor_ctx.pattern,
|
|
ctx.app.editor_ctx.step,
|
|
);
|
|
let pattern_len = ctx.app.current_edit_pattern().length;
|
|
let default_steps = pattern_len.min(32);
|
|
let default_pulses = (default_steps / 2).max(1).min(default_steps);
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::EuclideanDistribution {
|
|
bank,
|
|
pattern,
|
|
source_step: step,
|
|
field: EuclideanField::Pulses,
|
|
pulses: default_pulses.to_string(),
|
|
steps: default_steps.to_string(),
|
|
rotation: "0".to_string(),
|
|
}));
|
|
}
|
|
KeyCode::Char('m') => {
|
|
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
|
|
ctx.app.playback.toggle_mute(bank, pattern);
|
|
ctx.app.send_mute_state(ctx.seq_cmd_tx);
|
|
}
|
|
KeyCode::Char('x') => {
|
|
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
|
|
ctx.app.playback.toggle_solo(bank, pattern);
|
|
ctx.app.send_mute_state(ctx.seq_cmd_tx);
|
|
}
|
|
KeyCode::Char('M') => {
|
|
ctx.dispatch(AppCommand::ClearMutes);
|
|
ctx.app.send_mute_state(ctx.seq_cmd_tx);
|
|
}
|
|
KeyCode::Char('X') => {
|
|
ctx.dispatch(AppCommand::ClearSolos);
|
|
ctx.app.send_mute_state(ctx.seq_cmd_tx);
|
|
}
|
|
KeyCode::Char('d') => {
|
|
ctx.dispatch(AppCommand::EvaluatePrelude);
|
|
}
|
|
KeyCode::Char('g') => {
|
|
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
|
|
ctx.dispatch(AppCommand::SharePattern { bank, pattern });
|
|
}
|
|
KeyCode::Char('G') => {
|
|
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
|
|
ctx.dispatch(AppCommand::ImportPattern { bank, pattern });
|
|
}
|
|
_ => {}
|
|
}
|
|
InputResult::Continue
|
|
}
|
|
|
|
fn shift_navigate(ctx: &mut InputContext, cmd: AppCommand) {
|
|
if ctx.app.editor_ctx.selection_anchor.is_none() {
|
|
ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step));
|
|
}
|
|
ctx.dispatch(cmd);
|
|
}
|
|
|
|
fn navigate(ctx: &mut InputContext, cmd: AppCommand) {
|
|
ctx.app.editor_ctx.clear_selection();
|
|
ctx.dispatch(cmd);
|
|
}
|