use crate::playlist::Playlist;
use ratatui::{
Frame,
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
};
pub fn draw_playlist_overlay(f: &mut Frame, playlist: &Playlist) {
let area = f.area();
let popup_width = (area.width as f32 * 0.8) as u16;
let popup_height = (area.height as f32 * 0.7) as u16;
let popup_area = centered_rect(popup_width, popup_height, area);
f.render_widget(Clear, popup_area);
let title = if playlist.is_searching() {
format!(" Search: {} ", playlist.search_query())
} else {
" Playlist - Select Song ".to_string()
};
let border_color = if playlist.is_searching() {
Color::Yellow
} else {
Color::Cyan
};
let block = Block::default()
.title(title)
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_style(Style::default().fg(border_color))
.style(Style::default().bg(Color::Black));
let inner = block.inner(popup_area);
f.render_widget(block, popup_area);
if playlist.is_empty() {
let msg = Paragraph::new("No supported files found in directory")
.style(Style::default().fg(Color::DarkGray))
.alignment(Alignment::Center);
f.render_widget(msg, inner);
return;
}
let chunks = Layout::default()
.constraints([Constraint::Min(3), Constraint::Length(2)])
.split(inner);
let search_query = playlist.search_query().to_lowercase();
let items: Vec<ListItem> = playlist
.entries
.iter()
.enumerate()
.map(|(idx, entry)| {
let display = entry.display_string();
let is_selected = idx == playlist.selected;
let format_color = match entry.format.as_str() {
"AKS" => Color::Green,
"SNDH" => Color::Yellow,
"AY" => Color::Magenta,
_ => Color::Blue, };
let mut spans = vec![Span::styled(
format!("[{}] ", entry.format),
Style::default().fg(format_color),
)];
if !search_query.is_empty() {
let display_lower = display.to_lowercase();
if let Some(match_start) = display_lower.find(&search_query) {
let match_end = match_start + search_query.len();
if match_start > 0 {
let style = if is_selected {
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
spans.push(Span::styled(display[..match_start].to_string(), style));
}
let match_style = if is_selected {
Style::default()
.fg(Color::Black)
.bg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::Black).bg(Color::Yellow)
};
spans.push(Span::styled(
display[match_start..match_end].to_string(),
match_style,
));
if match_end < display.len() {
let style = if is_selected {
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
spans.push(Span::styled(display[match_end..].to_string(), style));
}
} else {
let style = if is_selected {
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::DarkGray) };
spans.push(Span::styled(display, style));
}
} else {
let style = if is_selected {
Style::default()
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
spans.push(Span::styled(display, style));
}
ListItem::new(Line::from(spans))
})
.collect();
let list = List::new(items)
.highlight_style(Style::default().bg(Color::DarkGray))
.highlight_symbol("> ");
let mut list_state = ListState::default();
list_state.select(Some(playlist.selected));
f.render_stateful_widget(list, chunks[0], &mut list_state);
let footer = if playlist.is_searching() {
Paragraph::new(Line::from(vec![
Span::styled(
"[↑↓] Next/Prev match ",
Style::default().fg(Color::DarkGray),
),
Span::styled("[Enter] Play ", Style::default().fg(Color::Green)),
Span::styled("[Backspace] Delete ", Style::default().fg(Color::DarkGray)),
Span::styled("[Esc] Clear search", Style::default().fg(Color::Yellow)),
]))
} else {
Paragraph::new(Line::from(vec![
Span::styled("[↑↓] Navigate ", Style::default().fg(Color::DarkGray)),
Span::styled("[Enter] Play ", Style::default().fg(Color::Green)),
Span::styled("[Type] Search ", Style::default().fg(Color::Cyan)),
Span::styled("[p/Esc] Close", Style::default().fg(Color::Yellow)),
]))
}
.alignment(Alignment::Center);
f.render_widget(footer, chunks[1]);
}
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
let x = area.x + (area.width.saturating_sub(width)) / 2;
let y = area.y + (area.height.saturating_sub(height)) / 2;
Rect {
x,
y,
width: width.min(area.width),
height: height.min(area.height),
}
}