149 lines
5.0 KiB
Rust
149 lines
5.0 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
|
use ratatui::style::Style;
|
|
use ratatui::widgets::{Block, Borders, Paragraph};
|
|
use ratatui::Frame;
|
|
|
|
use crate::app::App;
|
|
use crate::engine::SequencerSnapshot;
|
|
use crate::model::SourceSpan;
|
|
use crate::theme;
|
|
use crate::views::highlight;
|
|
use crate::views::render::{adjust_resolved_for_line, adjust_spans_for_line};
|
|
use crate::widgets::hint_line;
|
|
|
|
pub fn layout(area: Rect) -> [Rect; 2] {
|
|
Layout::horizontal([Constraint::Percentage(60), Constraint::Percentage(40)]).areas(area)
|
|
}
|
|
|
|
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
|
let [editor_area, sidebar_area] = layout(area);
|
|
|
|
render_editor(frame, app, snapshot, editor_area);
|
|
render_sidebar(frame, app, sidebar_area);
|
|
}
|
|
|
|
fn render_editor(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
|
|
let theme = theme::get();
|
|
let focused = app.script_editor.focused;
|
|
let speed_label = app.project_state.project.script_speed.label();
|
|
let length = app.project_state.project.script_length;
|
|
let title = format!(" Periodic Script ({speed_label}, {length} steps) ");
|
|
|
|
let border_color = if focused { theme.modal.editor } else { theme.ui.border };
|
|
let block = Block::default()
|
|
.borders(Borders::ALL)
|
|
.title(title)
|
|
.border_style(Style::new().fg(border_color));
|
|
let inner = block.inner(area);
|
|
frame.render_widget(block, area);
|
|
|
|
if inner.height < 2 {
|
|
return;
|
|
}
|
|
|
|
let editor_height = inner.height.saturating_sub(1);
|
|
let editor_area = Rect::new(inner.x, inner.y, inner.width, editor_height);
|
|
let hint_area = Rect::new(inner.x, inner.y + editor_height, inner.width, 1);
|
|
|
|
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
|
|
|
let trace = if app.ui.runtime_highlight && app.playback.playing {
|
|
snapshot.script_trace()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let text_lines = app.script_editor.editor.lines();
|
|
let mut line_offsets: Vec<usize> = Vec::with_capacity(text_lines.len());
|
|
let mut offset = 0;
|
|
for line in text_lines.iter() {
|
|
line_offsets.push(offset);
|
|
offset += line.len() + 1;
|
|
}
|
|
|
|
let resolved_display: Vec<(SourceSpan, String)> = trace
|
|
.map(|t| t.resolved.iter().map(|(s, v)| (*s, v.display())).collect())
|
|
.unwrap_or_default();
|
|
|
|
let highlighter = |row: usize, line: &str| -> Vec<(Style, String, bool)> {
|
|
let line_start = line_offsets[row];
|
|
let (exec, sel, res) = match trace {
|
|
Some(t) => (
|
|
adjust_spans_for_line(&t.executed_spans, line_start, line.len()),
|
|
adjust_spans_for_line(&t.selected_spans, line_start, line.len()),
|
|
adjust_resolved_for_line(&resolved_display, line_start, line.len()),
|
|
),
|
|
None => (Vec::new(), Vec::new(), Vec::new()),
|
|
};
|
|
highlight::highlight_line_with_runtime(line, &exec, &sel, &res, &user_words)
|
|
};
|
|
|
|
app.script_editor.editor.render(frame, editor_area, &highlighter);
|
|
|
|
if !focused {
|
|
let hints = hint_line(&[
|
|
("Enter", "edit"),
|
|
("S", "speed"),
|
|
("L", "length"),
|
|
("s", "save"),
|
|
("l", "load"),
|
|
("?", "keys"),
|
|
]);
|
|
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
|
|
} else {
|
|
let hints = hint_line(&[
|
|
("Esc", "unfocus"),
|
|
("C-e", "eval"),
|
|
]);
|
|
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
|
|
}
|
|
}
|
|
|
|
fn render_sidebar(frame: &mut Frame, app: &App, area: Rect) {
|
|
use crate::widgets::Orientation;
|
|
|
|
let mut constraints = Vec::new();
|
|
if app.audio.config.show_scope {
|
|
constraints.push(Constraint::Fill(1));
|
|
}
|
|
if app.audio.config.show_spectrum {
|
|
constraints.push(Constraint::Fill(1));
|
|
}
|
|
if app.audio.config.show_lissajous {
|
|
constraints.push(Constraint::Fill(1));
|
|
}
|
|
let has_prelude = !app.project_state.project.prelude.trim().is_empty()
|
|
|| !app.project_state.project.banks[app.editor_ctx.bank]
|
|
.prelude
|
|
.trim()
|
|
.is_empty();
|
|
if has_prelude {
|
|
constraints.push(Constraint::Fill(1));
|
|
}
|
|
if constraints.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let areas: Vec<Rect> = Layout::vertical(&constraints).split(area).to_vec();
|
|
let mut idx = 0;
|
|
|
|
if app.audio.config.show_scope {
|
|
super::main_view::render_scope(frame, app, areas[idx], Orientation::Horizontal);
|
|
idx += 1;
|
|
}
|
|
if app.audio.config.show_spectrum {
|
|
super::main_view::render_spectrum(frame, app, areas[idx]);
|
|
idx += 1;
|
|
}
|
|
if app.audio.config.show_lissajous {
|
|
super::main_view::render_lissajous(frame, app, areas[idx]);
|
|
idx += 1;
|
|
}
|
|
if has_prelude {
|
|
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
|
super::main_view::render_prelude_preview(frame, app, &user_words, areas[idx]);
|
|
}
|
|
}
|