Skip to main content

rust_synth/tui/
tracks.rs

1//! Track list widget — gain bars + mute/active state.
2
3use ratatui::layout::Rect;
4use ratatui::style::{Color, Modifier, Style};
5use ratatui::widgets::{Block, Borders, List, ListItem};
6use ratatui::Frame;
7
8use super::app::AppState;
9use crate::audio::engine::EngineHandle;
10
11pub fn render(f: &mut Frame, area: Rect, engine: &EngineHandle, app: &AppState) {
12    let tracks = engine.tracks.lock();
13    let items: Vec<ListItem> = tracks
14        .iter()
15        .enumerate()
16        .map(|(i, t)| {
17            let snap = t.params.snapshot();
18            let marker = if i == app.selected_track { "▶" } else { " " };
19            let status = if snap.muted { "·" } else { "●" };
20            let gain_bar = bar(snap.gain * (if snap.muted { 0.0 } else { 1.0 }), 8);
21            let label = t.kind.label();
22            // At-a-glance feature indicators: S = supermass on,
23            // A = arpeggiator on, L = LFO modulating something.
24            let sup = if snap.supermass > 0.15 { "S" } else { " " };
25            let arp = if snap.arp > 0.05 { "A" } else { " " };
26            let lfo = if snap.lfo_depth > 0.05 && (snap.lfo_target.round() as u32) > 0 {
27                "L"
28            } else {
29                " "
30            };
31            let line = format!(
32                "{marker} {status} {label:<10} {sup}{arp}{lfo} {bar} {f:>5.0}Hz",
33                bar = gain_bar,
34                f = snap.freq,
35            );
36            let style = if i == app.selected_track {
37                Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
38            } else if snap.muted {
39                Style::default().fg(Color::DarkGray)
40            } else {
41                Style::default().fg(Color::White)
42            };
43            ListItem::new(line).style(style)
44        })
45        .collect();
46
47    let list = List::new(items).block(
48        Block::default()
49            .borders(Borders::ALL)
50            .title(" tracks ")
51            .title_style(Style::default().add_modifier(Modifier::BOLD)),
52    );
53    f.render_widget(list, area);
54}
55
56fn bar(v: f32, width: usize) -> String {
57    let filled = (v.clamp(0.0, 1.0) * width as f32).round() as usize;
58    let empty = width.saturating_sub(filled);
59    format!("[{}{}]", "█".repeat(filled), "·".repeat(empty))
60}