This commit is contained in:
@@ -22,7 +22,7 @@ use crate::settings::Settings;
|
||||
use crate::state::{
|
||||
AudioSettings, CyclicEnum, EditorContext, EditorTarget, FlashKind, LiveKeyState, Metrics,
|
||||
Modal, MuteState, OptionsState, PanelState, PatternField, PatternPropsField, PatternsNav,
|
||||
PlaybackState, ProjectState, StagedChange, StagedPropChange, UiState,
|
||||
PlaybackState, ProjectState, SampleTree, StagedChange, StagedPropChange, UiState,
|
||||
};
|
||||
|
||||
const STEPS_PER_PAGE: usize = 32;
|
||||
@@ -328,6 +328,8 @@ impl App {
|
||||
self.editor_ctx
|
||||
.editor
|
||||
.set_completion_enabled(self.ui.show_completion);
|
||||
let tree = SampleTree::from_paths(&self.audio.config.sample_paths);
|
||||
self.editor_ctx.editor.set_sample_folders(tree.all_folder_names());
|
||||
if self.editor_ctx.show_stack {
|
||||
crate::services::stack_preview::update_cache(&self.editor_ctx);
|
||||
}
|
||||
@@ -359,6 +361,8 @@ impl App {
|
||||
self.editor_ctx
|
||||
.editor
|
||||
.set_completion_enabled(self.ui.show_completion);
|
||||
let tree = SampleTree::from_paths(&self.audio.config.sample_paths);
|
||||
self.editor_ctx.editor.set_sample_folders(tree.all_folder_names());
|
||||
self.editor_ctx.target = EditorTarget::Prelude;
|
||||
self.ui.modal = Modal::Editor;
|
||||
}
|
||||
|
||||
20
src/input.rs
20
src/input.rs
@@ -494,6 +494,19 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
return InputResult::Continue;
|
||||
}
|
||||
|
||||
if editor.sample_finder_active() {
|
||||
match key.code {
|
||||
KeyCode::Esc => editor.dismiss_sample_finder(),
|
||||
KeyCode::Tab | KeyCode::Enter => editor.accept_sample_finder(),
|
||||
KeyCode::Backspace => editor.sample_finder_backspace(),
|
||||
KeyCode::Char('n') if ctrl => editor.sample_finder_next(),
|
||||
KeyCode::Char('p') if ctrl => editor.sample_finder_prev(),
|
||||
KeyCode::Char(c) if !ctrl => editor.sample_finder_input(c),
|
||||
_ => {}
|
||||
}
|
||||
return InputResult::Continue;
|
||||
}
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
if editor.is_selecting() {
|
||||
@@ -525,12 +538,17 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
ctx.dispatch(AppCommand::EvaluatePrelude);
|
||||
}
|
||||
},
|
||||
KeyCode::Char('b') if ctrl => {
|
||||
editor.activate_sample_finder();
|
||||
}
|
||||
KeyCode::Char('f') if ctrl => {
|
||||
editor.activate_search();
|
||||
}
|
||||
KeyCode::Char('n') if ctrl => {
|
||||
if editor.completion_active() {
|
||||
editor.completion_next();
|
||||
} else if editor.sample_finder_active() {
|
||||
editor.sample_finder_next();
|
||||
} else {
|
||||
editor.search_next();
|
||||
}
|
||||
@@ -538,6 +556,8 @@ fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
||||
KeyCode::Char('p') if ctrl => {
|
||||
if editor.completion_active() {
|
||||
editor.completion_prev();
|
||||
} else if editor.sample_finder_active() {
|
||||
editor.sample_finder_prev();
|
||||
} else {
|
||||
editor.search_prev();
|
||||
}
|
||||
|
||||
@@ -43,5 +43,5 @@ pub use patterns_nav::{PatternsColumn, PatternsNav};
|
||||
pub use mute::MuteState;
|
||||
pub use playback::{PlaybackState, StagedChange, StagedMuteChange, StagedPropChange};
|
||||
pub use project::ProjectState;
|
||||
pub use sample_browser::SampleBrowserState;
|
||||
pub use sample_browser::{SampleBrowserState, SampleTree};
|
||||
pub use ui::{DictFocus, FlashKind, HelpFocus, UiState};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use cagire_ratatui::{TreeLine, TreeLineKind};
|
||||
use cagire_ratatui::{fuzzy_match, TreeLine, TreeLineKind};
|
||||
|
||||
const AUDIO_EXTENSIONS: &[&str] = &["wav", "flac", "ogg", "aiff", "aif", "mp3"];
|
||||
|
||||
@@ -208,6 +208,29 @@ impl SampleTree {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_folder_names(&self) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
for root in &self.roots {
|
||||
Self::collect_folder_names(root, &mut names);
|
||||
}
|
||||
names.sort_by_key(|n| n.to_lowercase());
|
||||
names
|
||||
}
|
||||
|
||||
fn collect_folder_names(node: &SampleNode, out: &mut Vec<String>) {
|
||||
match node {
|
||||
SampleNode::Root { children, .. } => {
|
||||
for child in children {
|
||||
Self::collect_folder_names(child, out);
|
||||
}
|
||||
}
|
||||
SampleNode::Folder { name, .. } => {
|
||||
out.push(name.clone());
|
||||
}
|
||||
SampleNode::File { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible_entries(&self) -> Vec<TreeLine> {
|
||||
let mut out = Vec::new();
|
||||
for root in &self.roots {
|
||||
@@ -558,31 +581,6 @@ impl SampleBrowserState {
|
||||
}
|
||||
}
|
||||
|
||||
fn fuzzy_match(query: &str, target: &str) -> Option<usize> {
|
||||
let target_lower: Vec<char> = target.to_lowercase().chars().collect();
|
||||
let query_lower: Vec<char> = query.to_lowercase().chars().collect();
|
||||
let mut ti = 0;
|
||||
let mut score = 0;
|
||||
let mut prev_pos = 0;
|
||||
for (qi, &qc) in query_lower.iter().enumerate() {
|
||||
loop {
|
||||
if ti >= target_lower.len() {
|
||||
return None;
|
||||
}
|
||||
if target_lower[ti] == qc {
|
||||
if qi > 0 {
|
||||
score += ti - prev_pos;
|
||||
}
|
||||
prev_pos = ti;
|
||||
ti += 1;
|
||||
break;
|
||||
}
|
||||
ti += 1;
|
||||
}
|
||||
}
|
||||
Some(score)
|
||||
}
|
||||
|
||||
fn is_audio_file(name: &str) -> bool {
|
||||
let lower = name.to_lowercase();
|
||||
AUDIO_EXTENSIONS.iter().any(|ext| lower.ends_with(ext))
|
||||
|
||||
@@ -333,14 +333,23 @@ fn render_header(
|
||||
let cpu_pct = (app.metrics.cpu_load * 100.0).min(100.0);
|
||||
let peers = link.peers();
|
||||
let voices = app.metrics.active_voices;
|
||||
let stats_text = format!(" CPU {cpu_pct:.0}% V:{voices} L:{peers} ");
|
||||
let stats_style = Style::new()
|
||||
.bg(theme.header.stats_bg)
|
||||
.fg(theme.header.stats_fg);
|
||||
let cpu_color = if cpu_pct >= 80.0 {
|
||||
theme.flash.error_fg
|
||||
} else if cpu_pct >= 50.0 {
|
||||
theme.ui.accent
|
||||
} else {
|
||||
theme.header.stats_fg
|
||||
};
|
||||
let dim = Style::new().bg(theme.header.stats_bg).fg(theme.header.stats_fg);
|
||||
let stats_line = Line::from(vec![
|
||||
Span::styled(format!(" CPU {cpu_pct:.0}%"), dim.fg(cpu_color)),
|
||||
Span::styled(format!(" V:{voices} L:{peers} "), dim),
|
||||
]);
|
||||
let block_style = Style::new().bg(theme.header.stats_bg);
|
||||
frame.render_widget(
|
||||
Paragraph::new(stats_text)
|
||||
.block(Block::default().padding(pad).style(stats_style))
|
||||
.alignment(Alignment::Right),
|
||||
Paragraph::new(stats_line)
|
||||
.block(Block::default().padding(pad).style(block_style))
|
||||
.alignment(Alignment::Center),
|
||||
stats_area,
|
||||
);
|
||||
}
|
||||
@@ -809,6 +818,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
Span::styled(" eval ", dim),
|
||||
Span::styled("C-f", key),
|
||||
Span::styled(" find ", dim),
|
||||
Span::styled("C-b", key),
|
||||
Span::styled(" samples ", dim),
|
||||
Span::styled("C-s", key),
|
||||
Span::styled(" stack ", dim),
|
||||
Span::styled("C-u", key),
|
||||
|
||||
Reference in New Issue
Block a user