seq continues

This commit is contained in:
2026-01-19 16:44:17 +01:00
parent 2900f84b7d
commit ac9e64dcb7
18 changed files with 1568 additions and 361 deletions

View File

@@ -0,0 +1,194 @@
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::Frame;
use crate::app::{App, PatternsViewLevel};
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
match app.patterns_view_level {
PatternsViewLevel::Banks => render_banks(frame, app, area),
PatternsViewLevel::Patterns { bank } => render_patterns(frame, app, area, bank),
}
}
fn render_banks(frame: &mut Frame, app: &App, area: Rect) {
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(Color::Rgb(100, 160, 180)))
.title("Banks");
let inner = block.inner(area);
frame.render_widget(block, area);
if inner.width < 50 {
let msg = Paragraph::new("Terminal too narrow")
.alignment(Alignment::Center)
.style(Style::new().fg(Color::Rgb(120, 125, 135)));
frame.render_widget(msg, inner);
return;
}
let banks_with_playback: Vec<usize> = app
.slot_data
.iter()
.filter(|(active, _, _)| *active)
.map(|(_, bank, _)| *bank)
.collect();
let bank_names: Vec<Option<&str>> = app
.project
.banks
.iter()
.map(|b| b.name.as_deref())
.collect();
render_grid(frame, inner, app.patterns_cursor, app.edit_bank, &banks_with_playback, &bank_names);
}
fn render_patterns(frame: &mut Frame, app: &App, area: Rect, bank: usize) {
let bank_name = app.project.banks[bank].name.as_deref();
let title_text = match bank_name {
Some(name) => format!("{name} Patterns"),
None => format!("Bank {:02} Patterns", bank + 1),
};
let title = Line::from(vec![
Span::raw(title_text),
Span::styled(" [Esc]←", Style::new().fg(Color::Rgb(120, 125, 135))),
]);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(Color::Rgb(100, 160, 180)))
.title(title);
let inner = block.inner(area);
frame.render_widget(block, area);
if inner.width < 50 {
let msg = Paragraph::new("Terminal too narrow")
.alignment(Alignment::Center)
.style(Style::new().fg(Color::Rgb(120, 125, 135)));
frame.render_widget(msg, inner);
return;
}
let playing_patterns: Vec<usize> = app
.slot_data
.iter()
.filter(|(active, b, _)| *active && *b == bank)
.map(|(_, _, pattern)| *pattern)
.collect();
let edit_pattern = if app.edit_bank == bank {
app.edit_pattern
} else {
usize::MAX
};
let pattern_names: Vec<Option<&str>> = app.project.banks[bank]
.patterns
.iter()
.map(|p| p.name.as_deref())
.collect();
render_pattern_grid(frame, app, inner, bank, app.patterns_cursor, edit_pattern, &playing_patterns, &pattern_names);
}
fn render_grid(
frame: &mut Frame,
area: Rect,
cursor: usize,
edit_pos: usize,
playing_positions: &[usize],
names: &[Option<&str>],
) {
let rows = Layout::vertical([
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
])
.split(area);
for row in 0..4 {
let cols = Layout::horizontal(vec![Constraint::Fill(1); 4]).split(rows[row]);
for col in 0..4 {
let idx = row * 4 + col;
let is_cursor = idx == cursor;
let is_edit = idx == edit_pos;
let is_playing = playing_positions.contains(&idx);
let (bg, fg) = match (is_cursor, is_edit, is_playing) {
(true, _, _) => (Color::Cyan, Color::Black),
(false, true, _) => (Color::Rgb(45, 106, 95), Color::White),
(false, false, true) => (Color::Rgb(45, 80, 45), Color::Green),
(false, false, false) => (Color::Rgb(45, 48, 55), Color::Rgb(120, 125, 135)),
};
let label = names.get(idx).and_then(|n| *n).unwrap_or_else(|| "");
let label = if label.is_empty() {
format!("{:02}", idx + 1)
} else {
label.to_string()
};
let tile = Paragraph::new(label)
.alignment(Alignment::Center)
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
frame.render_widget(tile, cols[col]);
}
}
}
fn render_pattern_grid(
frame: &mut Frame,
app: &App,
area: Rect,
bank: usize,
cursor: usize,
edit_pos: usize,
playing_positions: &[usize],
names: &[Option<&str>],
) {
let rows = Layout::vertical([
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
Constraint::Fill(1),
])
.split(area);
for row in 0..4 {
let cols = Layout::horizontal(vec![Constraint::Fill(1); 4]).split(rows[row]);
for col in 0..4 {
let idx = row * 4 + col;
let is_cursor = idx == cursor;
let is_edit = idx == edit_pos;
let is_playing = playing_positions.contains(&idx);
let queued = app.is_pattern_queued(bank, idx);
let (bg, fg, prefix) = match (is_cursor, is_playing, queued) {
(true, _, _) => (Color::Cyan, Color::Black, ""),
(false, true, Some(false)) => (Color::Rgb(120, 90, 30), Color::Yellow, "×"),
(false, true, _) => (Color::Rgb(45, 80, 45), Color::Green, ""),
(false, false, Some(true)) => (Color::Rgb(80, 80, 45), Color::Yellow, "?"),
(false, false, _) if is_edit => (Color::Rgb(45, 106, 95), Color::White, ""),
(false, false, _) => (Color::Rgb(45, 48, 55), Color::Rgb(120, 125, 135), ""),
};
let name = names.get(idx).and_then(|n| *n).unwrap_or("");
let label = if name.is_empty() {
format!("{}{:02}", prefix, idx + 1)
} else {
format!("{}{}", prefix, name)
};
let tile = Paragraph::new(label)
.alignment(Alignment::Center)
.style(Style::new().bg(bg).fg(fg).add_modifier(Modifier::BOLD));
frame.render_widget(tile, cols[col]);
}
}
}