Feat: documentation

This commit is contained in:
2026-02-16 23:19:06 +01:00
parent 773c7bbd1c
commit 540f59dcf5
18 changed files with 565 additions and 227 deletions

View File

@@ -432,7 +432,7 @@ fn render_samples(frame: &mut Frame, app: &App, area: Rect) {
dim,
)));
lines.push(Line::from(Span::styled(
" Add folders containing .wav files",
" Add folders containing audio files",
dim,
)));
} else {

View File

@@ -56,11 +56,19 @@ pub fn adjust_resolved_for_line(
) -> Vec<(SourceSpan, String)> {
resolved
.iter()
.filter_map(|(s, display)| clip_span(*s, line_start, line_len).map(|cs| (cs, display.clone())))
.filter_map(|(s, display)| {
clip_span(*s, line_start, line_len).map(|cs| (cs, display.clone()))
})
.collect()
}
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &SequencerSnapshot, elapsed: Duration) {
pub fn render(
frame: &mut Frame,
app: &App,
link: &LinkState,
snapshot: &SequencerSnapshot,
elapsed: Duration,
) {
let term = frame.area();
let theme = theme::get();
@@ -212,7 +220,11 @@ fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
});
let duration = sample.total_frames as f32 / app.audio.config.sample_rate;
let ch_label = if sample.channels == 1 { "mono" } else { "stereo" };
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);
@@ -349,7 +361,9 @@ fn render_header(
} else {
theme.header.stats_fg
};
let dim = Style::new().bg(theme.header.stats_bg).fg(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),
@@ -407,9 +421,9 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
Page::Engine => vec![
("Tab", "Section"),
("←→", "Switch/Adjust"),
("↑↓", "Navigate"),
("Enter", "Select"),
("A", "Add path"),
("R", "Restart"),
("h", "Hush"),
("?", "Keys"),
],
Page::Options => vec![
@@ -484,14 +498,18 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
frame.render_widget(footer, area);
}
fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term: Rect) -> Option<Rect> {
fn render_modal(
frame: &mut Frame,
app: &App,
snapshot: &SequencerSnapshot,
term: Rect,
) -> Option<Rect> {
let theme = theme::get();
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
let inner = match &app.ui.modal {
Modal::None => return None,
Modal::Confirm { action, selected } => {
ConfirmModal::new("Confirm", &action.message(), *selected)
.render_centered(frame, term)
ConfirmModal::new("Confirm", &action.message(), *selected).render_centered(frame, term)
}
Modal::FileBrowser(state) => {
use crate::state::file_browser::FileBrowserMode;
@@ -577,26 +595,42 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
} => {
use crate::state::PatternPropsField;
let inner =
ModalFrame::new(&format!(" Pattern B{:02}:P{:02} ", bank + 1, pattern + 1))
.width(50)
.height(12)
.border_color(theme.modal.input)
.render_centered(frame, term);
let inner = ModalFrame::new(&format!(" Pattern B{:02}:P{:02} ", bank + 1, pattern + 1))
.width(50)
.height(12)
.border_color(theme.modal.input)
.render_centered(frame, term);
let speed_label = speed.label();
let fields: Vec<(&str, &str, bool)> = vec![
("Name", name.as_str(), *field == PatternPropsField::Name),
("Length", length.as_str(), *field == PatternPropsField::Length),
(
"Length",
length.as_str(),
*field == PatternPropsField::Length,
),
("Speed", &speed_label, *field == PatternPropsField::Speed),
("Quantization", quantization.label(), *field == PatternPropsField::Quantization),
("Sync Mode", sync_mode.label(), *field == PatternPropsField::SyncMode),
(
"Quantization",
quantization.label(),
*field == PatternPropsField::Quantization,
),
(
"Sync Mode",
sync_mode.label(),
*field == PatternPropsField::SyncMode,
),
];
render_props_form(frame, inner, &fields);
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
let hints = hint_line(&[("↑↓", "nav"), ("←→", "change"), ("Enter", "save"), ("Esc", "cancel")]);
let hints = hint_line(&[
("↑↓", "nav"),
("←→", "change"),
("Enter", "save"),
("Esc", "cancel"),
]);
frame.render_widget(Paragraph::new(hints), hint_area);
inner
@@ -625,7 +659,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
lines
};
let key_lines = keys.len() as u16;
let modal_height = (3 + desc_lines + 1 + key_lines + 2).min(term.height.saturating_sub(4));
let modal_height =
(3 + desc_lines + 1 + key_lines + 2).min(term.height.saturating_sub(4));
let title = if page_count > 1 {
format!(" {} ({}/{}) ", app.page.name(), page_idx + 1, page_count)
@@ -654,16 +689,13 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
}
let line = Line::from(vec![
Span::raw(" "),
Span::styled(
format!("{:>8}", key),
Style::new().fg(theme.hint.key),
),
Span::styled(
format!(" {action}"),
Style::new().fg(theme.hint.text),
),
Span::styled(format!("{:>8}", key), Style::new().fg(theme.hint.key)),
Span::styled(format!(" {action}"), Style::new().fg(theme.hint.text)),
]);
frame.render_widget(Paragraph::new(line), Rect::new(inner.x + 1, y, inner.width.saturating_sub(2), 1));
frame.render_widget(
Paragraph::new(line),
Rect::new(inner.x + 1, y, inner.width.saturating_sub(2), 1),
);
y += 1;
}
@@ -677,7 +709,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
}
hints_vec.push(("Enter", "don't show again"));
let hints = hint_line(&hints_vec);
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Center), hint_area);
frame.render_widget(
Paragraph::new(hints).alignment(Alignment::Center),
hint_area,
);
inner
}
@@ -702,7 +737,11 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
let fields: Vec<(&str, &str, bool)> = vec![
("Pulses", pulses.as_str(), *field == EuclideanField::Pulses),
("Steps", steps.as_str(), *field == EuclideanField::Steps),
("Rotation", rotation.as_str(), *field == EuclideanField::Rotation),
(
"Rotation",
rotation.as_str(),
*field == EuclideanField::Rotation,
),
];
render_props_form(frame, inner, &fields);
@@ -723,7 +762,12 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
}
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
let hints = hint_line(&[("↑↓", "nav"), ("←→", "adjust"), ("Enter", "apply"), ("Esc", "cancel")]);
let hints = hint_line(&[
("↑↓", "nav"),
("←→", "adjust"),
("Enter", "apply"),
("Esc", "cancel"),
]);
frame.render_widget(Paragraph::new(hints), hint_area);
inner
@@ -791,12 +835,7 @@ fn render_modal_preview(
};
let resolved_display: Vec<(SourceSpan, String)> = trace
.map(|t| {
t.resolved
.iter()
.map(|(s, v)| (*s, v.display()))
.collect()
})
.map(|t| t.resolved.iter().map(|(s, v)| (*s, v.display())).collect())
.unwrap_or_default();
let mut line_start = 0usize;
@@ -804,21 +843,10 @@ fn render_modal_preview(
.lines()
.map(|line_str| {
let tokens = if let Some(t) = trace {
let exec = adjust_spans_for_line(
&t.executed_spans,
line_start,
line_str.len(),
);
let sel = adjust_spans_for_line(
&t.selected_spans,
line_start,
line_str.len(),
);
let res = adjust_resolved_for_line(
&resolved_display,
line_start,
line_str.len(),
);
let exec = adjust_spans_for_line(&t.executed_spans, line_start, line_str.len());
let sel = adjust_spans_for_line(&t.selected_spans, line_start, line_str.len());
let res =
adjust_resolved_for_line(&resolved_display, line_start, line_str.len());
highlight_line_with_runtime(line_str, &exec, &sel, &res, user_words)
} else {
highlight_line_with_runtime(line_str, &[], &[], &[], user_words)
@@ -898,12 +926,7 @@ fn render_modal_editor(
}
let resolved_display: Vec<(SourceSpan, String)> = trace
.map(|t| {
t.resolved
.iter()
.map(|(s, v)| (*s, v.display()))
.collect()
})
.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)> {
@@ -919,8 +942,8 @@ fn render_modal_editor(
highlight::highlight_line_with_runtime(line, &exec, &sel, &res, user_words)
};
let show_search = app.editor_ctx.editor.search_active()
|| !app.editor_ctx.editor.search_query().is_empty();
let show_search =
app.editor_ctx.editor.search_active() || !app.editor_ctx.editor.search_query().is_empty();
let reserved_lines = 1 + if show_search { 1 } else { 0 };
let editor_height = inner.height.saturating_sub(reserved_lines);
@@ -1061,10 +1084,7 @@ fn render_modal_keybindings(frame: &mut Frame, app: &App, scroll: usize, term: R
height: 1,
};
let hints = hint_line(&[("↑↓", "scroll"), ("PgUp/Dn", "page"), ("Esc/?", "close")]);
frame.render_widget(
Paragraph::new(hints).alignment(Alignment::Right),
hint_area,
);
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
inner
}