rmptui05c4b17
damn
+146 −33
6 files changed
- M src/app.rs +27 −11
- M src/connection.rs +39 −4
- M src/handler.rs +38 −6
- M src/list.rs +4 −2
- M src/main.rs +1 −1
- M src/ui.rs +37 −9
| @@ -1,5 +1,6 @@ | |||
| 1 | − | use crate::connection::Connection; | |
| 1 | + | use crate::connection::{self, Connection}; | |
| 2 | 2 | use crate::list::ContentList; | |
| 3 | + | use mpd::Client; | |
| 3 | 4 | use std::collections::VecDeque; | |
| 4 | 5 | ||
| 5 | 6 | // Application result type | |
| @@ -12,20 +13,30 @@ pub struct App { | |||
| 12 | 13 | pub running: bool, | |
| 13 | 14 | pub conn: Connection, | |
| 14 | 15 | pub play_deque: VecDeque<String>, | |
| 15 | − | pub list: ContentList, | |
| 16 | + | pub song_list: ContentList<String>, | |
| 17 | + | pub queue_list: ContentList<String>, | |
| 18 | + | pub pl_list: ContentList<String>, | |
| 16 | 19 | } | |
| 17 | 20 | ||
| 18 | 21 | impl App { | |
| 19 | − | pub fn new(addrs: &str) -> Self { | |
| 22 | + | pub fn builder(addrs: &str) -> AppResult<Self> { | |
| 20 | 23 | let mut conn = Connection::new(addrs).unwrap(); | |
| 21 | 24 | let mut vec: VecDeque<String> = VecDeque::new(); | |
| 25 | + | let mut pl_list = ContentList::new(); | |
| 26 | + | pl_list.list = Self::get_playlist(&mut conn.conn)?; | |
| 22 | 27 | Self::get_queue(&mut conn, &mut vec); | |
| 23 | − | Self { | |
| 28 | + | ||
| 29 | + | let mut song_list = ContentList::new(); | |
| 30 | + | song_list.list = conn.songs_filenames.clone(); | |
| 31 | + | ||
| 32 | + | Ok(Self { | |
| 24 | 33 | running: true, | |
| 25 | 34 | conn, | |
| 26 | 35 | play_deque: vec, | |
| 27 | − | list: ContentList::new(), | |
| 28 | − | } | |
| 36 | + | song_list, | |
| 37 | + | queue_list: ContentList::new(), | |
| 38 | + | pl_list, | |
| 39 | + | }) | |
| 29 | 40 | } | |
| 30 | 41 | ||
| 31 | 42 | pub fn tick(&self) {} | |
| @@ -52,10 +63,15 @@ impl App { | |||
| 52 | 63 | self.play_deque.clear(); | |
| 53 | 64 | Self::get_queue(&mut self.conn, &mut self.play_deque); | |
| 54 | 65 | } | |
| 55 | − | } | |
| 56 | 66 | ||
| 57 | − | fn to_vecdeque(filenames: &Vec<String>) -> VecDeque<String> { | |
| 58 | − | let mut v: VecDeque<String> = VecDeque::new(); | |
| 59 | − | v = filenames.iter().map(|x| x.to_string()).collect(); | |
| 60 | − | v | |
| 67 | + | pub fn get_playlist(conn: &mut Client) -> AppResult<Vec<String>> { | |
| 68 | + | let list: Vec<String> = conn.playlists()?.iter().map(|p| p.clone().name).collect(); | |
| 69 | + | ||
| 70 | + | Ok(list) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | pub fn update_playlist(&mut self) -> AppResult<()> { | |
| 74 | + | Self::get_playlist(&mut self.conn.conn)?; | |
| 75 | + | Ok(()) | |
| 76 | + | } | |
| 61 | 77 | } | |
| @@ -10,6 +10,7 @@ pub type Error = Box<dyn std::error::Error>; | |||
| 10 | 10 | pub struct Connection { | |
| 11 | 11 | pub conn: Client, | |
| 12 | 12 | pub songs_filenames: Vec<String>, | |
| 13 | + | // pub state: String, | |
| 13 | 14 | } | |
| 14 | 15 | ||
| 15 | 16 | impl Connection { | |
| @@ -25,6 +26,7 @@ impl Connection { | |||
| 25 | 26 | Ok(Self { | |
| 26 | 27 | conn, | |
| 27 | 28 | songs_filenames, | |
| 29 | + | // state: "Stopped".to_string(), | |
| 28 | 30 | }) | |
| 29 | 31 | } | |
| 30 | 32 | ||
| @@ -50,14 +52,22 @@ impl Connection { | |||
| 50 | 52 | Ok(()) | |
| 51 | 53 | } | |
| 52 | 54 | ||
| 55 | + | // pub fn update_state(&mut self) { | |
| 56 | + | // match self.conn.status().unwrap().state { | |
| 57 | + | // State::Stop => self.state = "Stopped".to_string(), | |
| 58 | + | // State::Play => self.state = "Playing".to_string(), | |
| 59 | + | // State::Pause => self.state = "Paused".to_string(), | |
| 60 | + | // } | |
| 61 | + | // } | |
| 62 | + | ||
| 53 | 63 | pub fn push(&mut self, song: &Song) -> Result<()> { | |
| 54 | 64 | if self.conn.queue().unwrap().is_empty() { | |
| 55 | 65 | self.conn.push(song).unwrap(); | |
| 56 | 66 | self.conn.play().unwrap(); | |
| 57 | 67 | } else { | |
| 58 | − | self.conn.push(song).unwrap(); | |
| 59 | − | if self.conn.status().unwrap().state == State::Stop { | |
| 60 | − | self.conn.play().unwrap(); | |
| 68 | + | self.conn.push(song)?; | |
| 69 | + | if self.conn.status()?.state == State::Stop { | |
| 70 | + | self.conn.play()?; | |
| 61 | 71 | } | |
| 62 | 72 | self.conn.next().unwrap(); | |
| 63 | 73 | } | |
| @@ -65,6 +75,19 @@ impl Connection { | |||
| 65 | 75 | Ok(()) | |
| 66 | 76 | } | |
| 67 | 77 | ||
| 78 | + | pub fn push_playlist(&mut self, playlist: &str) -> Result<()> { | |
| 79 | + | let songs: Vec<Song> = self.conn.playlist(playlist)?; | |
| 80 | + | ||
| 81 | + | for song in songs { | |
| 82 | + | if self.songs_filenames.contains(&song.file) { | |
| 83 | + | let song = self.get_song_with_only_filename(&song.file); | |
| 84 | + | self.conn.push(&song)?; | |
| 85 | + | self.conn.play()?; | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | Ok(()) | |
| 89 | + | } | |
| 90 | + | ||
| 68 | 91 | pub fn get_song_with_only_filename(&self, filename: &str) -> Song { | |
| 69 | 92 | Song { | |
| 70 | 93 | file: filename.to_string(), | |
| @@ -81,7 +104,6 @@ impl Connection { | |||
| 81 | 104 | ||
| 82 | 105 | pub fn get_current_song(&mut self) -> Option<String> { | |
| 83 | 106 | self.conn.currentsong().unwrap().unwrap_or_default().title | |
| 84 | − | ||
| 85 | 107 | } | |
| 86 | 108 | pub fn status(&mut self) { | |
| 87 | 109 | let current_song = self.conn.currentsong(); | |
| @@ -99,6 +121,19 @@ impl Connection { | |||
| 99 | 121 | ); | |
| 100 | 122 | } | |
| 101 | 123 | ||
| 124 | + | pub fn now_playing(&mut self) -> Option<String> { | |
| 125 | + | let song = self.conn.currentsong().unwrap().unwrap_or_default(); | |
| 126 | + | if let Some(s) = song.title { | |
| 127 | + | if let Some(a) = song.artist { | |
| 128 | + | Some(format!("{} - {}", s, a)) | |
| 129 | + | } else { | |
| 130 | + | Some(s) | |
| 131 | + | } | |
| 132 | + | } else { | |
| 133 | + | None | |
| 134 | + | } | |
| 135 | + | } | |
| 136 | + | ||
| 102 | 137 | // Playback controls | |
| 103 | 138 | pub fn pause(&mut self) { | |
| 104 | 139 | self.conn.pause(true).unwrap(); | |
| @@ -11,17 +11,17 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 11 | 11 | } | |
| 12 | 12 | ||
| 13 | 13 | KeyCode::Char('j') => { | |
| 14 | − | app.list.next(); | |
| 14 | + | app.song_list.next(); | |
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | 17 | KeyCode::Char('k') => { | |
| 18 | − | app.list.prev(); | |
| 18 | + | app.song_list.prev(); | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | KeyCode::Enter | KeyCode::Char('l') => { | |
| 22 | − | let song = app.conn.get_song_with_only_filename(app.conn.songs_filenames.get(app.list.index).unwrap()); | |
| 23 | − | app.conn.push(&song).unwrap(); | |
| 24 | − | app.update_queue(); | |
| 22 | + | let song = app.conn.get_song_with_only_filename(app.conn.songs_filenames.get(app.song_list.index).unwrap()); | |
| 23 | + | app.conn.push(&song)?; | |
| 24 | + | // app.update_queue(); | |
| 25 | 25 | } | |
| 26 | 26 | ||
| 27 | 27 | // Playback controls | |
| @@ -35,10 +35,42 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 35 | 35 | app.conn.pause(); | |
| 36 | 36 | } | |
| 37 | 37 | ||
| 38 | + | ||
| 38 | 39 | // Clearn Queue | |
| 39 | 40 | KeyCode::Char('x') => { | |
| 40 | 41 | app.conn.conn.clear()?; | |
| 41 | − | app.update_queue(); | |
| 42 | + | // app.update_queue(); | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | KeyCode::Char('d') => { | |
| 46 | + | app.conn.play_dmenu()?; | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | KeyCode::Down=> { | |
| 50 | + | app.pl_list.next(); | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | KeyCode::Up=> { | |
| 54 | + | app.pl_list.prev(); | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | ||
| 58 | + | KeyCode::Right => { | |
| 59 | + | app.conn.push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?; | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | KeyCode::Char('f')=> { | |
| 63 | + | // let place = app.conn.conn.status().unwrap().duration; | |
| 64 | + | let (pos, _) = app.conn.conn.status().unwrap().time.unwrap(); | |
| 65 | + | let pos: i64 = (pos.as_secs() + 2).try_into().unwrap(); | |
| 66 | + | app.conn.conn.seek(2, pos )?; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | KeyCode::Char('b')=> { | |
| 70 | + | // let place = app.conn.conn.status().unwrap().duration; | |
| 71 | + | let (pos, _) = app.conn.conn.status().unwrap().time.unwrap(); | |
| 72 | + | let pos: i64 = (pos.as_secs() - 2).try_into().unwrap(); | |
| 73 | + | app.conn.conn.seek(2, pos )?; | |
| 42 | 74 | } | |
| 43 | 75 | _ => {} | |
| 44 | 76 | } | |
| @@ -1,11 +1,13 @@ | |||
| 1 | 1 | #[derive(Debug)] | |
| 2 | − | pub struct ContentList { | |
| 2 | + | pub struct ContentList<T> { | |
| 3 | + | pub list: Vec<T>, | |
| 3 | 4 | pub index: usize | |
| 4 | 5 | } | |
| 5 | 6 | ||
| 6 | − | impl ContentList { | |
| 7 | + | impl<T> ContentList<T> { | |
| 7 | 8 | pub fn new() -> Self { | |
| 8 | 9 | ContentList { | |
| 10 | + | list: Vec::new(), | |
| 9 | 11 | index: 0 | |
| 10 | 12 | } | |
| 11 | 13 | } | |
| @@ -42,7 +42,7 @@ fn main() -> AppResult<()> { | |||
| 42 | 42 | ||
| 43 | 43 | let backend = CrosstermBackend::new(io::stderr()); | |
| 44 | 44 | let terminal = Terminal::new(backend)?; | |
| 45 | − | let mut app = App::new("127.0.0.1:6600"); | |
| 45 | + | let mut app = App::builder("127.0.0.1:6600")?; | |
| 46 | 46 | let events = EventHandler::new(250); | |
| 47 | 47 | ||
| 48 | 48 | let mut tui = tui::Tui::new(terminal, events); | |
| @@ -1,4 +1,4 @@ | |||
| 1 | − | use crate::{app::App, connection::Connection}; | |
| 1 | + | use crate::app::App; | |
| 2 | 2 | use ratatui::{prelude::*, widgets::*}; | |
| 3 | 3 | ||
| 4 | 4 | /// Renders the user interface widgets | |
| @@ -9,7 +9,7 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 9 | 9 | // - https://github.com/ratatui-org/ratatui/tree/master/examples | |
| 10 | 10 | ||
| 11 | 11 | // List of songs | |
| 12 | − | let mut state = ListState::default(); | |
| 12 | + | let mut song_state = ListState::default(); | |
| 13 | 13 | let size = Rect::new(100, 0, frame.size().width, frame.size().height - 3); | |
| 14 | 14 | let list = List::new(app.conn.songs_filenames.clone()) | |
| 15 | 15 | .block(Block::default().title("Song List").borders(Borders::ALL)) | |
| @@ -17,21 +17,49 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 17 | 17 | .highlight_symbol(">>") | |
| 18 | 18 | .repeat_highlight_symbol(true); | |
| 19 | 19 | ||
| 20 | − | state.select(Some(app.list.index)); | |
| 21 | − | frame.render_stateful_widget(list, size, &mut state); | |
| 20 | + | song_state.select(Some(app.song_list.index)); | |
| 21 | + | frame.render_stateful_widget(list, size, &mut song_state); | |
| 22 | 22 | ||
| 23 | 23 | // Play Queue | |
| 24 | + | let mut queue_state = ListState::default(); | |
| 24 | 25 | let size = Rect::new(0, 0, 100, frame.size().height - 25); | |
| 25 | 26 | let list = List::new(app.play_deque.clone()) | |
| 26 | 27 | .block(Block::default().title("Play Queue").borders(Borders::ALL)) | |
| 27 | 28 | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |
| 28 | 29 | .highlight_symbol(">>") | |
| 29 | 30 | .repeat_highlight_symbol(true); | |
| 30 | − | frame.render_widget(list, size); | |
| 31 | + | ||
| 32 | + | app.update_queue(); | |
| 33 | + | frame.render_stateful_widget(list, size, &mut queue_state); | |
| 31 | 34 | ||
| 32 | 35 | // Status | |
| 33 | − | let size = Rect::new(0, frame.size().height - 3, frame.size().width, 3); | |
| 34 | − | let status = Paragraph::new(app.conn.conn.status().unwrap().volume.to_string()) | |
| 35 | − | .block(Block::default().title("Status").borders(Borders::ALL)); | |
| 36 | − | frame.render_widget(status, size); | |
| 36 | + | // let size = Rect::new(0, frame.size().height - 3, frame.size().width, 3); | |
| 37 | + | // let song = app | |
| 38 | + | // .conn | |
| 39 | + | // .now_playing() | |
| 40 | + | // .unwrap_or_else(|| "No Title Found".to_string()); | |
| 41 | + | // | |
| 42 | + | // let (elapsed, total) = app.conn.conn.status().unwrap().time.unwrap(); | |
| 43 | + | // | |
| 44 | + | // let mut lines = vec![]; | |
| 45 | + | // lines.push(Line::from(vec![ | |
| 46 | + | // Span::styled("Current: ", Style::default().fg(Color::Red)), | |
| 47 | + | // Span::styled(song, Style::default().fg(Color::Yellow)), | |
| 48 | + | // Span::styled(format!("[{}/{}]", elapsed.as_secs(), total.as_secs()), Style::default().fg(Color::Yellow)), | |
| 49 | + | // ])); | |
| 50 | + | // let status = Paragraph::new(Text::from(lines)) | |
| 51 | + | // .block(Block::default().title("Status").borders(Borders::ALL)); | |
| 52 | + | // frame.render_widget(status, size); | |
| 53 | + | ||
| 54 | + | // Playlists | |
| 55 | + | let mut state = ListState::default(); | |
| 56 | + | let size = Rect::new(0, 25, 100, frame.size().height - 25 - 3); | |
| 57 | + | let list = List::new(app.pl_list.list.clone()) | |
| 58 | + | .block(Block::default().title("Playlists").borders(Borders::ALL)) | |
| 59 | + | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |
| 60 | + | .highlight_symbol(">>") | |
| 61 | + | .repeat_highlight_symbol(true); | |
| 62 | + | ||
| 63 | + | state.select(Some(app.pl_list.index)); | |
| 64 | + | frame.render_stateful_widget(list, size, &mut state); | |
| 37 | 65 | } | |