Feat: WIP pattern view redesign
This commit is contained in:
@@ -27,20 +27,106 @@ pub fn layout(area: Rect) -> [Rect; 3] {
|
||||
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
||||
let [main_area, _, vu_area] = layout(area);
|
||||
|
||||
let has_viz = app.audio.config.show_scope
|
||||
|| app.audio.config.show_spectrum
|
||||
|| app.audio.config.show_preview;
|
||||
let seq_h = sequencer_height(app.current_edit_pattern().length, app.editor_ctx.step);
|
||||
let (viz_area, sequencer_area) = viz_seq_split(main_area, app.audio.config.layout, has_viz, seq_h);
|
||||
|
||||
if has_viz {
|
||||
render_viz_area(frame, app, snapshot, viz_area);
|
||||
if matches!(app.audio.config.layout, MainLayout::Top) {
|
||||
render_top_layout(frame, app, snapshot, main_area);
|
||||
} else {
|
||||
let has_viz = app.audio.config.show_scope
|
||||
|| app.audio.config.show_spectrum
|
||||
|| app.audio.config.show_preview;
|
||||
let (viz_area, sequencer_area) =
|
||||
viz_seq_split(main_area, app.audio.config.layout, has_viz);
|
||||
if has_viz {
|
||||
render_viz_area(frame, app, snapshot, viz_area);
|
||||
}
|
||||
render_sequencer(frame, app, snapshot, sequencer_area);
|
||||
}
|
||||
|
||||
render_sequencer(frame, app, snapshot, sequencer_area);
|
||||
render_vu_meter(frame, app, vu_area);
|
||||
}
|
||||
|
||||
fn render_top_layout(
|
||||
frame: &mut Frame,
|
||||
app: &App,
|
||||
snapshot: &SequencerSnapshot,
|
||||
main_area: Rect,
|
||||
) {
|
||||
let has_audio_viz = app.audio.config.show_scope || app.audio.config.show_spectrum;
|
||||
let has_preview = app.audio.config.show_preview;
|
||||
|
||||
let mut constraints = Vec::new();
|
||||
if has_audio_viz {
|
||||
constraints.push(Constraint::Fill(1));
|
||||
}
|
||||
if has_preview {
|
||||
constraints.push(Constraint::Length(preview_height(has_audio_viz)));
|
||||
}
|
||||
constraints.push(Constraint::Fill(1));
|
||||
|
||||
let areas = Layout::vertical(&constraints).split(main_area);
|
||||
|
||||
let mut idx = 0;
|
||||
if has_audio_viz {
|
||||
render_audio_viz(frame, app, areas[idx]);
|
||||
idx += 1;
|
||||
}
|
||||
if has_preview {
|
||||
let has_prelude = !app.project_state.project.prelude.trim().is_empty();
|
||||
if has_prelude {
|
||||
let [script_area, prelude_area] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(areas[idx]);
|
||||
render_script_preview(frame, app, snapshot, script_area);
|
||||
render_prelude_preview(frame, app, prelude_area);
|
||||
} else {
|
||||
render_script_preview(frame, app, snapshot, areas[idx]);
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
render_sequencer(frame, app, snapshot, areas[idx]);
|
||||
}
|
||||
|
||||
fn render_audio_viz(frame: &mut Frame, app: &App, area: Rect) {
|
||||
match (app.audio.config.show_scope, app.audio.config.show_spectrum) {
|
||||
(true, true) => {
|
||||
let [scope_area, spectrum_area] =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(area);
|
||||
render_scope(frame, app, scope_area, Orientation::Horizontal);
|
||||
render_spectrum(frame, app, spectrum_area);
|
||||
}
|
||||
(true, false) => render_scope(frame, app, area, Orientation::Horizontal),
|
||||
(false, true) => render_spectrum(frame, app, area),
|
||||
(false, false) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_height(has_audio_viz: bool) -> u16 {
|
||||
if has_audio_viz { 10 } else { 14 }
|
||||
}
|
||||
|
||||
pub fn sequencer_rect(app: &App, main_area: Rect) -> Rect {
|
||||
if matches!(app.audio.config.layout, MainLayout::Top) {
|
||||
let has_audio_viz = app.audio.config.show_scope || app.audio.config.show_spectrum;
|
||||
let has_preview = app.audio.config.show_preview;
|
||||
|
||||
let mut constraints = Vec::new();
|
||||
if has_audio_viz {
|
||||
constraints.push(Constraint::Fill(1));
|
||||
}
|
||||
if has_preview {
|
||||
constraints.push(Constraint::Length(preview_height(has_audio_viz)));
|
||||
}
|
||||
constraints.push(Constraint::Fill(1));
|
||||
|
||||
let areas = Layout::vertical(&constraints).split(main_area);
|
||||
areas[areas.len() - 1]
|
||||
} else {
|
||||
let has_viz = app.audio.config.show_scope
|
||||
|| app.audio.config.show_spectrum
|
||||
|| app.audio.config.show_preview;
|
||||
let (_, seq_area) = viz_seq_split(main_area, app.audio.config.layout, has_viz);
|
||||
seq_area
|
||||
}
|
||||
}
|
||||
|
||||
enum VizPanel {
|
||||
Scope,
|
||||
Spectrum,
|
||||
@@ -81,39 +167,49 @@ fn render_viz_area(
|
||||
match panel {
|
||||
VizPanel::Scope => render_scope(frame, app, *panel_area, orientation),
|
||||
VizPanel::Spectrum => render_spectrum(frame, app, *panel_area),
|
||||
VizPanel::Preview => render_script_preview(frame, app, snapshot, *panel_area),
|
||||
VizPanel::Preview => {
|
||||
let has_prelude = !app.project_state.project.prelude.trim().is_empty();
|
||||
if has_prelude {
|
||||
let [script_area, prelude_area] = if is_vertical_layout {
|
||||
Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)])
|
||||
.areas(*panel_area)
|
||||
} else {
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)])
|
||||
.areas(*panel_area)
|
||||
};
|
||||
render_script_preview(frame, app, snapshot, script_area);
|
||||
render_prelude_preview(frame, app, prelude_area);
|
||||
} else {
|
||||
render_script_preview(frame, app, snapshot, *panel_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const STEPS_PER_PAGE: usize = 32;
|
||||
const TILE_HEIGHT: u16 = 3;
|
||||
const ROW_GAP: u16 = 1;
|
||||
|
||||
pub fn sequencer_height(pattern_length: usize, current_step: usize) -> u16 {
|
||||
let page = current_step / STEPS_PER_PAGE;
|
||||
let page_start = page * STEPS_PER_PAGE;
|
||||
let steps_on_page = (page_start + STEPS_PER_PAGE).min(pattern_length) - page_start;
|
||||
if steps_on_page == 0 {
|
||||
return 0;
|
||||
pub fn steps_per_page(area_height: u16) -> usize {
|
||||
if area_height < 5 {
|
||||
return 8;
|
||||
}
|
||||
let num_rows = steps_on_page.div_ceil(8);
|
||||
let grid_h = (num_rows as u16) * TILE_HEIGHT + (num_rows.saturating_sub(1) as u16) * ROW_GAP;
|
||||
grid_h + 2
|
||||
let usable = (area_height - 2) as usize;
|
||||
let max_rows = (usable + ROW_GAP as usize) / (TILE_HEIGHT as usize + ROW_GAP as usize);
|
||||
(max_rows * 8).clamp(8, 128)
|
||||
}
|
||||
|
||||
pub fn viz_seq_split(
|
||||
main_area: Rect,
|
||||
layout: MainLayout,
|
||||
has_viz: bool,
|
||||
seq_h: u16,
|
||||
) -> (Rect, Rect) {
|
||||
match layout {
|
||||
MainLayout::Top => {
|
||||
if has_viz {
|
||||
let [viz, seq] = Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(seq_h),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(main_area);
|
||||
(viz, seq)
|
||||
@@ -124,7 +220,7 @@ pub fn viz_seq_split(
|
||||
MainLayout::Bottom => {
|
||||
if has_viz {
|
||||
let [seq, viz] = Layout::vertical([
|
||||
Constraint::Length(seq_h),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(main_area);
|
||||
@@ -226,9 +322,11 @@ fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot,
|
||||
|
||||
let pattern = app.current_edit_pattern();
|
||||
let length = pattern.length;
|
||||
let page = app.editor_ctx.step / STEPS_PER_PAGE;
|
||||
let page_start = page * STEPS_PER_PAGE;
|
||||
let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;
|
||||
let spp = steps_per_page(area.height);
|
||||
app.editor_ctx.steps_per_page.set(spp);
|
||||
let page = app.editor_ctx.step / spp;
|
||||
let page_start = page * spp;
|
||||
let steps_on_page = (page_start + spp).min(length) - page_start;
|
||||
|
||||
for (tile_rect, step_offset) in grid_layout(area, steps_on_page) {
|
||||
let step_idx = page_start + step_offset;
|
||||
@@ -470,6 +568,34 @@ fn render_script_preview(
|
||||
frame.render_widget(Paragraph::new(lines), inner);
|
||||
}
|
||||
|
||||
fn render_prelude_preview(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
||||
let prelude = &app.project_state.project.prelude;
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Prelude ")
|
||||
.border_style(Style::new().fg(theme.ui.border));
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
let lines: Vec<Line> = prelude
|
||||
.lines()
|
||||
.take(inner.height as usize)
|
||||
.map(|line_str| {
|
||||
let tokens = highlight_line_with_runtime(line_str, &[], &[], &[], &user_words);
|
||||
let spans: Vec<Span> = tokens
|
||||
.into_iter()
|
||||
.map(|(style, text, _)| Span::styled(text, style))
|
||||
.collect();
|
||||
Line::from(spans)
|
||||
})
|
||||
.collect();
|
||||
|
||||
frame.render_widget(Paragraph::new(lines), inner);
|
||||
}
|
||||
|
||||
fn render_vu_meter(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let block = Block::default()
|
||||
|
||||
Reference in New Issue
Block a user