WIP: better precision?

This commit is contained in:
2026-01-29 18:50:54 +01:00
parent 00a90f1c15
commit 89e4795e86
13 changed files with 477 additions and 224 deletions

View File

@@ -9,6 +9,8 @@ use crate::engine::SequencerSnapshot;
use crate::model::{MAX_BANKS, MAX_PATTERNS};
use crate::state::PatternsColumn;
const MIN_ROW_HEIGHT: u16 = 1;
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
let [banks_area, gap, patterns_area] = Layout::horizontal([
Constraint::Fill(1),
@@ -55,16 +57,25 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
})
.collect();
let row_height = (inner.height / MAX_BANKS as u16).max(1);
let total_needed = row_height * MAX_BANKS as u16;
let top_padding = if inner.height > total_needed {
(inner.height - total_needed) / 2
} else {
let cursor = app.patterns_nav.bank_cursor;
let max_visible = (inner.height / MIN_ROW_HEIGHT) as usize;
let max_visible = max_visible.max(1);
let scroll_offset = if MAX_BANKS <= max_visible {
0
} else {
cursor
.saturating_sub(max_visible / 2)
.min(MAX_BANKS - max_visible)
};
for idx in 0..MAX_BANKS {
let y = inner.y + top_padding + (idx as u16) * row_height;
let visible_count = MAX_BANKS.min(max_visible);
let row_height = inner.height / visible_count as u16;
let row_height = row_height.max(MIN_ROW_HEIGHT);
for visible_idx in 0..visible_count {
let idx = scroll_offset + visible_idx;
let y = inner.y + (visible_idx as u16) * row_height;
if y >= inner.y + inner.height {
break;
}
@@ -126,6 +137,22 @@ fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area
let para = Paragraph::new(label).style(style);
frame.render_widget(para, text_area);
}
// Scroll indicators
let indicator_style = Style::new().fg(Color::Rgb(120, 125, 135));
if scroll_offset > 0 {
let indicator = Paragraph::new("")
.style(indicator_style)
.alignment(ratatui::layout::Alignment::Center);
frame.render_widget(indicator, Rect { height: 1, ..inner });
}
if scroll_offset + visible_count < MAX_BANKS {
let y = inner.y + inner.height.saturating_sub(1);
let indicator = Paragraph::new("")
.style(indicator_style)
.alignment(ratatui::layout::Alignment::Center);
frame.render_widget(indicator, Rect { y, height: 1, ..inner });
}
}
fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
@@ -191,16 +218,25 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
None
};
let row_height = (inner.height / MAX_PATTERNS as u16).max(1);
let total_needed = row_height * MAX_PATTERNS as u16;
let top_padding = if inner.height > total_needed {
(inner.height - total_needed) / 2
} else {
let cursor = app.patterns_nav.pattern_cursor;
let max_visible = (inner.height / MIN_ROW_HEIGHT) as usize;
let max_visible = max_visible.max(1);
let scroll_offset = if MAX_PATTERNS <= max_visible {
0
} else {
cursor
.saturating_sub(max_visible / 2)
.min(MAX_PATTERNS - max_visible)
};
for idx in 0..MAX_PATTERNS {
let y = inner.y + top_padding + (idx as u16) * row_height;
let visible_count = MAX_PATTERNS.min(max_visible);
let row_height = inner.height / visible_count as u16;
let row_height = row_height.max(MIN_ROW_HEIGHT);
for visible_idx in 0..visible_count {
let idx = scroll_offset + visible_idx;
let y = inner.y + (visible_idx as u16) * row_height;
if y >= inner.y + inner.height {
break;
}
@@ -247,52 +283,56 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
row_area.y
};
// Split row into columns: [index+name] [length] [speed]
let speed_width: u16 = 14; // "Speed: 1/4x "
let length_width: u16 = 13; // "Length: 16 "
let name_width = row_area
.width
.saturating_sub(speed_width + length_width + 2);
let [name_area, length_area, speed_area] = Layout::horizontal([
Constraint::Length(name_width),
Constraint::Length(length_width),
Constraint::Length(speed_width),
])
.areas(Rect {
let text_area = Rect {
x: row_area.x,
y: text_y,
width: row_area.width,
height: 1,
});
// Column 1: prefix + index + name (left-aligned)
let name_text = if name.is_empty() {
format!("{}{:02}", prefix, idx + 1)
} else {
format!("{}{:02} {}", prefix, idx + 1, name)
};
// Build the line: [prefix][idx] [name] ... [length] [speed]
let name_style = if is_playing || is_staged_play {
bold_style
} else {
base_style
};
frame.render_widget(Paragraph::new(name_text).style(name_style), name_area);
let dim_style = base_style.remove_modifier(Modifier::BOLD);
// Column 2: length
let length_line = Line::from(vec![
Span::styled("Length: ", bold_style),
Span::styled(format!("{length}"), base_style),
]);
frame.render_widget(Paragraph::new(length_line), length_area);
// Column 3: speed (only if non-default)
if speed != PatternSpeed::NORMAL {
let speed_line = Line::from(vec![
Span::styled("Speed: ", bold_style),
Span::styled(speed.label(), base_style),
]);
frame.render_widget(Paragraph::new(speed_line), speed_area);
let mut spans = vec![Span::styled(format!("{}{:02}", prefix, idx + 1), name_style)];
if !name.is_empty() {
spans.push(Span::styled(format!(" {name}"), name_style));
}
// Right-aligned info: length and speed
let speed_str = if speed != PatternSpeed::NORMAL {
format!(" {}", speed.label())
} else {
String::new()
};
let right_info = format!("{length}{speed_str}");
let left_width: usize = spans.iter().map(|s| s.content.chars().count()).sum();
let right_width = right_info.chars().count();
let padding = (text_area.width as usize).saturating_sub(left_width + right_width + 1);
spans.push(Span::raw(" ".repeat(padding)));
spans.push(Span::styled(right_info, dim_style));
frame.render_widget(Paragraph::new(Line::from(spans)), text_area);
}
// Scroll indicators
let indicator_style = Style::new().fg(Color::Rgb(120, 125, 135));
if scroll_offset > 0 {
let indicator = Paragraph::new("")
.style(indicator_style)
.alignment(ratatui::layout::Alignment::Center);
frame.render_widget(indicator, Rect { height: 1, ..inner });
}
if scroll_offset + visible_count < MAX_PATTERNS {
let y = inner.y + inner.height.saturating_sub(1);
let indicator = Paragraph::new("")
.style(indicator_style)
.alignment(ratatui::layout::Alignment::Center);
frame.render_widget(indicator, Rect { y, height: 1, ..inner });
}
}