use crate::browser::FileBrowser; use crate::connection::Connection; use crate::list::ContentList; use crate::ui::InputMode; use mpd::Client; // Application result type pub type AppResult = std::result::Result>; /// Application #[derive(Debug)] pub struct App { /// check if app is running pub running: bool, pub conn: Connection, pub queue_list: ContentList, pub pl_list: ContentList, pub selected_tab: SelectedTab, pub browser: FileBrowser, // Search pub inputmode: InputMode, pub search_input: String, pub cursor_position: usize, // add to playlists pub playlist_popup: bool, pub append_list: ContentList, } #[derive(Debug, PartialEq, Clone)] pub enum SelectedTab { DirectoryBrowser, Queue, Playlists, } impl App { pub fn builder(addrs: &str) -> AppResult { let mut conn = Connection::new(addrs).unwrap(); let mut queue_list = ContentList::new(); let mut pl_list = ContentList::new(); pl_list.list = Self::get_playlist(&mut conn.conn)?; let append_list = Self::get_append_list(&mut conn.conn)?; Self::get_queue(&mut conn, &mut queue_list.list); let browser = FileBrowser::new(); Ok(Self { running: true, conn, queue_list, pl_list, selected_tab: SelectedTab::DirectoryBrowser, browser, inputmode: InputMode::Normal, search_input: String::new(), cursor_position: 0, playlist_popup: false, append_list, }) } pub fn tick(&mut self) { self.conn.update_status(); self.update_queue(); // self.browser.update_directory(&mut self.conn).unwrap(); } pub fn quit(&mut self) { self.running = false; } pub fn get_queue(conn: &mut Connection, vec: &mut Vec) { // conn.conn.queue().unwrap().into_iter().for_each(|x| { // if let Some(title) = x.title { // if let Some(artist) = x.artist { // vec.push(format!("{} - {}", artist, title)); // } else { // vec.push(title) // } // } else { // vec.push(x.file) // } // }); conn.conn.queue().unwrap().into_iter().for_each(|x| { vec.push(x.file); }); } pub fn update_queue(&mut self) { self.queue_list.list.clear(); Self::get_queue(&mut self.conn, &mut self.queue_list.list); } pub fn get_playlist(conn: &mut Client) -> AppResult> { let list: Vec = conn.playlists()?.iter().map(|p| p.clone().name).collect(); Ok(list) } pub fn get_append_list(conn: &mut Client) -> AppResult> { let mut list = ContentList::new(); list.list.push("Current Playlist".to_string()); for item in Self::get_playlist(conn)? { list.list.push(item.to_string()); } Ok(list) } pub fn update_playlist(&mut self) -> AppResult<()> { Self::get_playlist(&mut self.conn.conn)?; Ok(()) } pub fn remove_from_current_playlist(&mut self) { let mut file = String::new(); match self.selected_tab { SelectedTab::DirectoryBrowser => { let (_, f) = self.browser.filetree.get(self.browser.selected).unwrap(); file.push_str(f); } SelectedTab::Queue => { file = self.queue_list.list.get(self.queue_list.index).unwrap().to_string(); } _ => {} } for (i, song) in self.queue_list.list.clone().iter().enumerate() { if song.contains(&file) { self.conn.conn.delete(i as u32).unwrap(); } } self.update_queue(); } pub fn cycle_tabls(&mut self) { self.selected_tab = match self.selected_tab { SelectedTab::DirectoryBrowser => SelectedTab::Queue, SelectedTab::Queue => SelectedTab::Playlists, SelectedTab::Playlists => SelectedTab::DirectoryBrowser, }; } pub fn search_song(&mut self) -> AppResult<()> { let filename = self.conn.get_full_path(&self.search_input)?; let song = self.conn.get_song_with_only_filename(&filename); self.conn.push(&song)?; Ok(()) } // Cursor movements pub fn move_cursor_left(&mut self) { let cursor_moved_left = self.cursor_position.saturating_sub(1); self.cursor_position = self.clamp_cursor(cursor_moved_left); } pub fn move_cursor_right(&mut self) { let cursor_moved_right = self.cursor_position.saturating_add(1); self.cursor_position = self.clamp_cursor(cursor_moved_right); } pub fn enter_char(&mut self, new_char: char) { self.search_input.insert(self.cursor_position, new_char); self.move_cursor_right(); } pub fn delete_char(&mut self) { let is_not_cursor_leftmost = self.cursor_position != 0; if is_not_cursor_leftmost { // Method "remove" is not used on the saved text for deleting the selected char. // Reason: Using remove on String works on bytes instead of the chars. // Using remove would require special care because of char boundaries. let current_index = self.cursor_position; let from_left_to_current_index = current_index - 1; // Getting all characters before the selected character. let before_char_to_delete = self.search_input.chars().take(from_left_to_current_index); // Getting all characters after selected character. let after_char_to_delete = self.search_input.chars().skip(current_index); // Put all characters together except the selected one. // By leaving the selected one out, it is forgotten and therefore deleted. self.search_input = before_char_to_delete.chain(after_char_to_delete).collect(); self.move_cursor_left(); } } pub fn clamp_cursor(&self, new_cursor_pos: usize) -> usize { new_cursor_pos.clamp(0, self.search_input.len()) } pub fn reset_cursor(&mut self) { self.cursor_position = 0; } }