diff --git a/musicdb-client/src/gui_library.rs b/musicdb-client/src/gui_library.rs index 4ba6631..b83d8d0 100755 --- a/musicdb-client/src/gui_library.rs +++ b/musicdb-client/src/gui_library.rs @@ -1,11 +1,15 @@ use std::{ cmp::Ordering, rc::Rc, - sync::{atomic::AtomicBool, Arc}, + sync::{ + atomic::{AtomicBool, AtomicUsize}, + Arc, Mutex, + }, }; use musicdb_lib::data::{ - album::Album, artist::Artist, database::Database, song::Song, AlbumId, ArtistId, SongId, + album::Album, artist::Artist, database::Database, song::Song, AlbumId, ArtistId, GeneralData, + SongId, }; use regex::{Regex, RegexBuilder}; use speedy2d::{ @@ -17,7 +21,7 @@ use speedy2d::{ use crate::{ gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}, - gui_base::{Button, Panel, ScrollBox}, + gui_base::{Button, Panel, ScrollBox, Slider}, gui_edit::GuiEdit, gui_text::{Label, TextField}, gui_wrappers::WithFocusHotkey, @@ -57,6 +61,9 @@ pub struct LibraryBrowser { search_was_case_sensitive: bool, search_prefer_start_matches: Rc, search_prefers_start_matches: bool, + filter_songs: Rc>, + filter_albums: Rc>, + filter_artists: Rc>, } fn search_regex_new(pat: &str, case_insensitive: bool) -> Result, regex::Error> { if pat.is_empty() { @@ -125,6 +132,18 @@ impl LibraryBrowser { Vec2::new(0.5, 0.5), ))], ); + let filter_songs = Rc::new(Mutex::new(Filter { + and: true, + filters: vec![], + })); + let filter_albums = Rc::new(Mutex::new(Filter { + and: true, + filters: vec![], + })); + let filter_artists = Rc::new(Mutex::new(Filter { + and: true, + filters: vec![], + })); Self { config, children: vec![ @@ -137,6 +156,9 @@ impl LibraryBrowser { Rc::clone(&search_settings_changed), Rc::clone(&search_is_case_sensitive), Rc::clone(&search_prefer_start_matches), + Rc::clone(&filter_songs), + Rc::clone(&filter_albums), + Rc::clone(&filter_artists), )), ], // - - - @@ -157,6 +179,9 @@ impl LibraryBrowser { search_was_case_sensitive, search_prefer_start_matches, search_prefers_start_matches, + filter_songs, + filter_albums, + filter_artists, } } } @@ -314,7 +339,12 @@ impl GuiElemTrait for LibraryBrowser { pat: &str, regex: &Option, search_text: &String, + filter: &Filter, + search_gd: &GeneralData, ) -> f32 { + if !filter.passes(search_gd) { + return 0.0; + }; if let Some(r) = regex { if s.search_prefers_start_matches { r.find_iter(pat) @@ -323,13 +353,15 @@ impl GuiElemTrait for LibraryBrowser { None if m.end() == pat.len() => 6.0, // found at start of h None => 4.0, - Some(ch) if ch.is_whitespace() => match pat[m.end()..].chars().next() { - // whole word matches - None => 5.0, - Some(ch) if ch.is_whitespace() => 5.0, - // found after whitespace in h - Some(_) => 3.0, - }, + Some(ch) if ch.is_whitespace() => { + match pat[m.end()..].chars().next() { + // whole word matches + None => 5.0, + Some(ch) if ch.is_whitespace() => 5.0, + // found after whitespace in h + Some(_) => 3.0, + } + } // found somewhere else in h _ => 2.0, }) @@ -347,13 +379,40 @@ impl GuiElemTrait for LibraryBrowser { 0.0 } } + let allow_singles = self.search_album.is_empty() + && self.filter_albums.lock().unwrap().filters.is_empty(); self.filter_local_library( &info.database, - |s, artist| filter(s, &artist.name, &s.search_artist_regex, &s.search_artist), - |s, album| filter(s, &album.name, &s.search_album_regex, &s.search_album), + |s, artist| { + filter( + s, + &artist.name, + &s.search_artist_regex, + &s.search_artist, + &s.filter_artists.lock().unwrap(), + &artist.general, + ) + }, + |s, album| { + filter( + s, + &album.name, + &s.search_album_regex, + &s.search_album, + &s.filter_albums.lock().unwrap(), + &album.general, + ) + }, |s, song| { - if song.album.is_some() || s.search_album.is_empty() { - filter(s, &song.title, &s.search_song_regex, &s.search_song) + if song.album.is_some() || allow_singles { + filter( + s, + &song.title, + &s.search_song_regex, + &s.search_song, + &s.filter_songs.lock().unwrap(), + &song.general, + ) } else { 0.0 } @@ -835,7 +894,13 @@ impl GuiElemTrait for ListSong { struct FilterPanel { config: GuiElemCfg, children: Vec, + search_settings_changed: Rc, + tab: usize, + new_tab: Rc, line_height: f32, + filter_songs: Rc>, + filter_albums: Rc>, + filter_artists: Rc>, } const FP_CASESENS_N: &'static str = "search is case-insensitive"; const FP_CASESENS_Y: &'static str = "search is case-sensitive!"; @@ -846,106 +911,568 @@ impl FilterPanel { search_settings_changed: Rc, search_is_case_sensitive: Rc, search_prefer_start_matches: Rc, + filter_songs: Rc>, + filter_albums: Rc>, + filter_artists: Rc>, ) -> Self { let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed); let prefer_start_matches = search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed); let ssc1 = Rc::clone(&search_settings_changed); - let ssc2 = search_settings_changed; + let ssc2 = Rc::clone(&search_settings_changed); + let tab_settings = GuiElem::new(ScrollBox::new( + GuiElemCfg::default(), + crate::gui_base::ScrollBoxSizeUnit::Pixels, + vec![ + ( + GuiElem::new(Button::new( + GuiElemCfg::default(), + move |button| { + let v = !search_is_case_sensitive + .load(std::sync::atomic::Ordering::Relaxed); + search_is_case_sensitive.store(v, std::sync::atomic::Ordering::Relaxed); + ssc1.store(true, std::sync::atomic::Ordering::Relaxed); + *button + .children() + .next() + .unwrap() + .try_as_mut::