Feat: UI / UX
This commit is contained in:
@@ -158,6 +158,8 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
time: None,
|
||||
});
|
||||
}
|
||||
KeyCode::Char('s') => super::open_save(ctx),
|
||||
KeyCode::Char('l') => super::open_load(ctx),
|
||||
KeyCode::Char('?') => {
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
use super::{InputContext, InputResult};
|
||||
use crate::commands::AppCommand;
|
||||
use crate::state::{ConfirmAction, DictFocus, HelpFocus, Modal};
|
||||
use crate::state::{ConfirmAction, DictFocus, FlashKind, HelpFocus, Modal};
|
||||
|
||||
pub(super) fn handle_help_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
@@ -26,7 +26,22 @@ pub(super) fn handle_help_page(ctx: &mut InputContext, key: KeyEvent) -> InputRe
|
||||
KeyCode::Esc if !ctx.app.ui.help_search_query.is_empty() => {
|
||||
ctx.dispatch(AppCommand::HelpClearSearch);
|
||||
}
|
||||
KeyCode::Esc if ctx.app.ui.help_focused_block.is_some() => {
|
||||
ctx.app.ui.help_focused_block = None;
|
||||
}
|
||||
KeyCode::Tab => ctx.dispatch(AppCommand::HelpToggleFocus),
|
||||
KeyCode::Char('n') if ctx.app.ui.help_focus == HelpFocus::Content => {
|
||||
navigate_code_block(ctx, true);
|
||||
}
|
||||
KeyCode::Char('p') if ctx.app.ui.help_focus == HelpFocus::Content => {
|
||||
navigate_code_block(ctx, false);
|
||||
}
|
||||
KeyCode::Enter
|
||||
if ctx.app.ui.help_focus == HelpFocus::Content
|
||||
&& ctx.app.ui.help_focused_block.is_some() =>
|
||||
{
|
||||
execute_focused_block(ctx);
|
||||
}
|
||||
KeyCode::Char('j') | KeyCode::Down if ctrl => {
|
||||
ctx.dispatch(AppCommand::HelpNextTopic(5));
|
||||
}
|
||||
@@ -49,6 +64,8 @@ pub(super) fn handle_help_page(ctx: &mut InputContext, key: KeyEvent) -> InputRe
|
||||
selected: false,
|
||||
}));
|
||||
}
|
||||
KeyCode::Char('s') => super::open_save(ctx),
|
||||
KeyCode::Char('l') => super::open_load(ctx),
|
||||
KeyCode::Char('?') => {
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
||||
}
|
||||
@@ -62,6 +79,57 @@ pub(super) fn handle_help_page(ctx: &mut InputContext, key: KeyEvent) -> InputRe
|
||||
InputResult::Continue
|
||||
}
|
||||
|
||||
fn navigate_code_block(ctx: &mut InputContext, forward: bool) {
|
||||
let cache = ctx.app.ui.help_parsed.borrow();
|
||||
let Some(parsed) = cache[ctx.app.ui.help_topic].as_ref() else {
|
||||
return;
|
||||
};
|
||||
let count = parsed.code_blocks.len();
|
||||
if count == 0 {
|
||||
return;
|
||||
}
|
||||
let next = match ctx.app.ui.help_focused_block {
|
||||
Some(cur) if forward => (cur + 1) % count,
|
||||
Some(0) if !forward => count - 1,
|
||||
Some(cur) if !forward => cur - 1,
|
||||
_ if forward => 0,
|
||||
_ => count - 1,
|
||||
};
|
||||
let scroll_to = parsed.code_blocks[next].start_line.saturating_sub(2);
|
||||
drop(cache);
|
||||
ctx.app.ui.help_focused_block = Some(next);
|
||||
*ctx.app.ui.help_scroll_mut() = scroll_to;
|
||||
}
|
||||
|
||||
fn execute_focused_block(ctx: &mut InputContext) {
|
||||
let source = {
|
||||
let cache = ctx.app.ui.help_parsed.borrow();
|
||||
let Some(parsed) = cache[ctx.app.ui.help_topic].as_ref() else {
|
||||
return;
|
||||
};
|
||||
let idx = ctx.app.ui.help_focused_block.unwrap();
|
||||
let Some(block) = parsed.code_blocks.get(idx) else {
|
||||
return;
|
||||
};
|
||||
block.source.clone()
|
||||
};
|
||||
let cleaned: String = source
|
||||
.lines()
|
||||
.map(|l| l.split(" => ").next().unwrap_or(l))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
match ctx
|
||||
.app
|
||||
.execute_script_oneshot(&cleaned, ctx.link, ctx.audio_tx)
|
||||
{
|
||||
Ok(()) => ctx.app.ui.flash("Executed", 100, FlashKind::Info),
|
||||
Err(e) => ctx
|
||||
.app
|
||||
.ui
|
||||
.flash(&format!("Error: {e}"), 200, FlashKind::Error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn handle_dict_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
|
||||
@@ -100,6 +168,8 @@ pub(super) fn handle_dict_page(ctx: &mut InputContext, key: KeyEvent) -> InputRe
|
||||
selected: false,
|
||||
}));
|
||||
}
|
||||
KeyCode::Char('s') => super::open_save(ctx),
|
||||
KeyCode::Char('l') => super::open_load(ctx),
|
||||
KeyCode::Char('?') => {
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
||||
}
|
||||
|
||||
@@ -84,18 +84,7 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool
|
||||
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('s') => super::open_save(ctx),
|
||||
KeyCode::Char('z') if ctrl && !shift => {
|
||||
ctx.dispatch(AppCommand::Undo);
|
||||
}
|
||||
@@ -115,25 +104,7 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool
|
||||
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('l') => super::open_load(ctx),
|
||||
KeyCode::Char('+') | KeyCode::Char('=') => ctx.dispatch(AppCommand::TempoUp),
|
||||
KeyCode::Char('-') => ctx.dispatch(AppCommand::TempoDown),
|
||||
KeyCode::Char('T') => {
|
||||
|
||||
@@ -147,6 +147,39 @@ fn handle_normal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_save(ctx: &mut InputContext) {
|
||||
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))));
|
||||
}
|
||||
|
||||
fn open_load(ctx: &mut InputContext) {
|
||||
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))));
|
||||
}
|
||||
|
||||
fn load_project_samples(ctx: &mut InputContext) {
|
||||
let paths = ctx.app.project_state.project.sample_paths.clone();
|
||||
if paths.is_empty() {
|
||||
|
||||
@@ -525,6 +525,14 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Modal::Onboarding => match key.code {
|
||||
KeyCode::Enter => {
|
||||
ctx.dispatch(AppCommand::DismissOnboarding);
|
||||
ctx.dispatch(AppCommand::CloseModal);
|
||||
ctx.app.save_settings(ctx.link);
|
||||
}
|
||||
_ => ctx.dispatch(AppCommand::CloseModal),
|
||||
},
|
||||
Modal::None => unreachable!(),
|
||||
}
|
||||
InputResult::Continue
|
||||
|
||||
@@ -87,6 +87,9 @@ pub(crate) fn cycle_option_value(ctx: &mut InputContext, right: bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
OptionsFocus::ResetOnboarding => {
|
||||
ctx.dispatch(AppCommand::ResetOnboarding);
|
||||
}
|
||||
OptionsFocus::MidiInput0
|
||||
| OptionsFocus::MidiInput1
|
||||
| OptionsFocus::MidiInput2
|
||||
@@ -162,6 +165,8 @@ pub(super) fn handle_options_page(ctx: &mut InputContext, key: KeyEvent) -> Inpu
|
||||
ctx.playing
|
||||
.store(ctx.app.playback.playing, Ordering::Relaxed);
|
||||
}
|
||||
KeyCode::Char('s') => super::open_save(ctx),
|
||||
KeyCode::Char('l') => super::open_load(ctx),
|
||||
KeyCode::Char('?') => {
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
||||
}
|
||||
|
||||
@@ -249,6 +249,8 @@ pub(super) fn handle_patterns_page(ctx: &mut InputContext, key: KeyEvent) -> Inp
|
||||
ctx.dispatch(AppCommand::ClearSolos);
|
||||
ctx.app.send_mute_state(ctx.seq_cmd_tx);
|
||||
}
|
||||
KeyCode::Char('s') => super::open_save(ctx),
|
||||
KeyCode::Char('l') => super::open_load(ctx),
|
||||
KeyCode::Char('?') => {
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user