rmptuib93eae4
add documentation
+83 −35
4 files changed
- M src/app.rs +1 −1
- M src/connection.rs +20 −5
- M src/list.rs +11 −3
- M src/ui.rs +51 −26
| @@ -1,4 +1,4 @@ | |||
| 1 | − | use crate::connection::{self, Connection}; | |
| 1 | + | use crate::connection::Connection; | |
| 2 | 2 | use crate::list::ContentList; | |
| 3 | 3 | use mpd::Client; | |
| 4 | 4 | use std::collections::VecDeque; | |
| @@ -14,6 +14,7 @@ pub struct Connection { | |||
| 14 | 14 | } | |
| 15 | 15 | ||
| 16 | 16 | impl Connection { | |
| 17 | + | /// Create a new connection | |
| 17 | 18 | pub fn new(addrs: &str) -> Result<Self> { | |
| 18 | 19 | let mut conn = Client::connect(addrs).unwrap(); | |
| 19 | 20 | let songs_filenames: Vec<String> = conn | |
| @@ -30,6 +31,7 @@ impl Connection { | |||
| 30 | 31 | }) | |
| 31 | 32 | } | |
| 32 | 33 | ||
| 34 | + | /// Fzf prompt for selecting song | |
| 33 | 35 | pub fn play_fzf(&mut self) -> Result<()> { | |
| 34 | 36 | is_installed("fzf").map_err(|ex| ex)?; | |
| 35 | 37 | ||
| @@ -42,6 +44,7 @@ impl Connection { | |||
| 42 | 44 | Ok(()) | |
| 43 | 45 | } | |
| 44 | 46 | ||
| 47 | + | /// Dmenu prompt for selecting songs | |
| 45 | 48 | pub fn play_dmenu(&mut self) -> Result<()> { | |
| 46 | 49 | is_installed("dmenu").map_err(|ex| ex)?; | |
| 47 | 50 | let ss: Vec<&str> = self.songs_filenames.iter().map(|x| x.as_str()).collect(); | |
| @@ -60,6 +63,7 @@ impl Connection { | |||
| 60 | 63 | // } | |
| 61 | 64 | // } | |
| 62 | 65 | ||
| 66 | + | /// push the given song to queue | |
| 63 | 67 | pub fn push(&mut self, song: &Song) -> Result<()> { | |
| 64 | 68 | if self.conn.queue().unwrap().is_empty() { | |
| 65 | 69 | self.conn.push(song).unwrap(); | |
| @@ -75,6 +79,7 @@ impl Connection { | |||
| 75 | 79 | Ok(()) | |
| 76 | 80 | } | |
| 77 | 81 | ||
| 82 | + | /// Push all songs of a playlist into queue | |
| 78 | 83 | pub fn push_playlist(&mut self, playlist: &str) -> Result<()> { | |
| 79 | 84 | let songs: Vec<Song> = self.conn.playlist(playlist)?; | |
| 80 | 85 | ||
| @@ -88,6 +93,7 @@ impl Connection { | |||
| 88 | 93 | Ok(()) | |
| 89 | 94 | } | |
| 90 | 95 | ||
| 96 | + | /// Given a filename, get instance of Song with only filename | |
| 91 | 97 | pub fn get_song_with_only_filename(&self, filename: &str) -> Song { | |
| 92 | 98 | Song { | |
| 93 | 99 | file: filename.to_string(), | |
| @@ -102,9 +108,12 @@ impl Connection { | |||
| 102 | 108 | } | |
| 103 | 109 | } | |
| 104 | 110 | ||
| 111 | + | /// get current playing song | |
| 105 | 112 | pub fn get_current_song(&mut self) -> Option<String> { | |
| 106 | 113 | self.conn.currentsong().unwrap().unwrap_or_default().title | |
| 107 | 114 | } | |
| 115 | + | ||
| 116 | + | /// Print status to stdout | |
| 108 | 117 | pub fn status(&mut self) { | |
| 109 | 118 | let current_song = self.conn.currentsong(); | |
| 110 | 119 | let status = self.conn.status().unwrap(); | |
| @@ -121,29 +130,33 @@ impl Connection { | |||
| 121 | 130 | ); | |
| 122 | 131 | } | |
| 123 | 132 | ||
| 124 | − | pub fn now_playing(&mut self) -> Option<String> { | |
| 125 | − | let song = self.conn.currentsong().unwrap().unwrap_or_default(); | |
| 133 | + | /// Gives title of current playing song | |
| 134 | + | pub fn now_playing(&mut self) -> Result<Option<String>> { | |
| 135 | + | let song = self.conn.currentsong()?.unwrap(); | |
| 126 | 136 | if let Some(s) = song.title { | |
| 127 | 137 | if let Some(a) = song.artist { | |
| 128 | − | Some(format!("{} - {}", s, a)) | |
| 138 | + | return Ok(Some(format!("{} - {}", s, a))); | |
| 129 | 139 | } else { | |
| 130 | − | Some(s) | |
| 140 | + | return Ok(Some(s)); | |
| 131 | 141 | } | |
| 132 | 142 | } else { | |
| 133 | − | None | |
| 143 | + | return Ok(Some(song.file)); | |
| 134 | 144 | } | |
| 135 | 145 | } | |
| 136 | 146 | ||
| 137 | 147 | // Playback controls | |
| 148 | + | /// Pause playback | |
| 138 | 149 | pub fn pause(&mut self) { | |
| 139 | 150 | self.conn.pause(true).unwrap(); | |
| 140 | 151 | } | |
| 141 | 152 | ||
| 153 | + | /// Toggles playback | |
| 142 | 154 | pub fn toggle_pause(&mut self) { | |
| 143 | 155 | self.conn.toggle_pause().unwrap(); | |
| 144 | 156 | } | |
| 145 | 157 | ||
| 146 | 158 | // Volume controls | |
| 159 | + | /// Sets the volume | |
| 147 | 160 | pub fn set_volume(&mut self, u: String) { | |
| 148 | 161 | let cur = self.conn.status().unwrap().volume; | |
| 149 | 162 | let sym = u.get(0..1).unwrap(); | |
| @@ -156,6 +169,7 @@ impl Connection { | |||
| 156 | 169 | } | |
| 157 | 170 | } | |
| 158 | 171 | ||
| 172 | + | /// Gets the index of the string from the Vector | |
| 159 | 173 | fn get_choice_index(ss: &Vec<String>, selection: &str) -> usize { | |
| 160 | 174 | let mut choice: usize = 0; | |
| 161 | 175 | if let Some(index) = ss.iter().position(|s| s == selection) { | |
| @@ -165,6 +179,7 @@ fn get_choice_index(ss: &Vec<String>, selection: &str) -> usize { | |||
| 165 | 179 | choice | |
| 166 | 180 | } | |
| 167 | 181 | ||
| 182 | + | /// Checks if given program is installed in your system | |
| 168 | 183 | fn is_installed(ss: &str) -> Result<()> { | |
| 169 | 184 | let output = Command::new("which") | |
| 170 | 185 | .arg(ss) | |
| @@ -1,20 +1,28 @@ | |||
| 1 | 1 | #[derive(Debug)] | |
| 2 | 2 | pub struct ContentList<T> { | |
| 3 | 3 | pub list: Vec<T>, | |
| 4 | − | pub index: usize | |
| 4 | + | pub index: usize, | |
| 5 | 5 | } | |
| 6 | 6 | ||
| 7 | 7 | impl<T> ContentList<T> { | |
| 8 | 8 | pub fn new() -> Self { | |
| 9 | 9 | ContentList { | |
| 10 | 10 | list: Vec::new(), | |
| 11 | − | index: 0 | |
| 11 | + | index: 0, | |
| 12 | 12 | } | |
| 13 | 13 | } | |
| 14 | 14 | ||
| 15 | 15 | // Go to next item in list | |
| 16 | 16 | pub fn next(&mut self) { | |
| 17 | − | self.index += 1; | |
| 17 | + | // if self.index < self.list.len() - 1 { | |
| 18 | + | // self.index += 1; | |
| 19 | + | // } | |
| 20 | + | ||
| 21 | + | if self.index == self.list.len() - 1 { | |
| 22 | + | self.index = 0; | |
| 23 | + | } else { | |
| 24 | + | self.index += 1; | |
| 25 | + | } | |
| 18 | 26 | } | |
| 19 | 27 | ||
| 20 | 28 | /// Go to previous item in list | |
| @@ -1,4 +1,4 @@ | |||
| 1 | − | use crate::app::App; | |
| 1 | + | use crate::app::{App, AppResult}; | |
| 2 | 2 | use ratatui::{prelude::*, widgets::*}; | |
| 3 | 3 | ||
| 4 | 4 | /// Renders the user interface widgets | |
| @@ -8,9 +8,51 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 8 | 8 | // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html | |
| 9 | 9 | // - https://github.com/ratatui-org/ratatui/tree/master/examples | |
| 10 | 10 | ||
| 11 | − | // List of songs | |
| 11 | + | // Layout | |
| 12 | + | let main_layout = Layout::default() | |
| 13 | + | .direction(Direction::Vertical) | |
| 14 | + | .constraints(vec![Constraint::Percentage(93), Constraint::Percentage(7)]) | |
| 15 | + | .split(frame.size()); | |
| 16 | + | ||
| 17 | + | let outer_layout = Layout::default() | |
| 18 | + | .direction(Direction::Horizontal) | |
| 19 | + | .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 20 | + | .split(main_layout[0]); | |
| 21 | + | ||
| 22 | + | let inner_layout = Layout::default() | |
| 23 | + | .direction(Direction::Vertical) | |
| 24 | + | .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) | |
| 25 | + | .split(outer_layout[1]); | |
| 26 | + | ||
| 27 | + | draw_song_list(frame, app, outer_layout[0]); | |
| 28 | + | draw_queue(frame, app, inner_layout[0]); | |
| 29 | + | draw_playlists(frame, app, inner_layout[1]); | |
| 30 | + | ||
| 31 | + | // Status | |
| 32 | + | // let song = app | |
| 33 | + | // .conn | |
| 34 | + | // .now_playing().unwrap() | |
| 35 | + | // .unwrap_or_else(|| "No Title Found".to_string()); | |
| 36 | + | // | |
| 37 | + | // let (elapsed, total) = app.conn.conn.status().unwrap().time.unwrap(); | |
| 38 | + | // | |
| 39 | + | // let mut lines = vec![]; | |
| 40 | + | // lines.push(Line::from(vec![ | |
| 41 | + | // Span::styled("Current: ", Style::default().fg(Color::Red)), | |
| 42 | + | // Span::styled(song, Style::default().fg(Color::Yellow)), | |
| 43 | + | // Span::styled( | |
| 44 | + | // format!("[{}/{}]", elapsed.as_secs(), total.as_secs()), | |
| 45 | + | // Style::default().fg(Color::Yellow), | |
| 46 | + | // ), | |
| 47 | + | // ])); | |
| 48 | + | // let status = Paragraph::new(Text::from(lines)) | |
| 49 | + | // .block(Block::default().title("Status").borders(Borders::ALL)); | |
| 50 | + | // frame.render_widget(status, main_layout[1]); | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | /// draws list of songs | |
| 54 | + | fn draw_song_list(frame: &mut Frame, app: &mut App, size: Rect) { | |
| 12 | 55 | let mut song_state = ListState::default(); | |
| 13 | − | let size = Rect::new(100, 0, frame.size().width, frame.size().height - 3); | |
| 14 | 56 | let list = List::new(app.conn.songs_filenames.clone()) | |
| 15 | 57 | .block(Block::default().title("Song List").borders(Borders::ALL)) | |
| 16 | 58 | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |
| @@ -19,10 +61,11 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 19 | 61 | ||
| 20 | 62 | song_state.select(Some(app.song_list.index)); | |
| 21 | 63 | frame.render_stateful_widget(list, size, &mut song_state); | |
| 64 | + | } | |
| 22 | 65 | ||
| 23 | − | // Play Queue | |
| 66 | + | /// draws playing queue | |
| 67 | + | fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) { | |
| 24 | 68 | let mut queue_state = ListState::default(); | |
| 25 | − | let size = Rect::new(0, 0, 100, frame.size().height - 25); | |
| 26 | 69 | let list = List::new(app.play_deque.clone()) | |
| 27 | 70 | .block(Block::default().title("Play Queue").borders(Borders::ALL)) | |
| 28 | 71 | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |
| @@ -31,29 +74,11 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 31 | 74 | ||
| 32 | 75 | app.update_queue(); | |
| 33 | 76 | frame.render_stateful_widget(list, size, &mut queue_state); | |
| 77 | + | } | |
| 34 | 78 | ||
| 35 | − | // Status | |
| 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 | |
| 79 | + | /// draws all playlists | |
| 80 | + | fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) { | |
| 55 | 81 | let mut state = ListState::default(); | |
| 56 | − | let size = Rect::new(0, 25, 100, frame.size().height - 25 - 3); | |
| 57 | 82 | let list = List::new(app.pl_list.list.clone()) | |
| 58 | 83 | .block(Block::default().title("Playlists").borders(Borders::ALL)) | |
| 59 | 84 | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |