WIP: half broken

This commit is contained in:
2026-01-24 01:59:51 +01:00
parent f75ea4bb97
commit 04f5e19ab2
21 changed files with 1310 additions and 119 deletions

View File

@@ -1,6 +1,5 @@
use crossbeam_channel::Sender;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -9,7 +8,7 @@ use crate::commands::AppCommand;
use crate::engine::{AudioCommand, LinkState, SequencerSnapshot};
use crate::model::PatternSpeed;
use crate::page::Page;
use crate::state::{AudioFocus, Modal, PatternField};
use crate::state::{AudioFocus, Modal, PanelFocus, PatternField, SampleBrowserState, SidePanel};
pub enum InputResult {
Continue,
@@ -335,42 +334,62 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
KeyCode::Char(c) if c.is_ascii_digit() || c == '.' => input.push(c),
_ => {}
},
Modal::AddSamplePath(path) => match key.code {
Modal::AddSamplePath(state) => match key.code {
KeyCode::Enter => {
let sample_path = PathBuf::from(path.as_str());
if sample_path.is_dir() {
let index = doux::loader::scan_samples_dir(&sample_path);
let sample_path = if let Some(entry) = state.entries.get(state.selected) {
if entry.is_dir && entry.name != ".." {
Some(state.current_dir().join(&entry.name))
} else if entry.is_dir {
state.enter_selected();
None
} else {
None
}
} else {
let dir = state.current_dir();
if dir.is_dir() { Some(dir) } else { None }
};
if let Some(path) = sample_path {
let index = doux::loader::scan_samples_dir(&path);
let count = index.len();
let _ = ctx.audio_tx.send(AudioCommand::LoadSamples(index));
ctx.app.audio.config.sample_count += count;
ctx.app.audio.add_sample_path(sample_path);
ctx.app.audio.add_sample_path(path);
ctx.dispatch(AppCommand::SetStatus(format!("Added {count} samples")));
} else {
ctx.dispatch(AppCommand::SetStatus("Path is not a directory".to_string()));
ctx.dispatch(AppCommand::CloseModal);
}
ctx.dispatch(AppCommand::CloseModal);
}
KeyCode::Esc => ctx.dispatch(AppCommand::CloseModal),
KeyCode::Backspace => {
path.pop();
KeyCode::Tab => state.autocomplete(),
KeyCode::Left => state.go_up(),
KeyCode::Right => state.enter_selected(),
KeyCode::Up => state.select_prev(14),
KeyCode::Down => state.select_next(14),
KeyCode::Backspace => state.backspace(),
KeyCode::Char(c) => {
state.input.push(c);
state.refresh_entries();
}
KeyCode::Char(c) => path.push(c),
_ => {}
},
Modal::Editor => {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
match key.code {
KeyCode::Esc => {
ctx.dispatch(AppCommand::SaveEditorToStep);
ctx.dispatch(AppCommand::CompileCurrentStep);
ctx.dispatch(AppCommand::CloseModal);
if ctx.app.editor_ctx.editor.completion_active() {
ctx.app.editor_ctx.editor.dismiss_completion();
} else {
ctx.dispatch(AppCommand::SaveEditorToStep);
ctx.dispatch(AppCommand::CompileCurrentStep);
ctx.dispatch(AppCommand::CloseModal);
}
}
KeyCode::Char('e') if ctrl => {
ctx.dispatch(AppCommand::SaveEditorToStep);
ctx.dispatch(AppCommand::CompileCurrentStep);
}
_ => {
ctx.app.editor_ctx.text.input(Event::Key(key));
ctx.app.editor_ctx.editor.input(Event::Key(key));
}
}
}
@@ -390,6 +409,10 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
fn handle_normal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
if ctx.app.panel.visible && ctx.app.panel.focus == PanelFocus::Side {
return handle_panel_input(ctx, key);
}
if ctrl {
match key.code {
KeyCode::Left => {
@@ -420,8 +443,94 @@ fn handle_normal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
}
}
fn handle_panel_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
use cagire_ratatui::TreeLineKind;
use crate::engine::AudioCommand;
let state = match &mut ctx.app.panel.side {
Some(SidePanel::SampleBrowser(s)) => s,
None => return InputResult::Continue,
};
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
if state.search_active {
match key.code {
KeyCode::Esc => {
state.clear_search();
}
KeyCode::Backspace => {
state.search_query.pop();
state.update_search();
}
KeyCode::Enter => {
state.search_active = false;
}
KeyCode::Char(c) => {
state.search_query.push(c);
state.update_search();
}
_ => {}
}
} else if ctrl {
match key.code {
KeyCode::Up => {
for _ in 0..10 {
state.move_up();
}
}
KeyCode::Down => {
for _ in 0..10 {
state.move_down(30);
}
}
_ => {}
}
} else {
match key.code {
KeyCode::Up | KeyCode::Char('k') => state.move_up(),
KeyCode::Down | KeyCode::Char('j') => state.move_down(30),
KeyCode::Enter | KeyCode::Right => {
if let Some(entry) = state.current_entry() {
match entry.kind {
TreeLineKind::File => {
let folder = &entry.folder;
let idx = entry.index;
let cmd =
format!("/sound/{folder}/n/{idx}/gain/0.5/dur/1");
let _ = ctx.audio_tx.send(AudioCommand::Evaluate(cmd));
}
_ => state.toggle_expand(),
}
}
}
KeyCode::Left => state.collapse_at_cursor(),
KeyCode::Char('/') => state.activate_search(),
KeyCode::Esc | KeyCode::Tab => {
ctx.app.panel.visible = false;
ctx.app.panel.focus = PanelFocus::Main;
}
_ => {}
}
}
InputResult::Continue
}
fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputResult {
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::ConfirmQuit {
selected: false,
@@ -659,6 +768,7 @@ fn handle_audio_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
AudioFocus::OutputDevice | AudioFocus::InputDevice => {}
AudioFocus::Channels => ctx.app.audio.adjust_channels(-1),
AudioFocus::BufferSize => ctx.app.audio.adjust_buffer_size(-64),
AudioFocus::Polyphony => ctx.app.audio.adjust_max_voices(-1),
AudioFocus::RefreshRate => ctx.app.audio.toggle_refresh_rate(),
AudioFocus::RuntimeHighlight => {
ctx.app.ui.runtime_highlight = !ctx.app.ui.runtime_highlight
@@ -669,6 +779,9 @@ fn handle_audio_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
AudioFocus::ShowSpectrum => {
ctx.app.audio.config.show_spectrum = !ctx.app.audio.config.show_spectrum;
}
AudioFocus::ShowCompletion => {
ctx.app.ui.show_completion = !ctx.app.ui.show_completion;
}
AudioFocus::SamplePaths => ctx.app.audio.remove_last_sample_path(),
AudioFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()),
AudioFocus::StartStopSync => ctx
@@ -688,6 +801,7 @@ fn handle_audio_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
AudioFocus::OutputDevice | AudioFocus::InputDevice => {}
AudioFocus::Channels => ctx.app.audio.adjust_channels(1),
AudioFocus::BufferSize => ctx.app.audio.adjust_buffer_size(64),
AudioFocus::Polyphony => ctx.app.audio.adjust_max_voices(1),
AudioFocus::RefreshRate => ctx.app.audio.toggle_refresh_rate(),
AudioFocus::RuntimeHighlight => {
ctx.app.ui.runtime_highlight = !ctx.app.ui.runtime_highlight
@@ -698,6 +812,9 @@ fn handle_audio_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
AudioFocus::ShowSpectrum => {
ctx.app.audio.config.show_spectrum = !ctx.app.audio.config.show_spectrum;
}
AudioFocus::ShowCompletion => {
ctx.app.ui.show_completion = !ctx.app.ui.show_completion;
}
AudioFocus::SamplePaths => {}
AudioFocus::LinkEnabled => ctx.link.set_enabled(!ctx.link.is_enabled()),
AudioFocus::StartStopSync => ctx
@@ -714,7 +831,9 @@ fn handle_audio_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
}
KeyCode::Char('R') => ctx.app.audio.trigger_restart(),
KeyCode::Char('A') => {
ctx.dispatch(AppCommand::OpenModal(Modal::AddSamplePath(String::new())));
use crate::state::file_browser::FileBrowserState;
let state = FileBrowserState::new_load(String::new());
ctx.dispatch(AppCommand::OpenModal(Modal::AddSamplePath(state)));
}
KeyCode::Char('D') => {
ctx.app.audio.refresh_devices();