Added directory playlist and simple queue management
parent
2d142e0657
commit
6089e9082a
70
src/app.rs
70
src/app.rs
|
|
@ -1,6 +1,6 @@
|
|||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use rmp::{QueuePlaylist, TrackChangeOptions};
|
||||
use rmp::{DirectoryPlaylist, PlaylistElement, QueuePlaylist, StatefulList, TrackChangeOptions};
|
||||
|
||||
use crate::playback::Playback;
|
||||
|
||||
|
|
@ -14,18 +14,45 @@ pub struct App {
|
|||
pub should_quit: bool,
|
||||
pub playback: Playback,
|
||||
pub track_change_options: TrackChangeOptions,
|
||||
pub focus: AppWindow,
|
||||
pub directory_playlist: DirectoryPlaylist,
|
||||
pub queue_playlist: QueuePlaylist,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum AppWindow {
|
||||
#[default]
|
||||
DirectoryPlaylist,
|
||||
QueuePlaylist,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(options: AppOptions) -> Self {
|
||||
Self {
|
||||
options,
|
||||
should_quit: false,
|
||||
playback: Playback::new(),
|
||||
// TODO: Maybe read from some configuration (args, file, etc...)
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
match key {
|
||||
'S' => self.toggle_shuffle(),
|
||||
'X' => self.toggle_next(),
|
||||
'R' => self.toggle_repeat(),
|
||||
' ' => self.toggle_pause(),
|
||||
'a' => self.add_selected_to_queue(),
|
||||
'd' => self.remove_selected_from_queue(),
|
||||
'q' => self.should_quit = true,
|
||||
_ => (),
|
||||
}
|
||||
|
|
@ -65,23 +112,26 @@ impl App {
|
|||
pub fn on_right(&mut self) {}
|
||||
|
||||
pub fn on_up(&mut self) {
|
||||
// TODO: Apply to selected playlist
|
||||
self.queue_playlist.playlist.previous();
|
||||
self.focused_window_mut().previous();
|
||||
}
|
||||
|
||||
pub fn on_down(&mut self) {
|
||||
// TODO: Apply to selected playlist
|
||||
self.queue_playlist.playlist.next();
|
||||
self.focused_window_mut().next();
|
||||
}
|
||||
|
||||
pub fn on_enter(&mut self) {
|
||||
// TODO: Apply to selected playlist
|
||||
if let Some(current) = self.queue_playlist.playlist.current() {
|
||||
// TODO: Handle directory navigation
|
||||
if let Some(current) = self.focused_window().current() {
|
||||
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) {}
|
||||
}
|
||||
|
|
|
|||
33
src/lib.rs
33
src/lib.rs
|
|
@ -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)]
|
||||
pub struct ListState {
|
||||
|
|
@ -112,28 +112,25 @@ pub struct DirectoryPlaylist {
|
|||
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)]
|
||||
pub struct QueuePlaylist {
|
||||
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 Kan⧸Rom⧸Eng] [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)]
|
||||
pub struct TrackChangeOptions {
|
||||
pub shuffle: bool,
|
||||
|
|
|
|||
55
src/ui.rs
55
src/ui.rs
|
|
@ -2,15 +2,16 @@ use ratatui::{
|
|||
prelude::*,
|
||||
widgets::{block::Title, *},
|
||||
};
|
||||
use rmp::{PlaylistElement, StatefulList};
|
||||
|
||||
use super::app::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())
|
||||
.split(f.size());
|
||||
draw_playlist(f, app, chunks[0]);
|
||||
draw_player(f, app, chunks[1]);
|
||||
draw_playlists(f, app, main_chunks[0]);
|
||||
draw_player(f, app, main_chunks[1]);
|
||||
}
|
||||
|
||||
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 CLEAR_CONTRAST: Color = Color::Rgb(100, 100, 100);
|
||||
|
||||
fn draw_playlist<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
|
||||
let tracks: Vec<_> = app
|
||||
.queue_playlist
|
||||
.playlist
|
||||
fn draw_playlists<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.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
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, path)| {
|
||||
let selected = app
|
||||
.queue_playlist
|
||||
.playlist
|
||||
let selected = playlist
|
||||
.state
|
||||
.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)
|
||||
})
|
||||
.collect();
|
||||
let playlist = List::new(tracks)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.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)
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue