mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-11-04 05:16:17 +01:00 
			
		
		
		
	server can now send error messages to clients
This commit is contained in:
		
							parent
							
								
									94df757f0c
								
							
						
					
					
						commit
						1eee22bb4b
					
				@ -6,7 +6,7 @@ use std::{
 | 
				
			|||||||
    net::TcpStream,
 | 
					    net::TcpStream,
 | 
				
			||||||
    sync::{Arc, Mutex},
 | 
					    sync::{Arc, Mutex},
 | 
				
			||||||
    thread::JoinHandle,
 | 
					    thread::JoinHandle,
 | 
				
			||||||
    time::Instant,
 | 
					    time::{Duration, Instant},
 | 
				
			||||||
    usize,
 | 
					    usize,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -28,7 +28,14 @@ use speedy2d::{
 | 
				
			|||||||
    Graphics2D,
 | 
					    Graphics2D,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{gui_screen::GuiScreen, gui_wrappers::WithFocusHotkey, textcfg};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    gui_base::Panel,
 | 
				
			||||||
 | 
					    gui_notif::{NotifInfo, NotifOverlay},
 | 
				
			||||||
 | 
					    gui_screen::GuiScreen,
 | 
				
			||||||
 | 
					    gui_text::Label,
 | 
				
			||||||
 | 
					    gui_wrappers::WithFocusHotkey,
 | 
				
			||||||
 | 
					    textcfg,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum GuiEvent {
 | 
					pub enum GuiEvent {
 | 
				
			||||||
    Refresh,
 | 
					    Refresh,
 | 
				
			||||||
@ -192,6 +199,7 @@ impl Gui {
 | 
				
			|||||||
        scroll_pages_multiplier: f64,
 | 
					        scroll_pages_multiplier: f64,
 | 
				
			||||||
        gui_config: GuiConfig,
 | 
					        gui_config: GuiConfig,
 | 
				
			||||||
    ) -> Self {
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        let (notif_overlay, notif_sender) = NotifOverlay::new();
 | 
				
			||||||
        database.lock().unwrap().update_endpoints.push(
 | 
					        database.lock().unwrap().update_endpoints.push(
 | 
				
			||||||
            musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
 | 
					            musicdb_lib::data::database::UpdateEndpoint::Custom(Box::new(move |cmd| match cmd {
 | 
				
			||||||
                Command::Resume
 | 
					                Command::Resume
 | 
				
			||||||
@ -225,6 +233,37 @@ impl Gui {
 | 
				
			|||||||
                        _ = s.send_event(GuiEvent::UpdatedLibrary);
 | 
					                        _ = s.send_event(GuiEvent::UpdatedLibrary);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                Command::ErrorInfo(t, d) => {
 | 
				
			||||||
 | 
					                    eprintln!("{t:?} | {d:?}");
 | 
				
			||||||
 | 
					                    let (t, d) = (t.clone(), d.clone());
 | 
				
			||||||
 | 
					                    notif_sender
 | 
				
			||||||
 | 
					                        .send(Box::new(move |_| {
 | 
				
			||||||
 | 
					                            (
 | 
				
			||||||
 | 
					                                GuiElem::new(Panel::with_background(
 | 
				
			||||||
 | 
					                                    GuiElemCfg::default(),
 | 
				
			||||||
 | 
					                                    vec![GuiElem::new(Label::new(
 | 
				
			||||||
 | 
					                                        GuiElemCfg::default(),
 | 
				
			||||||
 | 
					                                        if t.is_empty() {
 | 
				
			||||||
 | 
					                                            format!("Server message\n{d}")
 | 
				
			||||||
 | 
					                                        } else {
 | 
				
			||||||
 | 
					                                            format!("Server error ({t})\n{d}")
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                        Color::WHITE,
 | 
				
			||||||
 | 
					                                        None,
 | 
				
			||||||
 | 
					                                        Vec2::new(0.5, 0.5),
 | 
				
			||||||
 | 
					                                    ))],
 | 
				
			||||||
 | 
					                                    Color::from_rgba(0.0, 0.0, 0.0, 0.8),
 | 
				
			||||||
 | 
					                                )),
 | 
				
			||||||
 | 
					                                if t.is_empty() {
 | 
				
			||||||
 | 
					                                    NotifInfo::new(Duration::from_secs(2))
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    NotifInfo::new(Duration::from_secs(5))
 | 
				
			||||||
 | 
					                                        .with_highlight(Color::RED)
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        }))
 | 
				
			||||||
 | 
					                        .unwrap();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            })),
 | 
					            })),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        Gui {
 | 
					        Gui {
 | 
				
			||||||
@ -236,6 +275,7 @@ impl Gui {
 | 
				
			|||||||
                VirtualKeyCode::Escape,
 | 
					                VirtualKeyCode::Escape,
 | 
				
			||||||
                GuiScreen::new(
 | 
					                GuiScreen::new(
 | 
				
			||||||
                    GuiElemCfg::default(),
 | 
					                    GuiElemCfg::default(),
 | 
				
			||||||
 | 
					                    notif_overlay,
 | 
				
			||||||
                    line_height,
 | 
					                    line_height,
 | 
				
			||||||
                    scroll_pixels_multiplier,
 | 
					                    scroll_pixels_multiplier,
 | 
				
			||||||
                    scroll_lines_multiplier,
 | 
					                    scroll_lines_multiplier,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    cmp::Ordering,
 | 
					    cmp::Ordering,
 | 
				
			||||||
    collections::HashSet,
 | 
					    collections::HashSet,
 | 
				
			||||||
    rc::Rc,
 | 
					    sync::Arc,
 | 
				
			||||||
    sync::{
 | 
					    sync::{
 | 
				
			||||||
        atomic::{AtomicBool, AtomicUsize},
 | 
					        atomic::{AtomicBool, AtomicUsize},
 | 
				
			||||||
        mpsc, Mutex,
 | 
					        mpsc, Mutex,
 | 
				
			||||||
@ -57,17 +57,17 @@ pub struct LibraryBrowser {
 | 
				
			|||||||
    search_album_regex: Option<Regex>,
 | 
					    search_album_regex: Option<Regex>,
 | 
				
			||||||
    search_song: String,
 | 
					    search_song: String,
 | 
				
			||||||
    search_song_regex: Option<Regex>,
 | 
					    search_song_regex: Option<Regex>,
 | 
				
			||||||
    filter_target_state: Rc<AtomicBool>,
 | 
					    filter_target_state: Arc<AtomicBool>,
 | 
				
			||||||
    filter_state: f32,
 | 
					    filter_state: f32,
 | 
				
			||||||
    library_updated: bool,
 | 
					    library_updated: bool,
 | 
				
			||||||
    search_settings_changed: Rc<AtomicBool>,
 | 
					    search_settings_changed: Arc<AtomicBool>,
 | 
				
			||||||
    search_is_case_sensitive: Rc<AtomicBool>,
 | 
					    search_is_case_sensitive: Arc<AtomicBool>,
 | 
				
			||||||
    search_was_case_sensitive: bool,
 | 
					    search_was_case_sensitive: bool,
 | 
				
			||||||
    search_prefer_start_matches: Rc<AtomicBool>,
 | 
					    search_prefer_start_matches: Arc<AtomicBool>,
 | 
				
			||||||
    search_prefers_start_matches: bool,
 | 
					    search_prefers_start_matches: bool,
 | 
				
			||||||
    filter_songs: Rc<Mutex<Filter>>,
 | 
					    filter_songs: Arc<Mutex<Filter>>,
 | 
				
			||||||
    filter_albums: Rc<Mutex<Filter>>,
 | 
					    filter_albums: Arc<Mutex<Filter>>,
 | 
				
			||||||
    filter_artists: Rc<Mutex<Filter>>,
 | 
					    filter_artists: Arc<Mutex<Filter>>,
 | 
				
			||||||
    do_something_receiver: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
 | 
					    do_something_receiver: mpsc::Receiver<Box<dyn FnOnce(&mut Self)>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl Clone for LibraryBrowser {
 | 
					impl Clone for LibraryBrowser {
 | 
				
			||||||
@ -76,7 +76,7 @@ impl Clone for LibraryBrowser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
struct Selected(Rc<Mutex<(HashSet<ArtistId>, HashSet<AlbumId>, HashSet<SongId>)>>);
 | 
					struct Selected(Arc<Mutex<(HashSet<ArtistId>, HashSet<AlbumId>, HashSet<SongId>)>>);
 | 
				
			||||||
impl Selected {
 | 
					impl Selected {
 | 
				
			||||||
    pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec<Queue> {
 | 
					    pub fn as_queue(&self, lb: &LibraryBrowser, db: &Database) -> Vec<Queue> {
 | 
				
			||||||
        let lock = self.0.lock().unwrap();
 | 
					        let lock = self.0.lock().unwrap();
 | 
				
			||||||
@ -185,13 +185,13 @@ impl LibraryBrowser {
 | 
				
			|||||||
            vec![],
 | 
					            vec![],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        let (do_something_sender, do_something_receiver) = mpsc::channel();
 | 
					        let (do_something_sender, do_something_receiver) = mpsc::channel();
 | 
				
			||||||
        let search_settings_changed = Rc::new(AtomicBool::new(false));
 | 
					        let search_settings_changed = Arc::new(AtomicBool::new(false));
 | 
				
			||||||
        let search_was_case_sensitive = false;
 | 
					        let search_was_case_sensitive = false;
 | 
				
			||||||
        let search_is_case_sensitive = Rc::new(AtomicBool::new(search_was_case_sensitive));
 | 
					        let search_is_case_sensitive = Arc::new(AtomicBool::new(search_was_case_sensitive));
 | 
				
			||||||
        let search_prefers_start_matches = true;
 | 
					        let search_prefers_start_matches = true;
 | 
				
			||||||
        let search_prefer_start_matches = Rc::new(AtomicBool::new(search_prefers_start_matches));
 | 
					        let search_prefer_start_matches = Arc::new(AtomicBool::new(search_prefers_start_matches));
 | 
				
			||||||
        let filter_target_state = Rc::new(AtomicBool::new(false));
 | 
					        let filter_target_state = Arc::new(AtomicBool::new(false));
 | 
				
			||||||
        let fts = Rc::clone(&filter_target_state);
 | 
					        let fts = Arc::clone(&filter_target_state);
 | 
				
			||||||
        let filter_button = Button::new(
 | 
					        let filter_button = Button::new(
 | 
				
			||||||
            GuiElemCfg::at(Rectangle::from_tuples((0.46, 0.01), (0.54, 0.05))),
 | 
					            GuiElemCfg::at(Rectangle::from_tuples((0.46, 0.01), (0.54, 0.05))),
 | 
				
			||||||
            move |_| {
 | 
					            move |_| {
 | 
				
			||||||
@ -209,19 +209,19 @@ impl LibraryBrowser {
 | 
				
			|||||||
                Vec2::new(0.5, 0.5),
 | 
					                Vec2::new(0.5, 0.5),
 | 
				
			||||||
            ))],
 | 
					            ))],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        let filter_songs = Rc::new(Mutex::new(Filter {
 | 
					        let filter_songs = Arc::new(Mutex::new(Filter {
 | 
				
			||||||
            and: true,
 | 
					            and: true,
 | 
				
			||||||
            filters: vec![],
 | 
					            filters: vec![],
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
        let filter_albums = Rc::new(Mutex::new(Filter {
 | 
					        let filter_albums = Arc::new(Mutex::new(Filter {
 | 
				
			||||||
            and: true,
 | 
					            and: true,
 | 
				
			||||||
            filters: vec![],
 | 
					            filters: vec![],
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
        let filter_artists = Rc::new(Mutex::new(Filter {
 | 
					        let filter_artists = Arc::new(Mutex::new(Filter {
 | 
				
			||||||
            and: true,
 | 
					            and: true,
 | 
				
			||||||
            filters: vec![],
 | 
					            filters: vec![],
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
        let selected = Selected(Rc::new(Mutex::new((
 | 
					        let selected = Selected(Arc::new(Mutex::new((
 | 
				
			||||||
            HashSet::new(),
 | 
					            HashSet::new(),
 | 
				
			||||||
            HashSet::new(),
 | 
					            HashSet::new(),
 | 
				
			||||||
            HashSet::new(),
 | 
					            HashSet::new(),
 | 
				
			||||||
@ -235,12 +235,12 @@ impl LibraryBrowser {
 | 
				
			|||||||
                GuiElem::new(library_scroll_box),
 | 
					                GuiElem::new(library_scroll_box),
 | 
				
			||||||
                GuiElem::new(filter_button),
 | 
					                GuiElem::new(filter_button),
 | 
				
			||||||
                GuiElem::new(FilterPanel::new(
 | 
					                GuiElem::new(FilterPanel::new(
 | 
				
			||||||
                    Rc::clone(&search_settings_changed),
 | 
					                    Arc::clone(&search_settings_changed),
 | 
				
			||||||
                    Rc::clone(&search_is_case_sensitive),
 | 
					                    Arc::clone(&search_is_case_sensitive),
 | 
				
			||||||
                    Rc::clone(&search_prefer_start_matches),
 | 
					                    Arc::clone(&search_prefer_start_matches),
 | 
				
			||||||
                    Rc::clone(&filter_songs),
 | 
					                    Arc::clone(&filter_songs),
 | 
				
			||||||
                    Rc::clone(&filter_albums),
 | 
					                    Arc::clone(&filter_albums),
 | 
				
			||||||
                    Rc::clone(&filter_artists),
 | 
					                    Arc::clone(&filter_artists),
 | 
				
			||||||
                    selected.clone(),
 | 
					                    selected.clone(),
 | 
				
			||||||
                    do_something_sender.clone(),
 | 
					                    do_something_sender.clone(),
 | 
				
			||||||
                )),
 | 
					                )),
 | 
				
			||||||
@ -1137,13 +1137,13 @@ impl GuiElemTrait for ListSong {
 | 
				
			|||||||
struct FilterPanel {
 | 
					struct FilterPanel {
 | 
				
			||||||
    config: GuiElemCfg,
 | 
					    config: GuiElemCfg,
 | 
				
			||||||
    children: Vec<GuiElem>,
 | 
					    children: Vec<GuiElem>,
 | 
				
			||||||
    search_settings_changed: Rc<AtomicBool>,
 | 
					    search_settings_changed: Arc<AtomicBool>,
 | 
				
			||||||
    tab: usize,
 | 
					    tab: usize,
 | 
				
			||||||
    new_tab: Rc<AtomicUsize>,
 | 
					    new_tab: Arc<AtomicUsize>,
 | 
				
			||||||
    line_height: f32,
 | 
					    line_height: f32,
 | 
				
			||||||
    filter_songs: Rc<Mutex<Filter>>,
 | 
					    filter_songs: Arc<Mutex<Filter>>,
 | 
				
			||||||
    filter_albums: Rc<Mutex<Filter>>,
 | 
					    filter_albums: Arc<Mutex<Filter>>,
 | 
				
			||||||
    filter_artists: Rc<Mutex<Filter>>,
 | 
					    filter_artists: Arc<Mutex<Filter>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const FP_CASESENS_N: &'static str = "search is case-insensitive";
 | 
					const FP_CASESENS_N: &'static str = "search is case-insensitive";
 | 
				
			||||||
const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
 | 
					const FP_CASESENS_Y: &'static str = "search is case-sensitive!";
 | 
				
			||||||
@ -1151,21 +1151,21 @@ const FP_PREFSTART_N: &'static str = "simple search";
 | 
				
			|||||||
const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
 | 
					const FP_PREFSTART_Y: &'static str = "will prefer matches at the start of a word";
 | 
				
			||||||
impl FilterPanel {
 | 
					impl FilterPanel {
 | 
				
			||||||
    pub fn new(
 | 
					    pub fn new(
 | 
				
			||||||
        search_settings_changed: Rc<AtomicBool>,
 | 
					        search_settings_changed: Arc<AtomicBool>,
 | 
				
			||||||
        search_is_case_sensitive: Rc<AtomicBool>,
 | 
					        search_is_case_sensitive: Arc<AtomicBool>,
 | 
				
			||||||
        search_prefer_start_matches: Rc<AtomicBool>,
 | 
					        search_prefer_start_matches: Arc<AtomicBool>,
 | 
				
			||||||
        filter_songs: Rc<Mutex<Filter>>,
 | 
					        filter_songs: Arc<Mutex<Filter>>,
 | 
				
			||||||
        filter_albums: Rc<Mutex<Filter>>,
 | 
					        filter_albums: Arc<Mutex<Filter>>,
 | 
				
			||||||
        filter_artists: Rc<Mutex<Filter>>,
 | 
					        filter_artists: Arc<Mutex<Filter>>,
 | 
				
			||||||
        selected: Selected,
 | 
					        selected: Selected,
 | 
				
			||||||
        do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut LibraryBrowser)>>,
 | 
					        do_something_sender: mpsc::Sender<Box<dyn FnOnce(&mut LibraryBrowser)>>,
 | 
				
			||||||
    ) -> Self {
 | 
					    ) -> Self {
 | 
				
			||||||
        let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
 | 
					        let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
        let prefer_start_matches =
 | 
					        let prefer_start_matches =
 | 
				
			||||||
            search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
 | 
					            search_prefer_start_matches.load(std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
        let ssc1 = Rc::clone(&search_settings_changed);
 | 
					        let ssc1 = Arc::clone(&search_settings_changed);
 | 
				
			||||||
        let ssc2 = Rc::clone(&search_settings_changed);
 | 
					        let ssc2 = Arc::clone(&search_settings_changed);
 | 
				
			||||||
        let ssc3 = Rc::clone(&search_settings_changed);
 | 
					        let ssc3 = Arc::clone(&search_settings_changed);
 | 
				
			||||||
        let sel3 = selected.clone();
 | 
					        let sel3 = selected.clone();
 | 
				
			||||||
        const VSPLIT: f32 = 0.4;
 | 
					        const VSPLIT: f32 = 0.4;
 | 
				
			||||||
        let tab_main = GuiElem::new(ScrollBox::new(
 | 
					        let tab_main = GuiElem::new(ScrollBox::new(
 | 
				
			||||||
@ -1387,10 +1387,10 @@ impl FilterPanel {
 | 
				
			|||||||
            crate::gui_base::ScrollBoxSizeUnit::Pixels,
 | 
					            crate::gui_base::ScrollBoxSizeUnit::Pixels,
 | 
				
			||||||
            vec![],
 | 
					            vec![],
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
        let new_tab = Rc::new(AtomicUsize::new(0));
 | 
					        let new_tab = Arc::new(AtomicUsize::new(0));
 | 
				
			||||||
        let set_tab_1 = Rc::clone(&new_tab);
 | 
					        let set_tab_1 = Arc::clone(&new_tab);
 | 
				
			||||||
        let set_tab_2 = Rc::clone(&new_tab);
 | 
					        let set_tab_2 = Arc::clone(&new_tab);
 | 
				
			||||||
        let set_tab_3 = Rc::clone(&new_tab);
 | 
					        let set_tab_3 = Arc::clone(&new_tab);
 | 
				
			||||||
        const HEIGHT: f32 = 0.1;
 | 
					        const HEIGHT: f32 = 0.1;
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            config: GuiElemCfg::default().disabled(),
 | 
					            config: GuiElemCfg::default().disabled(),
 | 
				
			||||||
@ -1458,17 +1458,17 @@ impl FilterPanel {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    fn build_filter(
 | 
					    fn build_filter(
 | 
				
			||||||
        filter: &Rc<Mutex<Filter>>,
 | 
					        filter: &Arc<Mutex<Filter>>,
 | 
				
			||||||
        line_height: f32,
 | 
					        line_height: f32,
 | 
				
			||||||
        on_change: &Rc<impl Fn(bool) + 'static>,
 | 
					        on_change: &Arc<impl Fn(bool) + 'static>,
 | 
				
			||||||
        path: Vec<usize>,
 | 
					        path: Vec<usize>,
 | 
				
			||||||
    ) -> Vec<(GuiElem, f32)> {
 | 
					    ) -> Vec<(GuiElem, f32)> {
 | 
				
			||||||
        let f0 = Rc::clone(filter);
 | 
					        let f0 = Arc::clone(filter);
 | 
				
			||||||
        let oc0 = Rc::clone(on_change);
 | 
					        let oc0 = Arc::clone(on_change);
 | 
				
			||||||
        let f1 = Rc::clone(filter);
 | 
					        let f1 = Arc::clone(filter);
 | 
				
			||||||
        let f2 = Rc::clone(filter);
 | 
					        let f2 = Arc::clone(filter);
 | 
				
			||||||
        let oc1 = Rc::clone(on_change);
 | 
					        let oc1 = Arc::clone(on_change);
 | 
				
			||||||
        let oc2 = Rc::clone(on_change);
 | 
					        let oc2 = Arc::clone(on_change);
 | 
				
			||||||
        let mut children = vec![
 | 
					        let mut children = vec![
 | 
				
			||||||
            GuiElem::new(Button::new(
 | 
					            GuiElem::new(Button::new(
 | 
				
			||||||
                GuiElemCfg::default(),
 | 
					                GuiElemCfg::default(),
 | 
				
			||||||
@ -1536,16 +1536,16 @@ impl FilterPanel {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    fn build_filter_editor(
 | 
					    fn build_filter_editor(
 | 
				
			||||||
        filter: &Filter,
 | 
					        filter: &Filter,
 | 
				
			||||||
        mutex: &Rc<Mutex<Filter>>,
 | 
					        mutex: &Arc<Mutex<Filter>>,
 | 
				
			||||||
        children: &mut Vec<GuiElem>,
 | 
					        children: &mut Vec<GuiElem>,
 | 
				
			||||||
        mut indent: f32,
 | 
					        mut indent: f32,
 | 
				
			||||||
        indent_by: f32,
 | 
					        indent_by: f32,
 | 
				
			||||||
        on_change: &Rc<impl Fn(bool) + 'static>,
 | 
					        on_change: &Arc<impl Fn(bool) + 'static>,
 | 
				
			||||||
        path: Vec<usize>,
 | 
					        path: Vec<usize>,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if filter.filters.len() > 1 {
 | 
					        if filter.filters.len() > 1 {
 | 
				
			||||||
            let mx = Rc::clone(mutex);
 | 
					            let mx = Arc::clone(mutex);
 | 
				
			||||||
            let oc = Rc::clone(on_change);
 | 
					            let oc = Arc::clone(on_change);
 | 
				
			||||||
            let p = path.clone();
 | 
					            let p = path.clone();
 | 
				
			||||||
            children.push(GuiElem::new(Button::new(
 | 
					            children.push(GuiElem::new(Button::new(
 | 
				
			||||||
                GuiElemCfg::at(Rectangle::from_tuples((indent, 0.0), (1.0, 1.0))),
 | 
					                GuiElemCfg::at(Rectangle::from_tuples((indent, 0.0), (1.0, 1.0))),
 | 
				
			||||||
@ -1597,8 +1597,8 @@ impl FilterPanel {
 | 
				
			|||||||
                        Color::GRAY,
 | 
					                        Color::GRAY,
 | 
				
			||||||
                        Color::WHITE,
 | 
					                        Color::WHITE,
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    let mx = Rc::clone(mutex);
 | 
					                    let mx = Arc::clone(mutex);
 | 
				
			||||||
                    let oc = Rc::clone(on_change);
 | 
					                    let oc = Arc::clone(on_change);
 | 
				
			||||||
                    tf.on_changed = Some(Box::new(move |text| {
 | 
					                    tf.on_changed = Some(Box::new(move |text| {
 | 
				
			||||||
                        if let Some(Ok(FilterType::TagEq(v))) = mx.lock().unwrap().get_mut(&path) {
 | 
					                        if let Some(Ok(FilterType::TagEq(v))) = mx.lock().unwrap().get_mut(&path) {
 | 
				
			||||||
                            *v = text.to_owned();
 | 
					                            *v = text.to_owned();
 | 
				
			||||||
@ -1627,8 +1627,8 @@ impl FilterPanel {
 | 
				
			|||||||
                        Color::GRAY,
 | 
					                        Color::GRAY,
 | 
				
			||||||
                        Color::WHITE,
 | 
					                        Color::WHITE,
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    let mx = Rc::clone(mutex);
 | 
					                    let mx = Arc::clone(mutex);
 | 
				
			||||||
                    let oc = Rc::clone(on_change);
 | 
					                    let oc = Arc::clone(on_change);
 | 
				
			||||||
                    tf.on_changed = Some(Box::new(move |text| {
 | 
					                    tf.on_changed = Some(Box::new(move |text| {
 | 
				
			||||||
                        if let Some(Ok(FilterType::TagStartsWith(v))) =
 | 
					                        if let Some(Ok(FilterType::TagStartsWith(v))) =
 | 
				
			||||||
                            mx.lock().unwrap().get_mut(&path)
 | 
					                            mx.lock().unwrap().get_mut(&path)
 | 
				
			||||||
@ -1659,8 +1659,8 @@ impl FilterPanel {
 | 
				
			|||||||
                        Color::GRAY,
 | 
					                        Color::GRAY,
 | 
				
			||||||
                        Color::WHITE,
 | 
					                        Color::WHITE,
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    let mx = Rc::clone(mutex);
 | 
					                    let mx = Arc::clone(mutex);
 | 
				
			||||||
                    let oc = Rc::clone(on_change);
 | 
					                    let oc = Arc::clone(on_change);
 | 
				
			||||||
                    let p = path.clone();
 | 
					                    let p = path.clone();
 | 
				
			||||||
                    tf.on_changed = Some(Box::new(move |text| {
 | 
					                    tf.on_changed = Some(Box::new(move |text| {
 | 
				
			||||||
                        if let Some(Ok(FilterType::TagWithValueInt(v, _, _))) =
 | 
					                        if let Some(Ok(FilterType::TagWithValueInt(v, _, _))) =
 | 
				
			||||||
@ -1684,8 +1684,8 @@ impl FilterPanel {
 | 
				
			|||||||
                        Color::GRAY,
 | 
					                        Color::GRAY,
 | 
				
			||||||
                        Color::WHITE,
 | 
					                        Color::WHITE,
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    let mx = Rc::clone(mutex);
 | 
					                    let mx = Arc::clone(mutex);
 | 
				
			||||||
                    let oc = Rc::clone(on_change);
 | 
					                    let oc = Arc::clone(on_change);
 | 
				
			||||||
                    let p = path.clone();
 | 
					                    let p = path.clone();
 | 
				
			||||||
                    tf1.on_changed = Some(Box::new(move |text| {
 | 
					                    tf1.on_changed = Some(Box::new(move |text| {
 | 
				
			||||||
                        if let Ok(n) = text.parse() {
 | 
					                        if let Ok(n) = text.parse() {
 | 
				
			||||||
@ -1697,8 +1697,8 @@ impl FilterPanel {
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }));
 | 
					                    }));
 | 
				
			||||||
                    let mx = Rc::clone(mutex);
 | 
					                    let mx = Arc::clone(mutex);
 | 
				
			||||||
                    let oc = Rc::clone(on_change);
 | 
					                    let oc = Arc::clone(on_change);
 | 
				
			||||||
                    let p = path.clone();
 | 
					                    let p = path.clone();
 | 
				
			||||||
                    tf2.on_changed = Some(Box::new(move |text| {
 | 
					                    tf2.on_changed = Some(Box::new(move |text| {
 | 
				
			||||||
                        if let Ok(n) = text.parse() {
 | 
					                        if let Ok(n) = text.parse() {
 | 
				
			||||||
@ -1813,9 +1813,9 @@ impl GuiElemTrait for FilterPanel {
 | 
				
			|||||||
                        .unwrap()
 | 
					                        .unwrap()
 | 
				
			||||||
                        .try_as_mut::<ScrollBox>()
 | 
					                        .try_as_mut::<ScrollBox>()
 | 
				
			||||||
                        .unwrap();
 | 
					                        .unwrap();
 | 
				
			||||||
                    let ssc = Rc::clone(&self.search_settings_changed);
 | 
					                    let ssc = Arc::clone(&self.search_settings_changed);
 | 
				
			||||||
                    let my_tab = new_tab;
 | 
					                    let my_tab = new_tab;
 | 
				
			||||||
                    let ntab = Rc::clone(&self.new_tab);
 | 
					                    let ntab = Arc::clone(&self.new_tab);
 | 
				
			||||||
                    sb.children = Self::build_filter(
 | 
					                    sb.children = Self::build_filter(
 | 
				
			||||||
                        match new_tab {
 | 
					                        match new_tab {
 | 
				
			||||||
                            0 => &self.filter_songs,
 | 
					                            0 => &self.filter_songs,
 | 
				
			||||||
@ -1824,7 +1824,7 @@ impl GuiElemTrait for FilterPanel {
 | 
				
			|||||||
                            _ => unreachable!(),
 | 
					                            _ => unreachable!(),
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        info.line_height,
 | 
					                        info.line_height,
 | 
				
			||||||
                        &Rc::new(move |update_ui| {
 | 
					                        &Arc::new(move |update_ui| {
 | 
				
			||||||
                            if update_ui {
 | 
					                            if update_ui {
 | 
				
			||||||
                                ntab.store(my_tab, std::sync::atomic::Ordering::Relaxed);
 | 
					                                ntab.store(my_tab, std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										209
									
								
								musicdb-client/src/gui_notif.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								musicdb-client/src/gui_notif.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,209 @@
 | 
				
			|||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    sync::mpsc,
 | 
				
			||||||
 | 
					    time::{Duration, Instant},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use speedy2d::{color::Color, dimen::Vector2, shape::Rectangle};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::gui::{GuiElem, GuiElemCfg, GuiElemTrait};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// This should be added on top of overything else and set to fullscreen.
 | 
				
			||||||
 | 
					/// It will respond to notification events.
 | 
				
			||||||
 | 
					pub struct NotifOverlay {
 | 
				
			||||||
 | 
					    config: GuiElemCfg,
 | 
				
			||||||
 | 
					    notifs: Vec<(GuiElem, NotifInfo)>,
 | 
				
			||||||
 | 
					    light: Option<(Instant, Color)>,
 | 
				
			||||||
 | 
					    receiver: mpsc::Receiver<Box<dyn FnOnce(&Self) -> (GuiElem, NotifInfo) + Send>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl NotifOverlay {
 | 
				
			||||||
 | 
					    pub fn new() -> (
 | 
				
			||||||
 | 
					        Self,
 | 
				
			||||||
 | 
					        mpsc::Sender<Box<dyn FnOnce(&Self) -> (GuiElem, NotifInfo) + Send>>,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        let (sender, receiver) = mpsc::channel();
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					            Self {
 | 
				
			||||||
 | 
					                config: GuiElemCfg::default(),
 | 
				
			||||||
 | 
					                notifs: vec![],
 | 
				
			||||||
 | 
					                light: None,
 | 
				
			||||||
 | 
					                receiver,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            sender,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn check_notifs(&mut self) {
 | 
				
			||||||
 | 
					        let mut adjust_heights = false;
 | 
				
			||||||
 | 
					        let mut remove = Vec::with_capacity(0);
 | 
				
			||||||
 | 
					        for (i, (gui, info)) in self.notifs.iter_mut().enumerate() {
 | 
				
			||||||
 | 
					            match info.time {
 | 
				
			||||||
 | 
					                NotifInfoTime::Pending => {
 | 
				
			||||||
 | 
					                    if self.light.is_none() {
 | 
				
			||||||
 | 
					                        let now = Instant::now();
 | 
				
			||||||
 | 
					                        info.time = NotifInfoTime::FadingIn(now);
 | 
				
			||||||
 | 
					                        if let Some(color) = info.color {
 | 
				
			||||||
 | 
					                            self.light = Some((now, color));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        adjust_heights = true;
 | 
				
			||||||
 | 
					                        gui.inner.config_mut().enabled = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NotifInfoTime::FadingIn(since) => {
 | 
				
			||||||
 | 
					                    adjust_heights = true;
 | 
				
			||||||
 | 
					                    let p = since.elapsed().as_secs_f32() / 0.25;
 | 
				
			||||||
 | 
					                    if p >= 1.0 {
 | 
				
			||||||
 | 
					                        info.time = NotifInfoTime::Displayed(Instant::now());
 | 
				
			||||||
 | 
					                        info.progress = 0.0;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        info.progress = p;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NotifInfoTime::Displayed(since) => {
 | 
				
			||||||
 | 
					                    let p = since.elapsed().as_secs_f32() / info.duration.as_secs_f32();
 | 
				
			||||||
 | 
					                    if p >= 1.0 {
 | 
				
			||||||
 | 
					                        info.time = NotifInfoTime::FadingOut(Instant::now());
 | 
				
			||||||
 | 
					                        info.progress = 0.0;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        info.progress = p;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NotifInfoTime::FadingOut(since) => {
 | 
				
			||||||
 | 
					                    adjust_heights = true;
 | 
				
			||||||
 | 
					                    let p = since.elapsed().as_secs_f32() / 0.25;
 | 
				
			||||||
 | 
					                    if p >= 1.0 {
 | 
				
			||||||
 | 
					                        remove.push(i);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        info.progress = p;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for index in remove.into_iter().rev() {
 | 
				
			||||||
 | 
					            self.notifs.remove(index);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if adjust_heights {
 | 
				
			||||||
 | 
					            self.adjust_heights();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn adjust_heights(&mut self) {
 | 
				
			||||||
 | 
					        let screen_size = self.config.pixel_pos.size();
 | 
				
			||||||
 | 
					        let width = 0.3;
 | 
				
			||||||
 | 
					        let left = 0.5 - (0.5 * width);
 | 
				
			||||||
 | 
					        let right = 0.5 + (0.5 * width);
 | 
				
			||||||
 | 
					        let height = 0.2 * width * screen_size.x / screen_size.y;
 | 
				
			||||||
 | 
					        let space = 0.05 / 0.2 * height;
 | 
				
			||||||
 | 
					        let mut y = 0.0;
 | 
				
			||||||
 | 
					        for (gui, info) in self.notifs.iter_mut() {
 | 
				
			||||||
 | 
					            y += space;
 | 
				
			||||||
 | 
					            let pos_y = if matches!(info.time, NotifInfoTime::FadingOut(..)) {
 | 
				
			||||||
 | 
					                let v = y - (height + y) * info.progress * info.progress;
 | 
				
			||||||
 | 
					                // for notifs below this one
 | 
				
			||||||
 | 
					                y -= (height + space) * crate::gui_screen::transition(info.progress);
 | 
				
			||||||
 | 
					                v
 | 
				
			||||||
 | 
					            } else if matches!(info.time, NotifInfoTime::FadingIn(..)) {
 | 
				
			||||||
 | 
					                -height + (height + y) * (1.0 - (1.0 - info.progress) * (1.0 - info.progress))
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                y
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            y += height;
 | 
				
			||||||
 | 
					            gui.inner.config_mut().pos =
 | 
				
			||||||
 | 
					                Rectangle::from_tuples((left, pos_y), (right, pos_y + height));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone)]
 | 
				
			||||||
 | 
					pub struct NotifInfo {
 | 
				
			||||||
 | 
					    time: NotifInfoTime,
 | 
				
			||||||
 | 
					    duration: Duration,
 | 
				
			||||||
 | 
					    /// when the notification is first shown on screen,
 | 
				
			||||||
 | 
					    /// light up the edges of the screen/window
 | 
				
			||||||
 | 
					    /// in this color (usually red for important things)
 | 
				
			||||||
 | 
					    color: Option<Color>,
 | 
				
			||||||
 | 
					    /// used for fade-out animation
 | 
				
			||||||
 | 
					    progress: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#[derive(Clone)]
 | 
				
			||||||
 | 
					enum NotifInfoTime {
 | 
				
			||||||
 | 
					    Pending,
 | 
				
			||||||
 | 
					    FadingIn(Instant),
 | 
				
			||||||
 | 
					    Displayed(Instant),
 | 
				
			||||||
 | 
					    FadingOut(Instant),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl NotifInfo {
 | 
				
			||||||
 | 
					    pub fn new(duration: Duration) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            time: NotifInfoTime::Pending,
 | 
				
			||||||
 | 
					            duration,
 | 
				
			||||||
 | 
					            color: None,
 | 
				
			||||||
 | 
					            progress: 0.0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub fn with_highlight(mut self, color: Color) -> Self {
 | 
				
			||||||
 | 
					        self.color = Some(color);
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Clone for NotifOverlay {
 | 
				
			||||||
 | 
					    fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					        Self::new().0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GuiElemTrait for NotifOverlay {
 | 
				
			||||||
 | 
					    fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
 | 
				
			||||||
 | 
					        if let Ok(notif) = self.receiver.try_recv() {
 | 
				
			||||||
 | 
					            let mut n = notif(self);
 | 
				
			||||||
 | 
					            n.0.inner.config_mut().enabled = false;
 | 
				
			||||||
 | 
					            self.notifs.push(n);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.check_notifs();
 | 
				
			||||||
 | 
					        // light
 | 
				
			||||||
 | 
					        if let Some((since, color)) = self.light {
 | 
				
			||||||
 | 
					            let p = since.elapsed().as_secs_f32() / 0.5;
 | 
				
			||||||
 | 
					            if p >= 1.0 {
 | 
				
			||||||
 | 
					                self.light = None;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                let f = p * 2.0 - 1.0;
 | 
				
			||||||
 | 
					                let f = 1.0 - f * f;
 | 
				
			||||||
 | 
					                let color = Color::from_rgba(color.r(), color.g(), color.b(), color.a() * f);
 | 
				
			||||||
 | 
					                let Vector2 { x: x1, y: y1 } = *info.pos.top_left();
 | 
				
			||||||
 | 
					                let Vector2 { x: x2, y: y2 } = *info.pos.bottom_right();
 | 
				
			||||||
 | 
					                let width = info.pos.width() * 0.01;
 | 
				
			||||||
 | 
					                g.draw_rectangle(Rectangle::from_tuples((x1, y1), (x1 + width, y2)), color);
 | 
				
			||||||
 | 
					                g.draw_rectangle(Rectangle::from_tuples((x2 - width, y1), (x2, y2)), color);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // redraw
 | 
				
			||||||
 | 
					        if !self.notifs.is_empty() {
 | 
				
			||||||
 | 
					            if let Some(h) = &info.helper {
 | 
				
			||||||
 | 
					                h.request_redraw();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn draw_rev(&self) -> bool {
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn config(&self) -> &GuiElemCfg {
 | 
				
			||||||
 | 
					        &self.config
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn config_mut(&mut self) -> &mut GuiElemCfg {
 | 
				
			||||||
 | 
					        &mut self.config
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn children(&mut self) -> Box<dyn Iterator<Item = &mut GuiElem> + '_> {
 | 
				
			||||||
 | 
					        Box::new(self.notifs.iter_mut().map(|(v, _)| v))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn any(&self) -> &dyn std::any::Any {
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn any_mut(&mut self) -> &mut dyn std::any::Any {
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn clone_gui(&self) -> Box<dyn GuiElemTrait> {
 | 
				
			||||||
 | 
					        Box::new(self.clone())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,6 +7,7 @@ use crate::{
 | 
				
			|||||||
    gui::{morph_rect, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
 | 
					    gui::{morph_rect, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
 | 
				
			||||||
    gui_base::{Button, Panel},
 | 
					    gui_base::{Button, Panel},
 | 
				
			||||||
    gui_library::LibraryBrowser,
 | 
					    gui_library::LibraryBrowser,
 | 
				
			||||||
 | 
					    gui_notif::NotifOverlay,
 | 
				
			||||||
    gui_playback::{CurrentSong, PlayPauseToggle},
 | 
					    gui_playback::{CurrentSong, PlayPauseToggle},
 | 
				
			||||||
    gui_queue::QueueViewer,
 | 
					    gui_queue::QueueViewer,
 | 
				
			||||||
    gui_settings::Settings,
 | 
					    gui_settings::Settings,
 | 
				
			||||||
@ -34,15 +35,16 @@ pub fn transition(p: f32) -> f32 {
 | 
				
			|||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub struct GuiScreen {
 | 
					pub struct GuiScreen {
 | 
				
			||||||
    config: GuiElemCfg,
 | 
					    config: GuiElemCfg,
 | 
				
			||||||
    /// 0: StatusBar / Idle display
 | 
					    /// 0: Notifications
 | 
				
			||||||
    /// 1: Settings
 | 
					    /// 1: StatusBar / Idle display
 | 
				
			||||||
    /// 2: Panel for Main view
 | 
					    /// 2: Settings
 | 
				
			||||||
 | 
					    /// 3: Panel for Main view
 | 
				
			||||||
    ///  0: settings button
 | 
					    ///  0: settings button
 | 
				
			||||||
    ///  1: exit button
 | 
					    ///  1: exit button
 | 
				
			||||||
    ///  2: library browser
 | 
					    ///  2: library browser
 | 
				
			||||||
    ///  3: queue
 | 
					    ///  3: queue
 | 
				
			||||||
    ///  4: queue clear button
 | 
					    ///  4: queue clear button
 | 
				
			||||||
    /// 3: Edit Panel
 | 
					    /// 4: Edit Panel
 | 
				
			||||||
    children: Vec<GuiElem>,
 | 
					    children: Vec<GuiElem>,
 | 
				
			||||||
    pub idle: (bool, Option<Instant>),
 | 
					    pub idle: (bool, Option<Instant>),
 | 
				
			||||||
    pub settings: (bool, Option<Instant>),
 | 
					    pub settings: (bool, Option<Instant>),
 | 
				
			||||||
@ -59,21 +61,22 @@ impl GuiScreen {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
 | 
					            edit.inner.config_mut().pos = Rectangle::from_tuples((0.0, 0.0), (0.5, 0.9));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if let Some(prev) = self.children.get_mut(3) {
 | 
					        if let Some(prev) = self.children.get_mut(4) {
 | 
				
			||||||
            prev.inner.config_mut().enabled = false;
 | 
					            prev.inner.config_mut().enabled = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        self.children.insert(3, edit);
 | 
					        self.children.insert(4, edit);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn close_edit(&mut self) {
 | 
					    pub fn close_edit(&mut self) {
 | 
				
			||||||
        if self.children.len() > 4 {
 | 
					        if self.children.len() > 5 {
 | 
				
			||||||
            self.children.remove(3);
 | 
					            self.children.remove(4);
 | 
				
			||||||
            self.children[3].inner.config_mut().enabled = true;
 | 
					            self.children[4].inner.config_mut().enabled = true;
 | 
				
			||||||
        } else if self.edit_panel.0 {
 | 
					        } else if self.edit_panel.0 {
 | 
				
			||||||
            self.edit_panel = (false, Some(Instant::now()));
 | 
					            self.edit_panel = (false, Some(Instant::now()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn new(
 | 
					    pub fn new(
 | 
				
			||||||
        config: GuiElemCfg,
 | 
					        config: GuiElemCfg,
 | 
				
			||||||
 | 
					        notif_overlay: NotifOverlay,
 | 
				
			||||||
        line_height: f32,
 | 
					        line_height: f32,
 | 
				
			||||||
        scroll_sensitivity_pixels: f64,
 | 
					        scroll_sensitivity_pixels: f64,
 | 
				
			||||||
        scroll_sensitivity_lines: f64,
 | 
					        scroll_sensitivity_lines: f64,
 | 
				
			||||||
@ -82,6 +85,7 @@ impl GuiScreen {
 | 
				
			|||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            config: config.w_keyboard_watch().w_mouse(),
 | 
					            config: config.w_keyboard_watch().w_mouse(),
 | 
				
			||||||
            children: vec![
 | 
					            children: vec![
 | 
				
			||||||
 | 
					                GuiElem::new(notif_overlay),
 | 
				
			||||||
                GuiElem::new(StatusBar::new(
 | 
					                GuiElem::new(StatusBar::new(
 | 
				
			||||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
 | 
					                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
 | 
				
			||||||
                    true,
 | 
					                    true,
 | 
				
			||||||
@ -261,20 +265,20 @@ impl GuiElemTrait for GuiScreen {
 | 
				
			|||||||
                if let Some(h) = &info.helper {
 | 
					                if let Some(h) = &info.helper {
 | 
				
			||||||
                    h.set_cursor_visible(!self.idle.0);
 | 
					                    h.set_cursor_visible(!self.idle.0);
 | 
				
			||||||
                    if self.settings.0 {
 | 
					                    if self.settings.0 {
 | 
				
			||||||
                        self.children[1].inner.config_mut().enabled = !self.idle.0;
 | 
					                        self.children[2].inner.config_mut().enabled = !self.idle.0;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if self.edit_panel.0 {
 | 
					                    if self.edit_panel.0 {
 | 
				
			||||||
                        if let Some(c) = self.children.get_mut(3) {
 | 
					                        if let Some(c) = self.children.get_mut(4) {
 | 
				
			||||||
                            c.inner.config_mut().enabled = !self.idle.0;
 | 
					                            c.inner.config_mut().enabled = !self.idle.0;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    self.children[2].inner.config_mut().enabled = !self.idle.0;
 | 
					                    self.children[3].inner.config_mut().enabled = !self.idle.0;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let p = transition(p1);
 | 
					            let p = transition(p1);
 | 
				
			||||||
            self.children[0].inner.config_mut().pos =
 | 
					            self.children[1].inner.config_mut().pos =
 | 
				
			||||||
                Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0));
 | 
					                Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 1.0));
 | 
				
			||||||
            self.children[0]
 | 
					            self.children[1]
 | 
				
			||||||
                .inner
 | 
					                .inner
 | 
				
			||||||
                .any_mut()
 | 
					                .any_mut()
 | 
				
			||||||
                .downcast_mut::<StatusBar>()
 | 
					                .downcast_mut::<StatusBar>()
 | 
				
			||||||
@ -285,7 +289,7 @@ impl GuiElemTrait for GuiScreen {
 | 
				
			|||||||
        if self.settings.1.is_some() {
 | 
					        if self.settings.1.is_some() {
 | 
				
			||||||
            let p1 = Self::get_prog(&mut self.settings, 0.3);
 | 
					            let p1 = Self::get_prog(&mut self.settings, 0.3);
 | 
				
			||||||
            let p = transition(p1);
 | 
					            let p = transition(p1);
 | 
				
			||||||
            let cfg = self.children[1].inner.config_mut();
 | 
					            let cfg = self.children[2].inner.config_mut();
 | 
				
			||||||
            cfg.enabled = p > 0.0;
 | 
					            cfg.enabled = p > 0.0;
 | 
				
			||||||
            cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
 | 
					            cfg.pos = Rectangle::from_tuples((0.0, 0.9 - 0.9 * p), (1.0, 0.9));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -293,22 +297,22 @@ impl GuiElemTrait for GuiScreen {
 | 
				
			|||||||
        if self.edit_panel.1.is_some() {
 | 
					        if self.edit_panel.1.is_some() {
 | 
				
			||||||
            let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
 | 
					            let p1 = Self::get_prog(&mut self.edit_panel, 0.3);
 | 
				
			||||||
            let p = transition(p1);
 | 
					            let p = transition(p1);
 | 
				
			||||||
            if let Some(c) = self.children.get_mut(3) {
 | 
					            if let Some(c) = self.children.get_mut(4) {
 | 
				
			||||||
                c.inner.config_mut().enabled = p > 0.0;
 | 
					                c.inner.config_mut().enabled = p > 0.0;
 | 
				
			||||||
                c.inner.config_mut().pos =
 | 
					                c.inner.config_mut().pos =
 | 
				
			||||||
                    Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
 | 
					                    Rectangle::from_tuples((-0.5 + 0.5 * p, 0.0), (0.5 * p, 0.9));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if !self.edit_panel.0 && p == 0.0 {
 | 
					            if !self.edit_panel.0 && p == 0.0 {
 | 
				
			||||||
                while self.children.len() > 3 {
 | 
					                while self.children.len() > 4 {
 | 
				
			||||||
                    self.children.pop();
 | 
					                    self.children.pop();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            self.children[2].inner.config_mut().pos =
 | 
					            self.children[3].inner.config_mut().pos =
 | 
				
			||||||
                Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
 | 
					                Rectangle::from_tuples((0.5 * p, 0.0), (1.0 + 0.5 * p, 0.9));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // set idle timeout (only when settings are open)
 | 
					        // set idle timeout (only when settings are open)
 | 
				
			||||||
        if self.settings.0 || self.settings.1.is_some() {
 | 
					        if self.settings.0 || self.settings.1.is_some() {
 | 
				
			||||||
            self.idle_timeout = self.children[1]
 | 
					            self.idle_timeout = self.children[2]
 | 
				
			||||||
                .inner
 | 
					                .inner
 | 
				
			||||||
                .any()
 | 
					                .any()
 | 
				
			||||||
                .downcast_ref::<Settings>()
 | 
					                .downcast_ref::<Settings>()
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,8 @@ mod gui_edit;
 | 
				
			|||||||
#[cfg(feature = "speedy2d")]
 | 
					#[cfg(feature = "speedy2d")]
 | 
				
			||||||
mod gui_library;
 | 
					mod gui_library;
 | 
				
			||||||
#[cfg(feature = "speedy2d")]
 | 
					#[cfg(feature = "speedy2d")]
 | 
				
			||||||
 | 
					mod gui_notif;
 | 
				
			||||||
 | 
					#[cfg(feature = "speedy2d")]
 | 
				
			||||||
mod gui_playback;
 | 
					mod gui_playback;
 | 
				
			||||||
#[cfg(feature = "speedy2d")]
 | 
					#[cfg(feature = "speedy2d")]
 | 
				
			||||||
mod gui_queue;
 | 
					mod gui_queue;
 | 
				
			||||||
 | 
				
			|||||||
@ -232,7 +232,14 @@ impl Database {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn apply_command(&mut self, command: Command) {
 | 
					    pub fn apply_command(&mut self, mut command: Command) {
 | 
				
			||||||
 | 
					        if !self.is_client() {
 | 
				
			||||||
 | 
					            if let Command::ErrorInfo(t, _) = &mut command {
 | 
				
			||||||
 | 
					                // clients can send ErrorInfo to the server and it will show up on other clients,
 | 
				
			||||||
 | 
					                // BUT only the server can set the Title of the ErrorInfo.
 | 
				
			||||||
 | 
					                t.clear();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        // since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
 | 
					        // since db.update_endpoints is empty for clients, this won't cause unwanted back and forth
 | 
				
			||||||
        self.broadcast_update(&command);
 | 
					        self.broadcast_update(&command);
 | 
				
			||||||
        match command {
 | 
					        match command {
 | 
				
			||||||
@ -349,6 +356,7 @@ impl Database {
 | 
				
			|||||||
            Command::InitComplete => {
 | 
					            Command::InitComplete => {
 | 
				
			||||||
                self.client_is_init = true;
 | 
					                self.client_is_init = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Command::ErrorInfo(..) => {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,7 @@ impl Song {
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    Ok(data) => Some(data),
 | 
					                    Ok(data) => Some(data),
 | 
				
			||||||
                    Err(e) => {
 | 
					                    Err(e) => {
 | 
				
			||||||
                        eprintln!("[info] error loading song {id}: {e}");
 | 
					                        eprintln!("[WARN] error loading song {id}: {e}");
 | 
				
			||||||
                        None
 | 
					                        None
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -139,6 +139,13 @@ impl Player {
 | 
				
			|||||||
                                    db.apply_command(Command::NextSong);
 | 
					                                    db.apply_command(Command::NextSong);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            // couldn't load song bytes
 | 
				
			||||||
 | 
					                            db.broadcast_update(&Command::ErrorInfo(
 | 
				
			||||||
 | 
					                                "NoSongData".to_owned(),
 | 
				
			||||||
 | 
					                                format!("Couldn't load song #{}\n({})", song.id, song.title),
 | 
				
			||||||
 | 
					                            ));
 | 
				
			||||||
 | 
					                            db.apply_command(Command::NextSong);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        self.source = None;
 | 
					                        self.source = None;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
pub mod get;
 | 
					pub mod get;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    eprintln,
 | 
					 | 
				
			||||||
    io::{BufRead, BufReader, Read, Write},
 | 
					    io::{BufRead, BufReader, Read, Write},
 | 
				
			||||||
    net::{SocketAddr, TcpListener},
 | 
					    net::{SocketAddr, TcpListener},
 | 
				
			||||||
    sync::{mpsc, Arc, Mutex},
 | 
					    sync::{mpsc, Arc, Mutex},
 | 
				
			||||||
@ -51,6 +50,7 @@ pub enum Command {
 | 
				
			|||||||
    RemoveArtist(ArtistId),
 | 
					    RemoveArtist(ArtistId),
 | 
				
			||||||
    ModifyArtist(Artist),
 | 
					    ModifyArtist(Artist),
 | 
				
			||||||
    InitComplete,
 | 
					    InitComplete,
 | 
				
			||||||
 | 
					    ErrorInfo(String, String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl Command {
 | 
					impl Command {
 | 
				
			||||||
    pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
 | 
					    pub fn send_to_server(self, db: &Database) -> Result<(), Self> {
 | 
				
			||||||
@ -106,7 +106,6 @@ pub fn run_server(
 | 
				
			|||||||
                        let command_sender = command_sender.clone();
 | 
					                        let command_sender = command_sender.clone();
 | 
				
			||||||
                        let db = Arc::clone(&db);
 | 
					                        let db = Arc::clone(&db);
 | 
				
			||||||
                        thread::spawn(move || {
 | 
					                        thread::spawn(move || {
 | 
				
			||||||
                            eprintln!("[info] TCP connection accepted from {con_addr}.");
 | 
					 | 
				
			||||||
                            // each connection first has to send one line to tell us what it wants
 | 
					                            // each connection first has to send one line to tell us what it wants
 | 
				
			||||||
                            let mut connection = BufReader::new(connection);
 | 
					                            let mut connection = BufReader::new(connection);
 | 
				
			||||||
                            let mut line = String::new();
 | 
					                            let mut line = String::new();
 | 
				
			||||||
@ -279,6 +278,11 @@ impl ToFromBytes for Command {
 | 
				
			|||||||
            Self::InitComplete => {
 | 
					            Self::InitComplete => {
 | 
				
			||||||
                s.write_all(&[0b00110001])?;
 | 
					                s.write_all(&[0b00110001])?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Self::ErrorInfo(t, d) => {
 | 
				
			||||||
 | 
					                s.write_all(&[0b11011011])?;
 | 
				
			||||||
 | 
					                t.to_bytes(s)?;
 | 
				
			||||||
 | 
					                d.to_bytes(s)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -324,6 +328,7 @@ impl ToFromBytes for Command {
 | 
				
			|||||||
            0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
 | 
					            0b11011100 => Self::RemoveArtist(ToFromBytes::from_bytes(s)?),
 | 
				
			||||||
            0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
 | 
					            0b01011101 => Self::AddCover(ToFromBytes::from_bytes(s)?),
 | 
				
			||||||
            0b00110001 => Self::InitComplete,
 | 
					            0b00110001 => Self::InitComplete,
 | 
				
			||||||
 | 
					            0b11011011 => Self::ErrorInfo(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?),
 | 
				
			||||||
            _ => {
 | 
					            _ => {
 | 
				
			||||||
                eprintln!("unexpected byte when reading command; stopping playback.");
 | 
					                eprintln!("unexpected byte when reading command; stopping playback.");
 | 
				
			||||||
                Self::Stop
 | 
					                Self::Stop
 | 
				
			||||||
 | 
				
			|||||||
@ -438,7 +438,9 @@ async fn sse_handler(
 | 
				
			|||||||
                            .collect::<String>(),
 | 
					                            .collect::<String>(),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Command::Save | Command::InitComplete => return Poll::Pending,
 | 
					                Command::Save | Command::InitComplete | Command::ErrorInfo(..) => {
 | 
				
			||||||
 | 
					                    return Poll::Pending
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }))
 | 
					            }))
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return Poll::Pending;
 | 
					            return Poll::Pending;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user