rmptui00c5c12
use filetree view instead of all song list
+158 −17
5 files changed
- M src/app.rs +12 −7
- A src/browser.rs +92 −0
- M src/handler.rs +15 −8
- M src/lib.rs +3 −0
- M src/ui.rs +36 −2
| @@ -1,5 +1,6 @@ | |||
| 1 | 1 | use std::time::Duration; | |
| 2 | 2 | ||
| 3 | + | use crate::browser::FileBrowser; | |
| 3 | 4 | use crate::connection::Connection; | |
| 4 | 5 | use crate::list::ContentList; | |
| 5 | 6 | use mpd::Client; | |
| @@ -17,11 +18,12 @@ pub struct App { | |||
| 17 | 18 | pub queue_list: ContentList<String>, | |
| 18 | 19 | pub pl_list: ContentList<String>, | |
| 19 | 20 | pub selected_tab: SelectedTab, | |
| 21 | + | pub browser: FileBrowser, | |
| 20 | 22 | } | |
| 21 | 23 | ||
| 22 | 24 | #[derive(Debug, PartialEq, Clone)] | |
| 23 | 25 | pub enum SelectedTab { | |
| 24 | − | SongList, | |
| 26 | + | DirectoryBrowser, | |
| 25 | 27 | Queue, | |
| 26 | 28 | Playlists, | |
| 27 | 29 | } | |
| @@ -29,9 +31,9 @@ pub enum SelectedTab { | |||
| 29 | 31 | impl SelectedTab { | |
| 30 | 32 | fn as_usize(&self) { | |
| 31 | 33 | match self { | |
| 32 | − | SelectedTab::SongList => 0, | |
| 33 | − | SelectedTab::Queue => 1, | |
| 34 | − | SelectedTab::Playlists => 2, | |
| 34 | + | SelectedTab::Queue => 0, | |
| 35 | + | SelectedTab::Playlists => 1, | |
| 36 | + | SelectedTab::DirectoryBrowser => 2, | |
| 35 | 37 | }; | |
| 36 | 38 | } | |
| 37 | 39 | } | |
| @@ -48,13 +50,15 @@ impl App { | |||
| 48 | 50 | let mut song_list = ContentList::new(); | |
| 49 | 51 | song_list.list = conn.songs_filenames.clone(); | |
| 50 | 52 | ||
| 53 | + | let browser = FileBrowser::new(); | |
| 51 | 54 | Ok(Self { | |
| 52 | 55 | running: true, | |
| 53 | 56 | conn, | |
| 54 | 57 | song_list, | |
| 55 | 58 | queue_list, | |
| 56 | 59 | pl_list, | |
| 57 | − | selected_tab: SelectedTab::SongList, | |
| 60 | + | selected_tab: SelectedTab::DirectoryBrowser, | |
| 61 | + | browser, | |
| 58 | 62 | }) | |
| 59 | 63 | } | |
| 60 | 64 | ||
| @@ -62,6 +66,7 @@ impl App { | |||
| 62 | 66 | self.conn.update_state(); | |
| 63 | 67 | self.conn.update_progress(); | |
| 64 | 68 | self.update_queue(); | |
| 69 | + | self.browser.update_directory(&mut self.conn).unwrap(); | |
| 65 | 70 | } | |
| 66 | 71 | ||
| 67 | 72 | pub fn quit(&mut self) { | |
| @@ -103,9 +108,9 @@ impl App { | |||
| 103 | 108 | ||
| 104 | 109 | pub fn cycle_tabls(&mut self) { | |
| 105 | 110 | self.selected_tab = match self.selected_tab { | |
| 106 | − | SelectedTab::SongList => SelectedTab::Queue, | |
| 111 | + | SelectedTab::DirectoryBrowser => SelectedTab::Queue, | |
| 107 | 112 | SelectedTab::Queue => SelectedTab::Playlists, | |
| 108 | − | SelectedTab::Playlists => SelectedTab::SongList, | |
| 113 | + | SelectedTab::Playlists => SelectedTab::DirectoryBrowser, | |
| 109 | 114 | }; | |
| 110 | 115 | } | |
| 111 | 116 | } | |
| @@ -0,0 +1,92 @@ | |||
| 1 | + | use mpd::Query; | |
| 2 | + | ||
| 3 | + | use crate::{app::AppResult, connection::Connection}; | |
| 4 | + | ||
| 5 | + | #[derive(Debug)] | |
| 6 | + | pub struct FileBrowser { | |
| 7 | + | pub filetree: Vec<(String, String)>, | |
| 8 | + | pub selected: usize, | |
| 9 | + | pub prev_selected: usize, | |
| 10 | + | pub path: String, | |
| 11 | + | pub prev_path: String, | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | impl FileBrowser { | |
| 15 | + | pub fn new() -> FileBrowser { | |
| 16 | + | FileBrowser { | |
| 17 | + | filetree: Vec::new(), | |
| 18 | + | selected: 0, | |
| 19 | + | prev_selected: 0, | |
| 20 | + | path: ".".to_string(), | |
| 21 | + | prev_path: ".".to_string(), | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | pub fn update_directory(&mut self, conn: &mut Connection) -> AppResult<()> { | |
| 26 | + | self.filetree.clear(); | |
| 27 | + | self.filetree = conn | |
| 28 | + | .conn | |
| 29 | + | .listfiles(self.path.as_str())? | |
| 30 | + | .into_iter() | |
| 31 | + | .filter(|(f, _)| f == "directory" || f == "file") | |
| 32 | + | .collect::<Vec<(String, String)>>(); | |
| 33 | + | ||
| 34 | + | Ok(()) | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // Go to next item in filetree | |
| 38 | + | pub fn next(&mut self) { | |
| 39 | + | // if self.selected < self.filetree.len() - 1 { | |
| 40 | + | // self.selected += 1; | |
| 41 | + | // } | |
| 42 | + | ||
| 43 | + | if self.selected == self.filetree.len() - 1 { | |
| 44 | + | self.selected = 0; | |
| 45 | + | } else { | |
| 46 | + | self.selected += 1; | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | /// Go to previous item in filetree | |
| 51 | + | pub fn prev(&mut self) { | |
| 52 | + | if self.selected == 0 { | |
| 53 | + | self.selected = self.filetree.len() - 1; | |
| 54 | + | } else { | |
| 55 | + | self.selected -= 1; | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | pub fn handle_enter(&mut self, conn: &mut Connection) -> AppResult<()> { | |
| 60 | + | let (t, path) = self.filetree.get(self.selected).unwrap(); | |
| 61 | + | if t == "directory" { | |
| 62 | + | if path != "." { | |
| 63 | + | self.prev_path = self.path.clone(); | |
| 64 | + | self.path = path.to_string(); | |
| 65 | + | self.update_directory(conn)?; | |
| 66 | + | self.prev_selected = self.selected; | |
| 67 | + | self.selected = 0; | |
| 68 | + | } | |
| 69 | + | } else { | |
| 70 | + | let list = conn | |
| 71 | + | .songs_filenames | |
| 72 | + | .iter() | |
| 73 | + | .map(|f| f.as_str()) | |
| 74 | + | .collect::<Vec<&str>>(); | |
| 75 | + | let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&path, &list) | |
| 76 | + | .get(0) | |
| 77 | + | .unwrap() | |
| 78 | + | .clone(); | |
| 79 | + | ||
| 80 | + | let song = conn.get_song_with_only_filename(filename); | |
| 81 | + | conn.push(&song)?; | |
| 82 | + | } | |
| 83 | + | Ok(()) | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | pub fn handle_go_back(&mut self, conn: &mut Connection) -> AppResult<()> { | |
| 87 | + | self.path = self.prev_path.clone(); | |
| 88 | + | self.selected = self.prev_selected; | |
| 89 | + | self.update_directory(conn)?; | |
| 90 | + | Ok(()) | |
| 91 | + | } | |
| 92 | + | } | |
| @@ -17,13 +17,13 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | 19 | KeyCode::Char('j') | KeyCode::Down => match app.selected_tab { | |
| 20 | − | SelectedTab::SongList => app.song_list.next(), | |
| 20 | + | SelectedTab::DirectoryBrowser => app.browser.next(), | |
| 21 | 21 | SelectedTab::Queue => app.queue_list.next(), | |
| 22 | 22 | SelectedTab::Playlists => app.pl_list.next(), | |
| 23 | 23 | }, | |
| 24 | 24 | ||
| 25 | 25 | KeyCode::Char('k') | KeyCode::Up => match app.selected_tab { | |
| 26 | − | SelectedTab::SongList => app.song_list.prev(), | |
| 26 | + | SelectedTab::DirectoryBrowser => app.browser.prev(), | |
| 27 | 27 | SelectedTab::Queue => app.queue_list.prev(), | |
| 28 | 28 | SelectedTab::Playlists => app.pl_list.prev(), | |
| 29 | 29 | }, | |
| @@ -32,12 +32,10 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 32 | 32 | // app.update_queue(); | |
| 33 | 33 | ||
| 34 | 34 | match app.selected_tab { | |
| 35 | − | SelectedTab::SongList => { | |
| 36 | − | let song = app.conn.get_song_with_only_filename( | |
| 37 | − | app.conn.songs_filenames.get(app.song_list.index).unwrap(), | |
| 38 | − | ); | |
| 39 | − | app.conn.push(&song)?; | |
| 35 | + | SelectedTab::DirectoryBrowser => { | |
| 36 | + | app.browser.handle_enter(&mut app.conn)?; | |
| 40 | 37 | } | |
| 38 | + | ||
| 41 | 39 | SelectedTab::Queue => { | |
| 42 | 40 | let song = app.conn.get_song_with_only_filename( | |
| 43 | 41 | app.queue_list.list.get(app.queue_list.index).unwrap(), | |
| @@ -51,6 +49,14 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 51 | 49 | } | |
| 52 | 50 | } | |
| 53 | 51 | ||
| 52 | + | KeyCode::Char('h') => match app.selected_tab { | |
| 53 | + | SelectedTab::DirectoryBrowser => { | |
| 54 | + | app.browser.handle_go_back(&mut app.conn)?; | |
| 55 | + | } | |
| 56 | + | SelectedTab::Queue => {} | |
| 57 | + | SelectedTab::Playlists => {} | |
| 58 | + | }, | |
| 59 | + | ||
| 54 | 60 | // Playback controls | |
| 55 | 61 | // Toggle Pause | |
| 56 | 62 | KeyCode::Char('p') => { | |
| @@ -105,7 +111,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 105 | 111 | } | |
| 106 | 112 | ||
| 107 | 113 | KeyCode::Char('1') => { | |
| 108 | − | app.selected_tab = SelectedTab::SongList; | |
| 114 | + | app.selected_tab = SelectedTab::DirectoryBrowser; | |
| 109 | 115 | } | |
| 110 | 116 | ||
| 111 | 117 | KeyCode::Char('2') => { | |
| @@ -116,6 +122,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { | |||
| 116 | 122 | app.selected_tab = SelectedTab::Playlists; | |
| 117 | 123 | } | |
| 118 | 124 | ||
| 125 | + | ||
| 119 | 126 | KeyCode::Char('n') => { | |
| 120 | 127 | app.conn.conn.next()?; | |
| 121 | 128 | } | |
| @@ -13,6 +13,9 @@ pub mod tui; | |||
| 13 | 13 | /// Content list | |
| 14 | 14 | pub mod list; | |
| 15 | 15 | ||
| 16 | + | /// File Browser | |
| 17 | + | pub mod browser; | |
| 18 | + | ||
| 16 | 19 | /// Event Handler | |
| 17 | 20 | pub mod event; | |
| 18 | 21 | ||
| @@ -1,4 +1,7 @@ | |||
| 1 | − | use crate::app::{App, AppResult, SelectedTab}; | |
| 1 | + | use crate::{ | |
| 2 | + | app::{App, AppResult, SelectedTab}, | |
| 3 | + | browser::FileBrowser, | |
| 4 | + | }; | |
| 2 | 5 | use ratatui::{ | |
| 3 | 6 | prelude::*, | |
| 4 | 7 | widgets::{block::Title, *}, | |
| @@ -48,9 +51,10 @@ pub fn render(app: &mut App, frame: &mut Frame) { | |||
| 48 | 51 | // frame.render_widget(tab, layout[0]); | |
| 49 | 52 | ||
| 50 | 53 | match app.selected_tab { | |
| 51 | − | SelectedTab::SongList => draw_song_list(frame, app, layout[1]), | |
| 54 | + | // SelectedTab::SongList => draw_song_list(frame, app, layout[1]), | |
| 52 | 55 | SelectedTab::Queue => draw_queue(frame, app, layout[1]), | |
| 53 | 56 | SelectedTab::Playlists => draw_playlists(frame, app, layout[1]), | |
| 57 | + | SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[1]), | |
| 54 | 58 | } | |
| 55 | 59 | } | |
| 56 | 60 | ||
| @@ -147,3 +151,33 @@ fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) { | |||
| 147 | 151 | ||
| 148 | 152 | frame.render_widget(progress_bar, size); | |
| 149 | 153 | } | |
| 154 | + | ||
| 155 | + | fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) { | |
| 156 | + | let mut song_state = ListState::default(); | |
| 157 | + | let total_songs = app.conn.conn.stats().unwrap().songs.to_string(); | |
| 158 | + | let mut list: Vec<String> = vec![]; | |
| 159 | + | for (t, s) in app.browser.filetree.iter() { | |
| 160 | + | if t == "file" { | |
| 161 | + | list.push(s.to_string()); | |
| 162 | + | } else { | |
| 163 | + | list.push(format!("[{}]", *s)); | |
| 164 | + | } | |
| 165 | + | } | |
| 166 | + | let list = List::new(list) | |
| 167 | + | .block( | |
| 168 | + | Block::default() | |
| 169 | + | .title(format!("File Browser: {}", app.browser.path.clone()).bold()) | |
| 170 | + | .title( | |
| 171 | + | Title::from(format!("Total Songs: {}", total_songs).bold().green()) | |
| 172 | + | .alignment(Alignment::Right), | |
| 173 | + | ) | |
| 174 | + | .borders(Borders::ALL), | |
| 175 | + | ) | |
| 176 | + | .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) | |
| 177 | + | .highlight_symbol(">>") | |
| 178 | + | .repeat_highlight_symbol(true) | |
| 179 | + | .scroll_padding(20); | |
| 180 | + | ||
| 181 | + | song_state.select(Some(app.browser.selected)); | |
| 182 | + | frame.render_stateful_widget(list, size, &mut song_state); | |
| 183 | + | } | |