Fixes
This commit is contained in:
@@ -6,6 +6,7 @@ pub mod main_view;
|
||||
pub mod options_view;
|
||||
pub mod patterns_view;
|
||||
mod render;
|
||||
pub mod sample_explorer_view;
|
||||
pub mod script_view;
|
||||
pub mod title_view;
|
||||
|
||||
|
||||
@@ -14,19 +14,18 @@ use crate::engine::{LinkState, SequencerSnapshot};
|
||||
use crate::model::{ExecutionTrace, SourceSpan};
|
||||
use crate::page::Page;
|
||||
use crate::state::{
|
||||
EditorTarget, EuclideanField, FlashKind, Modal, PanelFocus, PatternField, RenameTarget,
|
||||
SidePanel,
|
||||
EditorTarget, EuclideanField, FlashKind, Modal, PatternField, RenameTarget,
|
||||
};
|
||||
use crate::theme;
|
||||
use crate::views::highlight::{self, highlight_line_with_runtime};
|
||||
use crate::widgets::{
|
||||
hint_line, render_props_form, render_search_bar, ConfirmModal, ModalFrame, NavMinimap, NavTile,
|
||||
SampleBrowser, TextInputModal,
|
||||
TextInputModal,
|
||||
};
|
||||
|
||||
use super::{
|
||||
dict_view, engine_view, help_view, main_view, options_view, patterns_view, script_view,
|
||||
title_view,
|
||||
dict_view, engine_view, help_view, main_view, options_view, patterns_view,
|
||||
sample_explorer_view, script_view, title_view,
|
||||
};
|
||||
|
||||
fn clip_span(span: SourceSpan, line_start: usize, line_len: usize) -> Option<SourceSpan> {
|
||||
@@ -164,28 +163,15 @@ pub fn render(
|
||||
render_header(frame, app, link, snapshot, header_area);
|
||||
}
|
||||
|
||||
let (page_area, panel_area) = if app.panel.visible && app.panel.side.is_some() {
|
||||
let panel_width = body_area.width * 35 / 100;
|
||||
let [main, side] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
|
||||
.areas(body_area);
|
||||
(main, Some(side))
|
||||
} else {
|
||||
(body_area, None)
|
||||
};
|
||||
|
||||
match app.page {
|
||||
Page::Main => main_view::render(frame, app, snapshot, page_area),
|
||||
Page::Patterns => patterns_view::render(frame, app, snapshot, page_area),
|
||||
Page::Engine => engine_view::render(frame, app, link, page_area),
|
||||
Page::Options => options_view::render(frame, app, page_area),
|
||||
Page::Help => help_view::render(frame, app, page_area),
|
||||
Page::Dict => dict_view::render(frame, app, page_area),
|
||||
Page::Script => script_view::render(frame, app, snapshot, page_area),
|
||||
}
|
||||
|
||||
if let Some(side_area) = panel_area {
|
||||
render_side_panel(frame, app, side_area);
|
||||
Page::Main => main_view::render(frame, app, snapshot, body_area),
|
||||
Page::Patterns => patterns_view::render(frame, app, snapshot, body_area),
|
||||
Page::Engine => engine_view::render(frame, app, link, body_area),
|
||||
Page::Options => options_view::render(frame, app, body_area),
|
||||
Page::Help => help_view::render(frame, app, body_area),
|
||||
Page::Dict => dict_view::render(frame, app, body_area),
|
||||
Page::Script => script_view::render(frame, app, snapshot, body_area),
|
||||
Page::SampleExplorer => sample_explorer_view::render(frame, app, body_area),
|
||||
}
|
||||
|
||||
if !perf {
|
||||
@@ -292,69 +278,6 @@ fn header_height(_width: u16) -> u16 {
|
||||
3
|
||||
}
|
||||
|
||||
fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let focused = app.panel.focus == PanelFocus::Side;
|
||||
match &app.panel.side {
|
||||
Some(SidePanel::SampleBrowser(state)) => {
|
||||
let [tree_area, preview_area] =
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(6)]).areas(area);
|
||||
|
||||
// Compute visible height: tree_area minus borders (2), minus search bar (1) if shown
|
||||
let mut vh = tree_area.height.saturating_sub(2) as usize;
|
||||
if state.search_active || !state.search_query.is_empty() {
|
||||
vh = vh.saturating_sub(1);
|
||||
}
|
||||
state.visible_height.set(vh);
|
||||
|
||||
let entries = state.entries();
|
||||
SampleBrowser::new(&entries, state.cursor)
|
||||
.scroll_offset(state.scroll_offset)
|
||||
.search(&state.search_query, state.search_active)
|
||||
.focused(focused)
|
||||
.render(frame, tree_area);
|
||||
|
||||
if let Some(sample) = state
|
||||
.sample_key()
|
||||
.and_then(|key| app.audio.sample_registry.as_ref()?.get(&key))
|
||||
.filter(|s| s.frame_count >= s.total_frames)
|
||||
{
|
||||
use crate::widgets::Waveform;
|
||||
use std::cell::RefCell;
|
||||
thread_local! {
|
||||
static MONO_BUF: RefCell<Vec<f32>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
let [wave_area, info_area] =
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)])
|
||||
.areas(preview_area);
|
||||
|
||||
MONO_BUF.with(|buf| {
|
||||
let mut buf = buf.borrow_mut();
|
||||
let channels = sample.channels as usize;
|
||||
let frame_count = sample.frame_count as usize;
|
||||
buf.clear();
|
||||
buf.reserve(frame_count);
|
||||
for i in 0..frame_count {
|
||||
buf.push(sample.frames[i * channels]);
|
||||
}
|
||||
frame.render_widget(Waveform::new(&buf), wave_area);
|
||||
});
|
||||
|
||||
let duration = sample.total_frames as f32 / app.audio.config.sample_rate;
|
||||
let ch_label = if sample.channels == 1 {
|
||||
"mono"
|
||||
} else {
|
||||
"stereo"
|
||||
};
|
||||
let info = Paragraph::new(format!(" {duration:.1}s · {ch_label}"))
|
||||
.style(Style::new().fg(theme::get().ui.text_dim));
|
||||
frame.render_widget(info, info_area);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_header(
|
||||
frame: &mut Frame,
|
||||
app: &App,
|
||||
@@ -527,6 +450,7 @@ fn render_footer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, are
|
||||
Page::Help => " HELP ",
|
||||
Page::Dict => " DICT ",
|
||||
Page::Script => " SCRIPT ",
|
||||
Page::SampleExplorer => " SAMPLES ",
|
||||
};
|
||||
|
||||
let content = if let Some(ref msg) = app.ui.status_message {
|
||||
@@ -549,10 +473,10 @@ fn render_footer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, are
|
||||
])
|
||||
} else {
|
||||
let bindings: Vec<(&str, &str)> = match app.page {
|
||||
Page::Main if app.panel.visible && app.panel.focus == PanelFocus::Side => vec![
|
||||
("↑↓", "Navigate"),
|
||||
("→", "Expand/Play"),
|
||||
("←", "Collapse"),
|
||||
Page::SampleExplorer => vec![
|
||||
("\u{2191}\u{2193}", "Navigate"),
|
||||
("\u{2192}", "Expand/Play"),
|
||||
("\u{2190}", "Collapse"),
|
||||
("/", "Search"),
|
||||
("Tab", "Close"),
|
||||
],
|
||||
|
||||
87
src/views/sample_explorer_view.rs
Normal file
87
src/views/sample_explorer_view.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::Style;
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::theme;
|
||||
use crate::widgets::{SampleBrowser, Waveform};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
render_browser(frame, app, area);
|
||||
}
|
||||
|
||||
fn render_browser(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let state = match &app.sample_browser {
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let [tree_area, preview_area] =
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(6)]).areas(area);
|
||||
|
||||
let mut vh = tree_area.height.saturating_sub(2) as usize;
|
||||
if state.search_active || !state.search_query.is_empty() {
|
||||
vh = vh.saturating_sub(1);
|
||||
}
|
||||
state.visible_height.set(vh);
|
||||
|
||||
let entries = state.entries();
|
||||
SampleBrowser::new(&entries, state.cursor)
|
||||
.scroll_offset(state.scroll_offset)
|
||||
.search(&state.search_query, state.search_active)
|
||||
.focused(true)
|
||||
.render(frame, tree_area);
|
||||
|
||||
render_waveform_preview(frame, app, state, preview_area);
|
||||
}
|
||||
|
||||
fn render_waveform_preview(
|
||||
frame: &mut Frame,
|
||||
app: &App,
|
||||
state: &crate::state::SampleBrowserState,
|
||||
area: Rect,
|
||||
) {
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use std::cell::RefCell;
|
||||
|
||||
let sample = match state
|
||||
.sample_key()
|
||||
.and_then(|key| app.audio.sample_registry.as_ref()?.get(&key))
|
||||
.filter(|s| s.frame_count >= s.total_frames)
|
||||
{
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static MONO_BUF: RefCell<Vec<f32>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
let [wave_area, info_area] =
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
MONO_BUF.with(|buf| {
|
||||
let mut buf = buf.borrow_mut();
|
||||
let channels = sample.channels as usize;
|
||||
let frame_count = sample.frame_count as usize;
|
||||
buf.clear();
|
||||
buf.reserve(frame_count);
|
||||
for i in 0..frame_count {
|
||||
buf.push(sample.frames[i * channels]);
|
||||
}
|
||||
frame.render_widget(Waveform::new(&buf), wave_area);
|
||||
});
|
||||
|
||||
let duration = sample.total_frames as f32 / app.audio.config.sample_rate;
|
||||
let ch_label = if sample.channels == 1 {
|
||||
"mono"
|
||||
} else {
|
||||
"stereo"
|
||||
};
|
||||
let info = Paragraph::new(Line::from(Span::styled(
|
||||
format!(" {duration:.1}s \u{00B7} {ch_label}"),
|
||||
Style::new().fg(theme::get().ui.text_dim),
|
||||
)));
|
||||
frame.render_widget(info, info_area);
|
||||
}
|
||||
Reference in New Issue
Block a user