This commit is contained in:
2026-02-10 23:51:17 +01:00
parent 7d670dacb9
commit 8ba89f91a0
12 changed files with 2312 additions and 2280 deletions

248
src/input/main_page.rs Normal file
View File

@@ -0,0 +1,248 @@
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 {
if ctx.app.panel.side.is_none() {
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') => {
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
action: ConfirmAction::Quit,
selected: false,
}));
}
KeyCode::Char(' ') => {
ctx.dispatch(AppCommand::TogglePlaying);
ctx.playing
.store(ctx.app.playback.playing, Ordering::Relaxed);
}
KeyCode::Left if shift && !ctrl => {
if ctx.app.editor_ctx.selection_anchor.is_none() {
ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step));
}
ctx.dispatch(AppCommand::PrevStep);
}
KeyCode::Right if shift && !ctrl => {
if ctx.app.editor_ctx.selection_anchor.is_none() {
ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step));
}
ctx.dispatch(AppCommand::NextStep);
}
KeyCode::Up if shift && !ctrl => {
if ctx.app.editor_ctx.selection_anchor.is_none() {
ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step));
}
ctx.dispatch(AppCommand::StepUp);
}
KeyCode::Down if shift && !ctrl => {
if ctx.app.editor_ctx.selection_anchor.is_none() {
ctx.dispatch(AppCommand::SetSelectionAnchor(ctx.app.editor_ctx.step));
}
ctx.dispatch(AppCommand::StepDown);
}
KeyCode::Left => {
ctx.app.editor_ctx.clear_selection();
ctx.dispatch(AppCommand::PrevStep);
}
KeyCode::Right => {
ctx.app.editor_ctx.clear_selection();
ctx.dispatch(AppCommand::NextStep);
}
KeyCode::Up => {
ctx.app.editor_ctx.clear_selection();
ctx.dispatch(AppCommand::StepUp);
}
KeyCode::Down => {
ctx.app.editor_ctx.clear_selection();
ctx.dispatch(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') => {
use crate::state::file_browser::FileBrowserState;
let initial = ctx
.app
.project_state
.file_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_default();
let state = FileBrowserState::new_save(initial);
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(Box::new(state))));
}
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') => {
use crate::state::file_browser::FileBrowserState;
let default_dir = ctx
.app
.project_state
.file_path
.as_ref()
.and_then(|p| p.parent())
.map(|p| {
let mut s = p.display().to_string();
if !s.ends_with('/') {
s.push('/');
}
s
})
.unwrap_or_default();
let state = FileBrowserState::new_load(default_dir);
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(Box::new(state))));
}
KeyCode::Char('+') | KeyCode::Char('=') => ctx.dispatch(AppCommand::TempoUp),
KeyCode::Char('-') => ctx.dispatch(AppCommand::TempoDown),
KeyCode::Char('T') => {
let current = format!("{:.1}", ctx.link.tempo());
ctx.dispatch(AppCommand::OpenModal(Modal::SetTempo(current)));
}
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::OpenModal(Modal::Preview)),
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.dispatch(AppCommand::StageMute { bank, pattern });
}
KeyCode::Char('x') => {
let (bank, pattern) = (ctx.app.editor_ctx.bank, ctx.app.editor_ctx.pattern);
ctx.dispatch(AppCommand::StageSolo { bank, pattern });
}
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::OpenPreludeEditor);
}
KeyCode::Char('D') => {
ctx.dispatch(AppCommand::EvaluatePrelude);
}
_ => {}
}
InputResult::Continue
}