diff --git a/src/gui/draw.rs b/src/gui/draw.rs index 8062e9e..c441346 100644 --- a/src/gui/draw.rs +++ b/src/gui/draw.rs @@ -191,19 +191,26 @@ impl SoundpadGui { .map(|s| s.to_string_lossy().to_string()) .unwrap_or_else(|| path.to_string_lossy().to_string()); + let mut dir_button_text = RichText::new(name.clone()); + if let Some(current_dir) = &self.app_state.current_dir { + if current_dir.eq(path) { + dir_button_text = dir_button_text.color(Color32::WHITE); + } + } + let dir_button = - Button::new(RichText::new(name).atom_max_width(area_size.x)) - .frame(false); + Button::new(dir_button_text.atom_max_width(area_size.x)).frame(false); + let dir_button_response = ui.add(dir_button); if dir_button_response.clicked() { - self.app_state.current_dir = Some(path.clone()); + self.open_dir(path); } let delete_dir_button = Button::new(icons::ICON_DELETE).frame(false); let delete_dir_button_response = ui.add_sized([18.0, 18.0], delete_dir_button); if delete_dir_button_response.clicked() { - self.remove_dir(path.clone()); + self.remove_dir(&path.clone()); } }); } @@ -226,10 +233,12 @@ impl SoundpadGui { ui.vertical(|ui| { ui.horizontal(|ui| { - ui.add_sized( + let search_field = ui.add_sized( [ui.available_width(), 22.0], TextEdit::singleline(&mut self.app_state.search_query).hint_text("Search..."), ); + + self.app_state.search_field_id = Some(search_field.id); }); ui.separator(); @@ -239,43 +248,49 @@ impl SoundpadGui { ui.set_min_height(area_size.y); ui.vertical(|ui| { - if let Some(path) = self.app_state.current_dir.clone() { - for entry in path.read_dir().unwrap() { - let entry = entry.unwrap(); - let entry_path = entry.path(); + let mut files: Vec = self.app_state.files.iter().cloned().collect(); + files.sort(); - if entry_path.is_dir() { - continue; + for entry_path in files { + if entry_path.is_dir() { + continue; + } + + if !extensions + .contains(&entry_path.extension().unwrap_or_default().to_str().unwrap()) + { + continue; + } + + let file_name = entry_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + + let search_query = self + .app_state + .search_query + .to_lowercase() + .trim() + .to_string(); + + if !file_name.to_lowercase().contains(search_query.as_str()) { + continue; + } + + let mut file_button_text = RichText::new(file_name); + if let Some(current_file) = &self.app_state.selected_file { + if current_file.eq(&entry_path) { + file_button_text = file_button_text.color(Color32::WHITE); } + } - if !extensions.contains( - &entry_path.extension().unwrap_or_default().to_str().unwrap(), - ) { - continue; - } - - let file_name = entry_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(); - - let search_query = self - .app_state - .search_query - .to_lowercase() - .trim() - .to_string(); - - if !file_name.to_lowercase().contains(search_query.as_str()) { - continue; - } - - let file_button = Button::new(file_name).frame(false); - let file_button_response = ui.add(file_button); - if file_button_response.clicked() { - self.play_file(entry_path); - } + let file_button = Button::new(file_button_text).frame(false); + let file_button_response = ui.add(file_button); + if file_button_response.clicked() { + self.play_file(&entry_path); + self.app_state.selected_file = Some(entry_path); } } }); diff --git a/src/gui/input.rs b/src/gui/input.rs index ee85fbb..ef9a0ba 100644 --- a/src/gui/input.rs +++ b/src/gui/input.rs @@ -1,23 +1,102 @@ use crate::gui::SoundpadGui; use egui::{Context, Key}; +use std::path::PathBuf; + impl SoundpadGui { pub fn handle_input(&mut self, ctx: &Context) { ctx.input(|i| { + // Close app on espace if i.key_pressed(Key::Escape) { std::process::exit(0); } - if !self.app_state.show_settings && i.key_pressed(Key::Space) { - self.play_toggle(); - } - - if i.key_pressed(Key::Slash) { + // Open/close settings + if i.key_pressed(Key::I) { self.app_state.show_settings = !self.app_state.show_settings; } - if self.app_state.show_settings && i.key_pressed(Key::Backspace) { - self.app_state.show_settings = false; + if i.key_pressed(Key::Enter) && self.app_state.selected_file.is_some() { + self.play_file(&self.app_state.selected_file.clone().unwrap()); + } + + if !self.app_state.show_settings { + // Pause / resume audio on space + if i.key_pressed(Key::Space) { + self.play_toggle(); + } + + // Focus search field + if i.key_pressed(Key::Slash) && self.app_state.search_field_id.is_some() { + self.app_state.force_focus_id = self.app_state.search_field_id; + } + + // Iterate through dirs if there are some + if i.modifiers.ctrl { + let arrow_up_pressed = i.key_pressed(Key::ArrowUp); + let arrow_down_pressed = i.key_pressed(Key::ArrowDown); + + if arrow_up_pressed || arrow_down_pressed { + if i.modifiers.shift && !self.app_state.dirs.is_empty() { + let mut dirs: Vec = + self.app_state.dirs.iter().cloned().collect(); + dirs.sort(); + + let current_dir_index: i8; + if let Some(current_dir) = &self.app_state.current_dir { + if let Some(index) = dirs.iter().position(|x| x == current_dir) { + current_dir_index = index as i8; + } else { + current_dir_index = -1; + } + } else { + current_dir_index = -1; + } + + let mut new_dir_index: i8; + + new_dir_index = current_dir_index - arrow_up_pressed as i8 + + arrow_down_pressed as i8; + + if new_dir_index < 0 { + new_dir_index = (dirs.len() - 1) as i8; + } else if new_dir_index >= dirs.len() as i8 { + new_dir_index = 0; + } + + self.open_dir(&dirs[new_dir_index as usize]); + } else if self.app_state.current_dir.is_some() { + let mut files: Vec = + self.app_state.files.iter().cloned().collect(); + files.sort(); + + let current_files_index: i64; + if let Some(selected_file) = &self.app_state.selected_file { + if let Some(index) = files.iter().position(|x| x == selected_file) { + current_files_index = index as i64; + } else { + current_files_index = -1; + } + } else { + current_files_index = -1; + } + + let mut new_files_index: i64; + + new_files_index = current_files_index - arrow_up_pressed as i64 + + arrow_down_pressed as i64; + + if new_files_index < 0 { + new_files_index = (files.len() - 1) as i64; + } else if new_files_index >= files.len() as i64 { + new_files_index = 0; + } + + self.app_state.selected_file = + Some(files[new_files_index as usize].clone()); + } + } + } } }); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index f018aee..2465e2a 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -77,10 +77,10 @@ impl SoundpadGui { } } - pub fn remove_dir(&mut self, path: PathBuf) { - self.app_state.dirs.remove(&path); + pub fn remove_dir(&mut self, path: &PathBuf) { + self.app_state.dirs.remove(path); if let Some(current_dir) = &self.app_state.current_dir - && current_dir == &path + && current_dir == path { self.app_state.current_dir = None; } @@ -88,7 +88,17 @@ impl SoundpadGui { self.config.save_to_file().ok(); } - pub fn play_file(&mut self, path: PathBuf) { + pub fn open_dir(&mut self, path: &PathBuf) { + self.app_state.current_dir = Some(path.clone()); + self.app_state.files = path + .read_dir() + .unwrap() + .filter_map(|res| res.ok()) + .map(|entry| entry.path()) + .collect(); + } + + pub fn play_file(&mut self, path: &PathBuf) { make_request_sync(Request::play(path.to_str().unwrap())).ok(); } diff --git a/src/gui/update.rs b/src/gui/update.rs index a74bb28..ac8a83d 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -40,6 +40,13 @@ impl App for SoundpadGui { } self.draw(ui).ok(); + + if let Some(force_focus_id) = self.app_state.force_focus_id { + ui.memory_mut(|reder| { + reder.request_focus(force_focus_id); + }); + self.app_state.force_focus_id = None; + } }); if self.app_state.position_dragged { diff --git a/src/types/gui.rs b/src/types/gui.rs index 354099d..cbb1a8a 100644 --- a/src/types/gui.rs +++ b/src/types/gui.rs @@ -1,4 +1,7 @@ use crate::types::audio_player::PlayerState; + +use egui::Id; + use std::{ collections::{HashMap, HashSet}, path::PathBuf, @@ -18,6 +21,12 @@ pub struct AppState { pub current_dir: Option, pub dirs: HashSet, + + pub selected_file: Option, + pub files: HashSet, + + pub search_field_id: Option, + pub force_focus_id: Option, } #[derive(Default, Debug, Clone)]