From 6089e9082ab01c74c7d181fba118e7a2a9db8142 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Fri, 23 Aug 2024 01:16:30 +0300 Subject: [PATCH] Added directory playlist and simple queue management --- src/app.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 33 ++++++++++++------------- src/ui.rs | 55 +++++++++++++++++++++++++++++++++--------- 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/app.rs b/src/app.rs index 48f6195..8368185 100644 --- a/src/app.rs +++ b/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 { + match &self.focus { + AppWindow::DirectoryPlaylist => &self.directory_playlist.playlist, + AppWindow::QueuePlaylist => &self.queue_playlist.playlist, + } + } + + pub fn focused_window_mut(&mut self) -> &mut StatefulList { + 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) {} } diff --git a/src/lib.rs b/src/lib.rs index 9f2341e..5bfadb7 100644 --- a/src/lib.rs +++ b/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, } +impl DirectoryPlaylist { + pub fn read_directory(path: PathBuf) -> Result> { + let mut playlist: Vec = 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, } -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, diff --git a/src/ui.rs b/src/ui.rs index 2ac23c8..31b5d95 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -2,15 +2,16 @@ use ratatui::{ prelude::*, widgets::{block::Title, *}, }; +use rmp::{PlaylistElement, StatefulList}; use super::app::App; pub fn draw(f: &mut Frame, 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(f: &mut Frame, app: &App, area: Rect) { - let tracks: Vec<_> = app - .queue_playlist - .playlist +fn draw_playlists(f: &mut Frame, 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( + f: &mut Frame, + app: &App, + area: Rect, + title: String, + playlist: &StatefulList, +) { + 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(f: &mut Frame, 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(f: &mut Frame, app: &App, area: Rect) { f.render_widget(player, area) } + +fn render_track_name(track: &PlaylistElement) -> String { + track + .file_name() + .map_or("".into(), |os_str| { + os_str.to_string_lossy().to_string() + }) +}