use crate::state::{AppState, PlayerState}; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Style}, text::{Line, Span}, widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, }; pub fn render(frame: &mut Frame, state: &AppState) { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(frame.area()); render_file_panel(frame, state, chunks[0]); render_status_panel(frame, state, chunks[1]); } fn render_file_panel(frame: &mut Frame, state: &AppState, area: Rect) { let items: Vec = state .flattened_items .iter() .enumerate() .map(|(idx, item)| { let indent = " ".repeat(item.depth); let expand_marker = if item.node.is_dir { if item.is_expanded { "[-] " } else { "[+] " } } else { " " }; let suffix = if item.node.is_dir { "/" } else { "" }; let text = format!("{}{}{}{}", indent, expand_marker, item.node.name, suffix); let style = if idx == state.selected_index { Style::default().fg(Color::Black).bg(Color::Cyan) } else if item.node.is_dir { Style::default().fg(Color::Blue) } else { Style::default() }; ListItem::new(text).style(style) }) .collect(); let list = List::new(items) .block( Block::default() .borders(Borders::ALL) .title("Media Files (Cached)") .style(Style::default().fg(Color::White)), ); frame.render_widget(list, area); } fn render_status_panel(frame: &mut Frame, state: &AppState, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Min(0), ]) .split(area); // Player state let state_text = match state.player_state { PlayerState::Stopped => "⏹ Stopped", PlayerState::Playing => "▶ Playing", PlayerState::Paused => "⏸ Paused", }; let state_widget = Paragraph::new(state_text) .block(Block::default().borders(Borders::ALL).title("Status")) .style(Style::default().fg(Color::White)); frame.render_widget(state_widget, chunks[0]); // Current file let current_file = state .current_file .as_ref() .and_then(|p| p.file_name()) .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| "None".to_string()); let file_widget = Paragraph::new(current_file) .block(Block::default().borders(Borders::ALL).title("Current File")) .style(Style::default().fg(Color::White)); frame.render_widget(file_widget, chunks[1]); // Progress let progress_text = if state.current_duration > 0.0 { let position_mins = (state.current_position / 60.0) as u32; let position_secs = (state.current_position % 60.0) as u32; let duration_mins = (state.current_duration / 60.0) as u32; let duration_secs = (state.current_duration % 60.0) as u32; format!( "{:02}:{:02} / {:02}:{:02}", position_mins, position_secs, duration_mins, duration_secs ) } else { "00:00 / 00:00".to_string() }; let progress_widget = Paragraph::new(progress_text) .block(Block::default().borders(Borders::ALL).title("Progress")) .style(Style::default().fg(Color::White)); frame.render_widget(progress_widget, chunks[2]); // Volume let volume_text = format!("{}%", state.volume); let volume_widget = Paragraph::new(volume_text) .block(Block::default().borders(Borders::ALL).title("Volume")) .style(Style::default().fg(Color::White)); frame.render_widget(volume_widget, chunks[3]); // Help let help_text = vec![ Line::from(""), Line::from(vec![ Span::styled("j/k", Style::default().fg(Color::Cyan)), Span::raw(" Navigate down/up"), ]), Line::from(vec![ Span::styled("h/l", Style::default().fg(Color::Cyan)), Span::raw(" Collapse/expand dir"), ]), Line::from(vec![ Span::styled("Enter", Style::default().fg(Color::Cyan)), Span::raw(" Play file"), ]), Line::from(vec![ Span::styled("Space", Style::default().fg(Color::Cyan)), Span::raw(" Pause/Resume"), ]), Line::from(vec![ Span::styled("r", Style::default().fg(Color::Cyan)), Span::raw(" Rescan"), ]), Line::from(vec![ Span::styled("q", Style::default().fg(Color::Cyan)), Span::raw(" Quit"), ]), ]; let help_widget = Paragraph::new(help_text) .block(Block::default().borders(Borders::ALL).title("Help")) .style(Style::default().fg(Color::White)); frame.render_widget(help_widget, chunks[4]); }