Added directory playlist and simple queue management

monolith
hheik 2024-08-23 01:16:30 +03:00
parent 2d142e0657
commit 6089e9082a
3 changed files with 119 additions and 39 deletions

View File

@ -1,6 +1,6 @@
use std::{path::PathBuf, time::Duration}; use std::{path::PathBuf, time::Duration};
use rmp::{QueuePlaylist, TrackChangeOptions}; use rmp::{DirectoryPlaylist, PlaylistElement, QueuePlaylist, StatefulList, TrackChangeOptions};
use crate::playback::Playback; use crate::playback::Playback;
@ -14,18 +14,45 @@ pub struct App {
pub should_quit: bool, pub should_quit: bool,
pub playback: Playback, pub playback: Playback,
pub track_change_options: TrackChangeOptions, pub track_change_options: TrackChangeOptions,
pub focus: AppWindow,
pub directory_playlist: DirectoryPlaylist,
pub queue_playlist: QueuePlaylist, pub queue_playlist: QueuePlaylist,
} }
#[derive(Clone, Copy, Debug, Default)]
pub enum AppWindow {
#[default]
DirectoryPlaylist,
QueuePlaylist,
}
impl App { impl App {
pub fn new(options: AppOptions) -> Self { pub fn new(options: AppOptions) -> Self {
Self { Self {
options, options,
should_quit: false, should_quit: false,
playback: Playback::new(), playback: Playback::new(),
// TODO: Maybe read from some configuration (args, file, etc...)
track_change_options: Default::default(), track_change_options: Default::default(),
queue_playlist: QueuePlaylist::generate_mock(), focus: Default::default(),
directory_playlist: DirectoryPlaylist::read_directory(
format!("{}/Music/Ultra Bra", std::env::var("HOME").unwrap()).into(),
)
.unwrap(),
queue_playlist: Default::default(),
}
}
pub fn focused_window(&self) -> &StatefulList<PlaylistElement> {
match &self.focus {
AppWindow::DirectoryPlaylist => &self.directory_playlist.playlist,
AppWindow::QueuePlaylist => &self.queue_playlist.playlist,
}
}
pub fn focused_window_mut(&mut self) -> &mut StatefulList<PlaylistElement> {
match &mut self.focus {
AppWindow::DirectoryPlaylist => &mut self.directory_playlist.playlist,
AppWindow::QueuePlaylist => &mut self.queue_playlist.playlist,
} }
} }
@ -49,12 +76,32 @@ impl App {
self.playback.play_immediate(track); self.playback.play_immediate(track);
} }
pub fn add_selected_to_queue(&mut self) {
// TODO: Fix this mess, add some arguments
if let AppWindow::DirectoryPlaylist = self.focus {
if let Some(selected) = self.directory_playlist.playlist.current() {
self.queue_playlist.playlist.items.push(selected.clone());
}
}
}
pub fn remove_selected_from_queue(&mut self) {
// TODO: Fix this mess, add some arguments
if let AppWindow::QueuePlaylist = self.focus {
if let Some(cursor) = self.queue_playlist.playlist.state.selected() {
self.queue_playlist.playlist.items.remove(cursor);
}
}
}
pub fn on_key(&mut self, key: char) { pub fn on_key(&mut self, key: char) {
match key { match key {
'S' => self.toggle_shuffle(), 'S' => self.toggle_shuffle(),
'X' => self.toggle_next(), 'X' => self.toggle_next(),
'R' => self.toggle_repeat(), 'R' => self.toggle_repeat(),
' ' => self.toggle_pause(), ' ' => self.toggle_pause(),
'a' => self.add_selected_to_queue(),
'd' => self.remove_selected_from_queue(),
'q' => self.should_quit = true, 'q' => self.should_quit = true,
_ => (), _ => (),
} }
@ -65,23 +112,26 @@ impl App {
pub fn on_right(&mut self) {} pub fn on_right(&mut self) {}
pub fn on_up(&mut self) { pub fn on_up(&mut self) {
// TODO: Apply to selected playlist self.focused_window_mut().previous();
self.queue_playlist.playlist.previous();
} }
pub fn on_down(&mut self) { pub fn on_down(&mut self) {
// TODO: Apply to selected playlist self.focused_window_mut().next();
self.queue_playlist.playlist.next();
} }
pub fn on_enter(&mut self) { pub fn on_enter(&mut self) {
// TODO: Apply to selected playlist // TODO: Handle directory navigation
if let Some(current) = self.queue_playlist.playlist.current() { if let Some(current) = self.focused_window().current() {
self.play(current.into()); self.play(current.into());
} }
} }
pub fn on_tab(&mut self) {} pub fn on_tab(&mut self) {
self.focus = match self.focus {
AppWindow::QueuePlaylist => AppWindow::DirectoryPlaylist,
AppWindow::DirectoryPlaylist => AppWindow::QueuePlaylist,
}
}
pub fn on_tick(&mut self, _duration: Duration) {} pub fn on_tick(&mut self, _duration: Duration) {}
} }

View File

@ -1,4 +1,4 @@
use std::{ops::Deref, path::PathBuf}; use std::{error::Error, ops::Deref, path::PathBuf};
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct ListState { pub struct ListState {
@ -112,28 +112,25 @@ pub struct DirectoryPlaylist {
pub playlist: StatefulList<PlaylistElement>, pub playlist: StatefulList<PlaylistElement>,
} }
impl DirectoryPlaylist {
pub fn read_directory(path: PathBuf) -> Result<Self, Box<dyn Error>> {
let mut playlist: Vec<PlaylistElement> = vec![];
for entry in std::fs::read_dir(&path)? {
let entry = entry?;
playlist.push(entry.path());
}
Ok(Self {
directory: path,
playlist: StatefulList::with_items(playlist),
})
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct QueuePlaylist { pub struct QueuePlaylist {
pub playlist: StatefulList<PlaylistElement>, pub playlist: StatefulList<PlaylistElement>,
} }
impl QueuePlaylist {
pub fn generate_mock() -> Self {
Self {
playlist: StatefulList::with_items(vec![
"/home/hheikkinen/Music/Mariya Takeuchi/Mariya Takeuchi (竹内 まりや) - September [Lyrics KanRomEng] [3wkxIBBP7Vg].opus",
"/home/hheikkinen/Music/Compilerbau - Le Jardin/LEJARDIN.OGG",
"/home/hheikkinen/Music/Casiopea - Mint Jams (1982) FULL ALBUM/001 Take Me.opus",
"/home/hheikkinen/Music/Casiopea - Mint Jams (1982) FULL ALBUM/002 Asayake.opus",
"/home/hheikkinen/Music/A Groovy Thing/01 - Flamingosis - A Groovy Intro.mp3",
"/home/hheikkinen/Music/Brian Ellis - Smocaine 3/Brian Ellis - Smocaine 3- An MDE Film OST - 01 Smocaine Theme (TV Edit).wav",
"/home/hheikkinen/Music/Jun Fukamachi - Starview HCT-5808/T-02.m4a",
"/home/hheikkinen/Music/noby/Fluidy [189046035].flac",
].iter().map(|val| val.into()).collect()),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct TrackChangeOptions { pub struct TrackChangeOptions {
pub shuffle: bool, pub shuffle: bool,

View File

@ -2,15 +2,16 @@ use ratatui::{
prelude::*, prelude::*,
widgets::{block::Title, *}, widgets::{block::Title, *},
}; };
use rmp::{PlaylistElement, StatefulList};
use super::app::App; use super::app::App;
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) { pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default() let main_chunks = Layout::default()
.constraints([Constraint::Min(3), Constraint::Length(2)].as_ref()) .constraints([Constraint::Min(3), Constraint::Length(2)].as_ref())
.split(f.size()); .split(f.size());
draw_playlist(f, app, chunks[0]); draw_playlists(f, app, main_chunks[0]);
draw_player(f, app, chunks[1]); draw_player(f, app, main_chunks[1]);
} }
static PRIMARY_COLOR: Color = Color::Rgb(200, 150, 70); static PRIMARY_COLOR: Color = Color::Rgb(200, 150, 70);
@ -19,17 +20,40 @@ static SECONDARY_COLOR: Color = Color::Rgb(200, 200, 200);
static PRIMARY_CONTRAST: Color = Color::Black; static PRIMARY_CONTRAST: Color = Color::Black;
static CLEAR_CONTRAST: Color = Color::Rgb(100, 100, 100); static CLEAR_CONTRAST: Color = Color::Rgb(100, 100, 100);
fn draw_playlist<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) { fn draw_playlists<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
let tracks: Vec<_> = app let layout = Layout::default()
.queue_playlist .direction(Direction::Horizontal)
.playlist .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area);
draw_playlist(
f,
app,
layout[0],
app.directory_playlist.directory.to_string_lossy().into(),
&app.directory_playlist.playlist,
);
draw_playlist(
f,
app,
layout[1],
"queue".into(),
&app.queue_playlist.playlist,
);
}
fn draw_playlist<B: Backend>(
f: &mut Frame<B>,
app: &App,
area: Rect,
title: String,
playlist: &StatefulList<PlaylistElement>,
) {
let tracks: Vec<_> = playlist
.items .items
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, path)| { .map(|(index, path)| {
let selected = app let selected = playlist
.queue_playlist
.playlist
.state .state
.selected() .selected()
.map_or(false, |selected| index == selected); .map_or(false, |selected| index == selected);
@ -51,13 +75,14 @@ fn draw_playlist<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
} }
(_, _) => (), (_, _) => (),
} }
let content = Span::from(path.to_string_lossy().to_string()); let content = Span::from(render_track_name(path));
ListItem::new(content).set_style(style) ListItem::new(content).set_style(style)
}) })
.collect(); .collect();
let playlist = List::new(tracks) let playlist = List::new(tracks)
.block( .block(
Block::default() Block::default()
.title(title)
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(Style::default().fg(SECONDARY_COLOR)), .border_style(Style::default().fg(SECONDARY_COLOR)),
) )
@ -127,3 +152,11 @@ fn draw_player<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
f.render_widget(player, area) f.render_widget(player, area)
} }
fn render_track_name(track: &PlaylistElement) -> String {
track
.file_name()
.map_or("<unspeakable file name>".into(), |os_str| {
os_str.to_string_lossy().to_string()
})
}