use std::{ cmp::Ordering, rc::Rc, sync::{atomic::AtomicBool, Arc}, }; use musicdb_lib::data::{ album::Album, artist::Artist, database::Database, song::Song, AlbumId, ArtistId, SongId, }; use regex::{Regex, RegexBuilder}; use speedy2d::{ color::Color, dimen::Vec2, shape::Rectangle, window::{MouseButton, VirtualKeyCode}, }; use crate::{ gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}, gui_base::{Button, Panel, ScrollBox}, gui_edit::GuiEdit, gui_text::{Label, TextField}, gui_wrappers::WithFocusHotkey, }; /* This is responsible for showing the library, with Regex search and drag-n-drop. */ #[derive(Clone)] pub struct LibraryBrowser { config: GuiElemCfg, pub children: Vec, // - - - library_sorted: Vec<(ArtistId, Vec, Vec<(AlbumId, Vec)>)>, library_filtered: Vec<( ArtistId, Vec<(SongId, f32)>, Vec<(AlbumId, Vec<(SongId, f32)>, f32)>, f32, )>, // - - - search_artist: String, search_artist_regex: Option, search_album: String, search_album_regex: Option, search_song: String, search_song_regex: Option, filter_target_state: Rc, filter_state: f32, library_updated: bool, search_settings_changed: Rc, search_is_case_sensitive: Rc, search_was_case_sensitive: bool, search_prefer_start_matches: Rc, search_prefers_start_matches: bool, } fn search_regex_new(pat: &str, case_insensitive: bool) -> Result, regex::Error> { if pat.is_empty() { Ok(None) } else { Ok(Some( RegexBuilder::new(pat) .unicode(true) .case_insensitive(case_insensitive) .build()?, )) } } const LP_LIB1: f32 = 0.1; const LP_LIB2: f32 = 1.0; const LP_LIB1S: f32 = 0.4; impl LibraryBrowser { pub fn new(config: GuiElemCfg) -> Self { let search_artist = TextField::new( GuiElemCfg::at(Rectangle::from_tuples((0.01, 0.01), (0.45, 0.05))), "artist".to_string(), Color::GRAY, Color::WHITE, ); let search_album = TextField::new( GuiElemCfg::at(Rectangle::from_tuples((0.55, 0.01), (0.99, 0.05))), "album".to_string(), Color::GRAY, Color::WHITE, ); let search_song = WithFocusHotkey::new_ctrl( VirtualKeyCode::F, TextField::new( GuiElemCfg::at(Rectangle::from_tuples((0.01, 0.06), (0.99, 0.1))), "song".to_string(), Color::GRAY, Color::WHITE, ), ); let library_scroll_box = ScrollBox::new( GuiElemCfg::at(Rectangle::from_tuples((0.0, LP_LIB1), (1.0, LP_LIB2))), crate::gui_base::ScrollBoxSizeUnit::Pixels, vec![], ); let search_settings_changed = Rc::new(AtomicBool::new(false)); let search_was_case_sensitive = false; let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive)); let search_prefers_start_matches = true; let search_prefer_start_matches = Rc::new(AtomicBool::new(search_prefers_start_matches)); let filter_target_state = Rc::new(AtomicBool::new(false)); let fts = Rc::clone(&filter_target_state); let filter_button = Button::new( GuiElemCfg::at(Rectangle::from_tuples((0.46, 0.01), (0.54, 0.05))), move |_| { fts.store( !fts.load(std::sync::atomic::Ordering::Relaxed), std::sync::atomic::Ordering::Relaxed, ); vec![] }, vec![GuiElem::new(Label::new( GuiElemCfg::default(), "more".to_owned(), Color::GRAY, None, Vec2::new(0.5, 0.5), ))], ); Self { config, children: vec![ GuiElem::new(search_artist), GuiElem::new(search_album), GuiElem::new(search_song), GuiElem::new(library_scroll_box), GuiElem::new(filter_button), GuiElem::new(FilterPanel::new( Rc::clone(&search_settings_changed), Rc::clone(&search_is_case_sensitive), Rc::clone(&search_prefer_start_matches), )), ], // - - - library_sorted: vec![], library_filtered: vec![], // - - - search_artist: String::new(), search_artist_regex: None, search_album: String::new(), search_album_regex: None, search_song: String::new(), search_song_regex: None, filter_target_state, filter_state: 0.0, library_updated: true, search_settings_changed, search_is_case_sensitive, search_was_case_sensitive, search_prefer_start_matches, search_prefers_start_matches, } } } impl GuiElemTrait for LibraryBrowser { fn config(&self) -> &GuiElemCfg { &self.config } fn config_mut(&mut self) -> &mut GuiElemCfg { &mut self.config } fn children(&mut self) -> Box + '_> { Box::new(self.children.iter_mut()) } fn any(&self) -> &dyn std::any::Any { self } fn any_mut(&mut self) -> &mut dyn std::any::Any { self } fn clone_gui(&self) -> Box { Box::new(self.clone()) } fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) { // search let mut search_changed = false; let mut rebuild_regex = false; if self .search_settings_changed .load(std::sync::atomic::Ordering::Relaxed) { search_changed = true; self.search_settings_changed .store(false, std::sync::atomic::Ordering::Relaxed); let case_sensitive = self .search_is_case_sensitive .load(std::sync::atomic::Ordering::Relaxed); if self.search_was_case_sensitive != case_sensitive { self.search_was_case_sensitive = case_sensitive; rebuild_regex = true; } let pref_start = self .search_prefer_start_matches .load(std::sync::atomic::Ordering::Relaxed); if self.search_prefers_start_matches != pref_start { self.search_prefers_start_matches = pref_start; } } { let v = &mut self.children[0].try_as_mut::().unwrap().children[0] .try_as_mut::