- Replace arrow keys with j/k for navigation - Add h/l for collapse/expand directories - Remove emoji icons, use clean text markers - Show directories with [-]/[+] expand markers - Track expanded state per directory path - Add directory suffix (/) for clarity - Update help text with vim bindings
151 lines
5.1 KiB
Rust
151 lines
5.1 KiB
Rust
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<ListItem> = 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]);
|
|
}
|