Feat: documentation, UI/UX

This commit is contained in:
2026-03-01 19:09:52 +01:00
parent ecb559e556
commit db44f9b98e
57 changed files with 1531 additions and 615 deletions

View File

@@ -228,8 +228,8 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
let mut lines = parsed.lines.clone();
// Highlight focused code block with background tint
if let Some(block_idx) = app.ui.help_focused_block {
// Highlight focused code block with background tint and border
let border_lines_inserted = if let Some(block_idx) = app.ui.help_focused_block {
if let Some(block) = parsed.code_blocks.get(block_idx) {
let tint_bg = theme.ui.surface;
for line in lines.iter_mut().take(block.end_line).skip(block.start_line) {
@@ -242,6 +242,31 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
*span = Span::styled(span.content.clone(), style);
}
}
let border = "".repeat(content_width.saturating_sub(1));
let border_line = RLine::from(Span::styled(
format!(" {border}"),
Style::new().fg(theme.ui.accent),
));
lines.insert(block.end_line, border_line.clone());
lines.insert(block.start_line, border_line);
2
} else {
0
}
} else {
0
};
// Insert print output line after the executed code block
if let Some((topic, block_idx, ref output)) = app.ui.help_block_output {
if topic == app.ui.help_topic {
if let Some(block) = parsed.code_blocks.get(block_idx) {
let output_line = RLine::from(vec![
Span::styled("", Style::new().fg(theme.markdown.code_border)),
Span::styled(output.clone(), Style::new().fg(theme.markdown.code)),
]);
lines.insert(block.end_line + border_lines_inserted, output_line);
}
}
}

View File

@@ -1,6 +1,8 @@
//! Main page view — sequencer grid, visualizations (scope/spectrum), script previews.
use std::collections::HashSet;
use std::sync::OnceLock;
use std::time::Instant;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
@@ -361,6 +363,26 @@ fn render_sequencer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot,
}
}
fn pulse_phase() -> f32 {
static EPOCH: OnceLock<Instant> = OnceLock::new();
let t = EPOCH.get_or_init(Instant::now).elapsed().as_secs_f32();
(1.0 + (t * std::f32::consts::TAU).sin()) * 0.5
}
fn pulse_color(color: Color) -> Color {
let Color::Rgb(r, g, b) = color else { return color };
let lum = 0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32;
let amp = pulse_phase() * 0.12;
let shift = |c: u8| -> u8 {
if lum < 128.0 {
(c as f32 + (255.0 - c as f32) * amp) as u8
} else {
(c as f32 * (1.0 - amp)) as u8
}
};
Color::Rgb(shift(r), shift(g), shift(b))
}
fn render_tile(
frame: &mut Frame,
area: Rect,
@@ -417,6 +439,7 @@ fn render_tile(
(false, false, _, _, true) => (theme.selection.in_range, theme.selection.cursor_fg),
(false, false, false, _, _) => (theme.tile.inactive_bg, theme.tile.inactive_fg),
};
let bg = if is_selected && !is_playing { pulse_color(bg) } else { bg };
let bg_fill = Paragraph::new("").style(Style::new().bg(bg));
frame.render_widget(bg_fill, area);

View File

@@ -197,7 +197,7 @@ pub fn render(
}
if !perf {
render_footer(frame, app, footer_area);
render_footer(frame, app, snapshot, footer_area);
}
let modal_area = render_modal(frame, app, snapshot, term);
@@ -512,7 +512,7 @@ fn render_header(
);
}
fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
fn render_footer(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
let theme = theme::get();
let block = Block::default()
.borders(Borders::ALL)
@@ -539,6 +539,15 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
Span::raw(" "),
Span::styled(msg.clone(), Style::new().fg(theme.modal.confirm)),
])
} else if let Some(ref text) = snapshot.print_output {
Line::from(vec![
Span::styled(
page_indicator.to_string(),
Style::new().bg(theme.view_badge.bg).fg(theme.view_badge.fg),
),
Span::raw(" "),
Span::styled(text.clone(), Style::new().fg(theme.hint.text)),
])
} else {
let bindings: Vec<(&str, &str)> = match app.page {
Page::Main => vec![
@@ -593,7 +602,6 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
("Esc", "Save & Back"),
("C-e", "Eval"),
("[ ]", "Speed"),
("C-s", "Stack"),
("?", "Keys"),
],
};
@@ -1071,33 +1079,12 @@ fn render_modal_editor(
if app.editor_ctx.editor.search_active() {
let hints = hint_line(&[("Enter", "confirm"), ("Esc", "cancel")]);
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
} else if app.editor_ctx.show_stack {
let stack_text = app
.editor_ctx
.stack_cache
.borrow()
.as_ref()
.map(|c| c.result.clone())
.unwrap_or_else(|| "Stack: []".to_string());
let hints = hint_line(&[("Esc", "save"), ("C-e", "eval"), ("C-s", "hide")]);
let [hint_left, stack_right] = Layout::horizontal([
Constraint::Length(hints.width() as u16),
Constraint::Fill(1),
])
.areas(hint_area);
frame.render_widget(Paragraph::new(hints), hint_left);
let dim = Style::default().fg(theme.hint.text);
frame.render_widget(
Paragraph::new(Span::styled(stack_text, dim)).alignment(Alignment::Right),
stack_right,
);
} else {
let hints = hint_line(&[
("Esc", "save"),
("C-e", "eval"),
("C-f", "find"),
("C-b", "samples"),
("C-s", "stack"),
("C-u", "/"),
("C-r", "undo/redo"),
]);

View File

@@ -2,7 +2,6 @@ use std::collections::HashSet;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::Span;
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::Frame;
@@ -93,31 +92,10 @@ fn render_editor(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, are
("?", "keys"),
]);
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
} else if app.script_editor.show_stack {
let stack_text = app
.script_editor
.stack_cache
.borrow()
.as_ref()
.map(|c| c.result.clone())
.unwrap_or_else(|| "Stack: []".to_string());
let hints = hint_line(&[("Esc", "unfocus"), ("C-e", "eval"), ("C-s", "hide stack")]);
let [hint_left, stack_right] = Layout::horizontal([
Constraint::Length(hints.width() as u16),
Constraint::Fill(1),
])
.areas(hint_area);
frame.render_widget(Paragraph::new(hints), hint_left);
let dim = Style::default().fg(theme.hint.text);
frame.render_widget(
Paragraph::new(Span::styled(stack_text, dim)).alignment(Alignment::Right),
stack_right,
);
} else {
let hints = hint_line(&[
("Esc", "unfocus"),
("C-e", "eval"),
("C-s", "stack"),
]);
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
}