Feat: UI/UX fixes + removing clones from places

This commit is contained in:
2026-03-05 00:15:51 +01:00
parent 35370a6f2c
commit 60fb62829f
17 changed files with 1817 additions and 290 deletions

View File

@@ -1,7 +1,7 @@
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::Frame;
use crate::app::App;
@@ -359,8 +359,7 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
let cursor = app.patterns_nav.pattern_cursor;
let available = inner.height as usize;
// Cursor row takes 2 lines (main + detail); account for 1 extra
let max_visible = available.saturating_sub(1).max(1);
let max_visible = available.max(1);
let scroll_offset = if MAX_PATTERNS <= max_visible {
0
@@ -375,8 +374,6 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
let mut y = inner.y;
for visible_idx in 0..visible_count {
let idx = scroll_offset + visible_idx;
let is_expanded = idx == cursor;
let row_h = if is_expanded { 2u16 } else { 1u16 };
if y >= inner.y + inner.height {
break;
}
@@ -385,7 +382,7 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
x: inner.x,
y,
width: inner.width,
height: row_h.min(inner.y + inner.height - y),
height: 1u16.min(inner.y + inner.height - y),
};
let is_cursor = is_focused && idx == app.patterns_nav.pattern_cursor;
@@ -471,21 +468,9 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
let base_style = Style::new().bg(bg).fg(fg);
let bold_style = base_style.add_modifier(Modifier::BOLD);
let content_area = if is_expanded {
let border_color = if is_focused { theme.selection.cursor } else { theme.ui.unfocused };
let block = Block::default()
.borders(Borders::LEFT | Borders::RIGHT)
.border_type(BorderType::QuadrantOutside)
.border_style(Style::new().fg(border_color).bg(bg))
.style(Style::new().bg(bg));
let content = block.inner(row_area);
frame.render_widget(block, row_area);
content
} else {
let bg_block = Block::default().style(Style::new().bg(bg));
frame.render_widget(bg_block, row_area);
row_area
};
let bg_block = Block::default().style(Style::new().bg(bg));
frame.render_widget(bg_block, row_area);
let content_area = row_area;
let text_area = Rect {
x: content_area.x,
@@ -521,16 +506,38 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
String::new()
};
let props_indicator = if has_staged_props { "~" } else { "" };
let right_info = if content_count > 0 {
format!("{props_indicator}{content_count}/{length}{speed_str}")
let quant_sync = if is_selected {
format!(
"{}:{} ",
pattern.quantization.short_label(),
pattern.sync_mode.short_label()
)
} else {
format!("{props_indicator} {length}{speed_str}")
String::new()
};
let right_info = if content_count > 0 {
format!("{quant_sync}{props_indicator}{content_count}/{length}{speed_str}")
} else {
format!("{quant_sync}{props_indicator} {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);
let gap = (text_area.width as usize).saturating_sub(left_width + right_width + 1);
spans.push(Span::styled(" ".repeat(padding), dim_style));
if let Some(desc) = pattern.description.as_deref().filter(|d| !d.is_empty() && gap > 4) {
let budget = gap - 2;
let char_count = desc.chars().count();
if char_count <= budget {
spans.push(Span::styled(format!(" {desc}"), dim_style));
spans.push(Span::styled(" ".repeat(gap - char_count - 1), dim_style));
} else {
let truncated: String = desc.chars().take(budget - 1).collect();
spans.push(Span::styled(format!(" {truncated}\u{2026}"), dim_style));
spans.push(Span::styled(" ", dim_style));
}
} else {
spans.push(Span::styled(" ".repeat(gap), dim_style));
}
spans.push(Span::styled(right_info, dim_style));
let spans = if is_playing && !is_cursor && !is_in_range {
@@ -543,52 +550,6 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
frame.render_widget(Paragraph::new(Line::from(spans)), text_area);
if is_expanded && content_area.height >= 2 {
let detail_area = Rect {
x: content_area.x,
y: content_area.y + 1,
width: content_area.width,
height: 1,
};
let right_label = format!(
"{} · {}",
pattern.quantization.label(),
pattern.sync_mode.label()
);
let w = detail_area.width as usize;
let label = if let Some(desc) = &pattern.description {
let right_len = right_label.chars().count();
let max_desc = w.saturating_sub(right_len + 1);
let truncated: String = desc.chars().take(max_desc).collect();
let pad = w.saturating_sub(truncated.chars().count() + right_len);
format!("{truncated}{}{right_label}", " ".repeat(pad))
} else {
format!("{right_label:>w$}")
};
let padded_label = label;
let filled_width = if is_playing {
let ratio = snapshot.get_smooth_progress(bank, idx, length, speed.multiplier()).unwrap_or(0.0);
(ratio * detail_area.width as f64).min(detail_area.width as f64) as usize
} else {
0
};
let dim_fg = theme.ui.text_muted;
let progress_bg = theme.list.playing_bg;
let byte_offset = padded_label
.char_indices()
.nth(filled_width)
.map_or(padded_label.len(), |(i, _)| i);
let (left, right) = padded_label.split_at(byte_offset);
let detail_spans = vec![
Span::styled(left.to_string(), Style::new().bg(progress_bg).fg(dim_fg)),
Span::styled(right.to_string(), Style::new().bg(theme.ui.bg).fg(dim_fg)),
];
frame.render_widget(Paragraph::new(Line::from(detail_spans)), detail_area);
}
y += row_area.height;
}