diff --git a/musicdb-client/src/gui.rs b/musicdb-client/src/gui.rs index e8de835..6dbf7c5 100755 --- a/musicdb-client/src/gui.rs +++ b/musicdb-client/src/gui.rs @@ -44,6 +44,35 @@ pub enum GuiEvent { Exit, } +pub fn hotkey_deselect_all(modifiers: &ModifiersState, key: Option) -> bool { + !modifiers.logo() + && !modifiers.alt() + && modifiers.ctrl() + && !modifiers.shift() + && matches!(key, Some(VirtualKeyCode::S)) +} +pub fn hotkey_select_all(modifiers: &ModifiersState, key: Option) -> bool { + !modifiers.logo() + && !modifiers.alt() + && modifiers.ctrl() + && !modifiers.shift() + && matches!(key, Some(VirtualKeyCode::A)) +} +pub fn hotkey_select_albums(modifiers: &ModifiersState, key: Option) -> bool { + !modifiers.logo() + && !modifiers.alt() + && modifiers.ctrl() + && modifiers.shift() + && matches!(key, Some(VirtualKeyCode::A)) +} +pub fn hotkey_select_songs(modifiers: &ModifiersState, key: Option) -> bool { + !modifiers.logo() + && !modifiers.alt() + && modifiers.ctrl() + && modifiers.shift() + && matches!(key, Some(VirtualKeyCode::S)) +} + pub fn main( database: Arc>, connection: TcpStream, @@ -234,7 +263,6 @@ impl Gui { } } Command::ErrorInfo(t, d) => { - eprintln!("{t:?} | {d:?}"); let (t, d) = (t.clone(), d.clone()); notif_sender .send(Box::new(move |_| { diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index ca2bf6c..0824678 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -31,6 +31,8 @@ use crate::{ gui_wrappers::WithFocusHotkey, }; +use self::selected::Selected; + /* This is responsible for showing the library, @@ -75,70 +77,140 @@ impl Clone for LibraryBrowser { Self::new(self.config.clone()) } } -#[derive(Clone)] -struct Selected(Arc, HashSet, HashSet)>>); -impl Selected { - pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec { - let lock = self.0.lock().unwrap(); - let (sel_artists, sel_albums, sel_songs) = &*lock; - let mut out = vec![]; - for (artist, singles, albums, _) in &lb.library_filtered { - let artist_selected = sel_artists.contains(artist); - let mut local_artist_owned = vec![]; - let mut local_artist = if artist_selected { - &mut local_artist_owned - } else { - &mut out - }; - for (song, _) in singles { - let song_selected = sel_songs.contains(song); - if song_selected { - local_artist.push(QueueContent::Song(*song).into()); - } - } - for (album, songs, _) in albums { - let album_selected = sel_albums.contains(album); - let mut local_album_owned = vec![]; - let local_album = if album_selected { - &mut local_album_owned +mod selected { + use super::*; + #[derive(Clone)] + pub struct Selected( + // artist, album, songs + Arc, HashSet, HashSet)>>, + Arc, + ); + impl Selected { + pub fn new(update: Arc) -> Self { + Self(Default::default(), update) + } + pub fn clear(&self) { + self.set_to(HashSet::new(), HashSet::new(), HashSet::new()) + } + pub fn set_to(&self, artists: HashSet, albums: HashSet, songs: HashSet) { + let mut s = self.0.lock().unwrap(); + s.0 = artists; + s.1 = albums; + s.2 = songs; + self.changed(); + } + pub fn contains_artist(&self, id: &ArtistId) -> bool { + self.0.lock().unwrap().0.contains(id) + } + pub fn contains_album(&self, id: &AlbumId) -> bool { + self.0.lock().unwrap().1.contains(id) + } + pub fn contains_song(&self, id: &SongId) -> bool { + self.0.lock().unwrap().2.contains(id) + } + pub fn insert_artist(&self, id: ArtistId) -> bool { + self.changed(); + self.0.lock().unwrap().0.insert(id) + } + pub fn insert_album(&self, id: AlbumId) -> bool { + self.changed(); + self.0.lock().unwrap().1.insert(id) + } + pub fn insert_song(&self, id: SongId) -> bool { + self.changed(); + self.0.lock().unwrap().2.insert(id) + } + pub fn remove_artist(&self, id: &ArtistId) -> bool { + self.changed(); + self.0.lock().unwrap().0.remove(id) + } + pub fn remove_album(&self, id: &AlbumId) -> bool { + self.changed(); + self.0.lock().unwrap().1.remove(id) + } + pub fn remove_song(&self, id: &SongId) -> bool { + self.changed(); + self.0.lock().unwrap().2.remove(id) + } + pub fn view( + &self, + f: impl FnOnce(&(HashSet, HashSet, HashSet)) -> T, + ) -> T { + f(&self.0.lock().unwrap()) + } + pub fn view_mut( + &self, + f: impl FnOnce(&mut (HashSet, HashSet, HashSet)) -> T, + ) -> T { + let v = f(&mut self.0.lock().unwrap()); + self.changed(); + v + } + fn changed(&self) { + self.1.store(true, std::sync::atomic::Ordering::Relaxed); + } + pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec { + let lock = self.0.lock().unwrap(); + let (sel_artists, sel_albums, sel_songs) = &*lock; + let mut out = vec![]; + for (artist, singles, albums, _) in &lb.library_filtered { + let artist_selected = sel_artists.contains(artist); + let mut local_artist_owned = vec![]; + let mut local_artist = if artist_selected { + &mut local_artist_owned } else { - &mut local_artist + &mut out }; - for (song, _) in songs { + for (song, _) in singles { let song_selected = sel_songs.contains(song); if song_selected { - local_album.push(QueueContent::Song(*song).into()); + local_artist.push(QueueContent::Song(*song).into()); } } - if album_selected { - local_artist.push( + for (album, songs, _) in albums { + let album_selected = sel_albums.contains(album); + let mut local_album_owned = vec![]; + let local_album = if album_selected { + &mut local_album_owned + } else { + &mut local_artist + }; + for (song, _) in songs { + let song_selected = sel_songs.contains(song); + if song_selected { + local_album.push(QueueContent::Song(*song).into()); + } + } + if album_selected { + local_artist.push( + QueueContent::Folder( + 0, + local_album_owned, + match db.albums().get(album) { + Some(v) => v.name.clone(), + None => "< unknown album >".to_owned(), + }, + ) + .into(), + ); + } + } + if artist_selected { + out.push( QueueContent::Folder( 0, - local_album_owned, - match db.albums().get(album) { - Some(v) => v.name.clone(), - None => "< unknown album >".to_owned(), + local_artist_owned, + match db.artists().get(artist) { + Some(v) => v.name.to_owned(), + None => "< unknown artist >".to_owned(), }, ) .into(), ); } } - if artist_selected { - out.push( - QueueContent::Folder( - 0, - local_artist_owned, - match db.artists().get(artist) { - Some(v) => v.name.to_owned(), - None => "< unknown artist >".to_owned(), - }, - ) - .into(), - ); - } + out } - out } } fn search_regex_new(pat: &str, case_insensitive: bool) -> Result, regex::Error> { @@ -221,13 +293,9 @@ impl LibraryBrowser { and: true, filters: vec![], })); - let selected = Selected(Arc::new(Mutex::new(( - HashSet::new(), - HashSet::new(), - HashSet::new(), - )))); + let selected = Selected::new(Arc::clone(&search_settings_changed)); Self { - config, + config: config.w_keyboard_watch(), children: vec![ GuiElem::new(search_artist), GuiElem::new(search_album), @@ -270,6 +338,48 @@ impl LibraryBrowser { do_something_receiver, } } + pub fn selected_add_all(&self) { + self.selected.view_mut(|sel| { + for (id, singles, albums, _) in &self.library_filtered { + sel.0.insert(*id); + for (s, _) in singles { + sel.2.insert(*s); + } + for (id, album, _) in albums { + sel.1.insert(*id); + for (s, _) in album { + sel.2.insert(*s); + } + } + } + }) + } + pub fn selected_add_songs(&self) { + self.selected.view_mut(|sel| { + for (_, singles, albums, _) in &self.library_filtered { + for (s, _) in singles { + sel.2.insert(*s); + } + for (_, album, _) in albums { + for (s, _) in album { + sel.2.insert(*s); + } + } + } + }) + } + pub fn selected_add_albums(&self) { + self.selected.view_mut(|sel| { + for (_, _, albums, _) in &self.library_filtered { + for (id, album, _) in albums { + sel.1.insert(*id); + for (s, _) in album { + sel.2.insert(*s); + } + } + } + }) + } } impl GuiElemTrait for LibraryBrowser { fn config(&self) -> &GuiElemCfg { @@ -521,6 +631,27 @@ impl GuiElemTrait for LibraryBrowser { fn updated_library(&mut self) { self.library_updated = true; } + fn key_watch( + &mut self, + modifiers: speedy2d::window::ModifiersState, + down: bool, + key: Option, + scan: speedy2d::window::KeyScancode, + ) -> Vec { + if down && crate::gui::hotkey_deselect_all(&modifiers, key) { + self.selected.clear(); + } + if down && crate::gui::hotkey_select_all(&modifiers, key) { + self.selected_add_all(); + } + if down && crate::gui::hotkey_select_albums(&modifiers, key) { + self.selected_add_albums(); + } + if down && crate::gui::hotkey_select_songs(&modifiers, key) { + self.selected_add_songs(); + } + vec![] + } } impl LibraryBrowser { /// Sets `self.library_sorted` based on the contents of the `Database`. @@ -757,7 +888,7 @@ impl GuiElemTrait for ListArtist { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { if self.config.redraw { self.config.redraw = false; - let sel = self.selected.0.lock().unwrap().0.contains(&self.id); + let sel = self.selected.contains_artist(&self.id); if sel != self.sel { self.sel = sel; if sel { @@ -783,7 +914,7 @@ impl GuiElemTrait for ListArtist { gui.gui .inner .children() - .nth(2) + .nth(3) .unwrap() .inner .children() @@ -838,9 +969,9 @@ impl GuiElemTrait for ListArtist { self.mouse = false; self.config.redraw = true; if !self.sel { - self.selected.0.lock().unwrap().0.insert(self.id); + self.selected.insert_artist(self.id); } else { - self.selected.0.lock().unwrap().0.remove(&self.id); + self.selected.remove_artist(&self.id); } } vec![] @@ -900,7 +1031,7 @@ impl GuiElemTrait for ListAlbum { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { if self.config.redraw { self.config.redraw = false; - let sel = self.selected.0.lock().unwrap().1.contains(&self.id); + let sel = self.selected.contains_album(&self.id); if sel != self.sel { self.sel = sel; if sel { @@ -926,7 +1057,7 @@ impl GuiElemTrait for ListAlbum { gui.gui .inner .children() - .nth(2) + .nth(3) .unwrap() .inner .children() @@ -981,9 +1112,9 @@ impl GuiElemTrait for ListAlbum { self.mouse = false; self.config.redraw = true; if !self.sel { - self.selected.0.lock().unwrap().1.insert(self.id); + self.selected.insert_album(self.id); } else { - self.selected.0.lock().unwrap().1.remove(&self.id); + self.selected.remove_album(&self.id); } } vec![] @@ -1043,7 +1174,7 @@ impl GuiElemTrait for ListSong { fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { if self.config.redraw { self.config.redraw = false; - let sel = self.selected.0.lock().unwrap().2.contains(&self.id); + let sel = self.selected.contains_song(&self.id); if sel != self.sel { self.sel = sel; if sel { @@ -1069,7 +1200,7 @@ impl GuiElemTrait for ListSong { gui.gui .inner .children() - .nth(2) + .nth(3) .unwrap() .inner .children() @@ -1124,9 +1255,9 @@ impl GuiElemTrait for ListSong { self.mouse = false; self.config.redraw = true; if !self.sel { - self.selected.0.lock().unwrap().2.insert(self.id); + self.selected.insert_song(self.id); } else { - self.selected.0.lock().unwrap().2.remove(&self.id); + self.selected.remove_song(&self.id); } } vec![] @@ -1165,7 +1296,6 @@ impl FilterPanel { search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed); let ssc1 = Arc::clone(&search_settings_changed); let ssc2 = Arc::clone(&search_settings_changed); - let ssc3 = Arc::clone(&search_settings_changed); let sel3 = selected.clone(); const VSPLIT: f32 = 0.4; let tab_main = GuiElem::new(ScrollBox::new( @@ -1249,11 +1379,7 @@ impl FilterPanel { GuiElem::new(Button::new( GuiElemCfg::default(), move |_| { - ssc3.store(true, std::sync::atomic::Ordering::Relaxed); - let mut sel = sel3.0.lock().unwrap(); - sel.2 = HashSet::new(); - sel.1 = HashSet::new(); - sel.0 = HashSet::new(); + let mut sel = sel3.clear(); vec![] }, vec![GuiElem::new(Label::new( @@ -1275,24 +1401,7 @@ impl FilterPanel { { let dss = do_something_sender.clone(); move |_| { - dss.send(Box::new(|s| { - s.search_settings_changed - .store(true, std::sync::atomic::Ordering::Relaxed); - let mut sel = s.selected.0.lock().unwrap(); - for (id, singles, albums, _) in &s.library_filtered { - sel.0.insert(*id); - for (s, _) in singles { - sel.2.insert(*s); - } - for (id, album, _) in albums { - sel.1.insert(*id); - for (s, _) in album { - sel.2.insert(*s); - } - } - } - })) - .unwrap(); + dss.send(Box::new(|s| s.selected_add_all())).unwrap(); vec![] } }, @@ -1309,22 +1418,7 @@ impl FilterPanel { { let dss = do_something_sender.clone(); move |_| { - dss.send(Box::new(|s| { - s.search_settings_changed - .store(true, std::sync::atomic::Ordering::Relaxed); - let mut sel = s.selected.0.lock().unwrap(); - for (_, singles, albums, _) in &s.library_filtered { - for (s, _) in singles { - sel.2.insert(*s); - } - for (_, album, _) in albums { - for (s, _) in album { - sel.2.insert(*s); - } - } - } - })) - .unwrap(); + dss.send(Box::new(|s| s.selected_add_songs())).unwrap(); vec![] } }, @@ -1341,20 +1435,7 @@ impl FilterPanel { { let dss = do_something_sender.clone(); move |_| { - dss.send(Box::new(|s| { - s.search_settings_changed - .store(true, std::sync::atomic::Ordering::Relaxed); - let mut sel = s.selected.0.lock().unwrap(); - for (_, _, albums, _) in &s.library_filtered { - for (id, album, _) in albums { - sel.1.insert(*id); - for (s, _) in album { - sel.2.insert(*s); - } - } - } - })) - .unwrap(); + dss.send(Box::new(|s| s.selected_add_albums())).unwrap(); vec![] } }, diff --git a/musicdb-client/src/gui_notif.rs b/musicdb-client/src/gui_notif.rs index e6aed3c..f8f62cd 100644 --- a/musicdb-client/src/gui_notif.rs +++ b/musicdb-client/src/gui_notif.rs @@ -185,7 +185,7 @@ impl GuiElemTrait for NotifOverlay { } } fn draw_rev(&self) -> bool { - true + false } fn config(&self) -> &GuiElemCfg { diff --git a/musicdb-client/src/gui_playback.rs b/musicdb-client/src/gui_playback.rs index bfb290c..cb44996 100755 --- a/musicdb-client/src/gui_playback.rs +++ b/musicdb-client/src/gui_playback.rs @@ -23,6 +23,9 @@ This file could probably have a better name. #[derive(Clone)] pub struct CurrentSong { config: GuiElemCfg, + /// 0: AdvancedLabel for small mode + /// 1: AdvancedLabel for big mode heading + /// 2: AdvancedLabel for big mode info text children: Vec, prev_song: Option, cover_pos: Rectangle, diff --git a/musicdb-client/src/gui_screen.rs b/musicdb-client/src/gui_screen.rs index 0c273f3..b2c39e0 100755 --- a/musicdb-client/src/gui_screen.rs +++ b/musicdb-client/src/gui_screen.rs @@ -341,6 +341,11 @@ impl GuiElemTrait for GuiScreen { Command::Resume })] }))] + } else if down && matches!(key, Some(VirtualKeyCode::F8)) { + vec![GuiAction::SendToServer(Command::ErrorInfo( + "".to_owned(), + "tEsT".to_owned(), + ))] } else { vec![] }