mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-11-03 21:16:16 +01:00 
			
		
		
		
	various improvements to the client (colored song subtext, search/'more', clear queue)
This commit is contained in:
		
							parent
							
								
									68f9747686
								
							
						
					
					
						commit
						adaea9cd64
					
				@ -308,11 +308,11 @@ impl ScrollBoxSizeUnit {
 | 
			
		||||
pub struct Button {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
    pub children: Vec<GuiElem>,
 | 
			
		||||
    action: Arc<dyn Fn(&Self) -> Vec<GuiAction> + 'static>,
 | 
			
		||||
    action: Arc<dyn Fn(&mut Self) -> Vec<GuiAction> + 'static>,
 | 
			
		||||
}
 | 
			
		||||
impl Button {
 | 
			
		||||
    /// automatically adds w_mouse to config
 | 
			
		||||
    pub fn new<F: Fn(&Self) -> Vec<GuiAction> + 'static>(
 | 
			
		||||
    pub fn new<F: Fn(&mut Self) -> Vec<GuiAction> + 'static>(
 | 
			
		||||
        config: GuiElemCfg,
 | 
			
		||||
        action: F,
 | 
			
		||||
        children: Vec<GuiElem>,
 | 
			
		||||
@ -345,7 +345,7 @@ impl GuiElemTrait for Button {
 | 
			
		||||
    }
 | 
			
		||||
    fn mouse_pressed(&mut self, button: MouseButton) -> Vec<GuiAction> {
 | 
			
		||||
        if button == MouseButton::Left {
 | 
			
		||||
            (self.action)(self)
 | 
			
		||||
            (self.action.clone())(self)
 | 
			
		||||
        } else {
 | 
			
		||||
            vec![]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,8 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    rc::Rc,
 | 
			
		||||
    sync::{atomic::AtomicBool, Arc},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use musicdb_lib::data::{database::Database, AlbumId, ArtistId, SongId};
 | 
			
		||||
use regex::{Regex, RegexBuilder};
 | 
			
		||||
use speedy2d::{
 | 
			
		||||
@ -9,7 +14,7 @@ use speedy2d::{
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    gui::{Dragging, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiElemTrait},
 | 
			
		||||
    gui_base::ScrollBox,
 | 
			
		||||
    gui_base::{Button, Panel, ScrollBox},
 | 
			
		||||
    gui_edit::GuiEdit,
 | 
			
		||||
    gui_text::{Label, TextField},
 | 
			
		||||
    gui_wrappers::WithFocusHotkey,
 | 
			
		||||
@ -32,13 +37,20 @@ pub struct LibraryBrowser {
 | 
			
		||||
    search_album_regex: Option<Regex>,
 | 
			
		||||
    search_song: String,
 | 
			
		||||
    search_song_regex: Option<Regex>,
 | 
			
		||||
    filter_target_state: Rc<AtomicBool>,
 | 
			
		||||
    filter_state: f32,
 | 
			
		||||
    search_is_case_sensitive: Rc<AtomicBool>,
 | 
			
		||||
    search_was_case_sensitive: bool,
 | 
			
		||||
}
 | 
			
		||||
fn search_regex_new(pat: &str) -> Result<Regex, regex::Error> {
 | 
			
		||||
fn search_regex_new(pat: &str, case_insensitive: bool) -> Result<Regex, regex::Error> {
 | 
			
		||||
    RegexBuilder::new(pat)
 | 
			
		||||
        .unicode(true)
 | 
			
		||||
        .case_insensitive(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(
 | 
			
		||||
@ -63,10 +75,30 @@ impl LibraryBrowser {
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
        let library_scroll_box = ScrollBox::new(
 | 
			
		||||
            GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.1), (1.0, 1.0))),
 | 
			
		||||
            GuiElemCfg::at(Rectangle::from_tuples((0.0, LP_LIB1), (1.0, LP_LIB2))),
 | 
			
		||||
            crate::gui_base::ScrollBoxSizeUnit::Pixels,
 | 
			
		||||
            vec![],
 | 
			
		||||
        );
 | 
			
		||||
        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),
 | 
			
		||||
            ))],
 | 
			
		||||
        );
 | 
			
		||||
        let search_is_case_sensitive = Rc::new(AtomicBool::new(false));
 | 
			
		||||
        Self {
 | 
			
		||||
            config,
 | 
			
		||||
            children: vec![
 | 
			
		||||
@ -74,6 +106,8 @@ impl LibraryBrowser {
 | 
			
		||||
                GuiElem::new(search_album),
 | 
			
		||||
                GuiElem::new(search_song),
 | 
			
		||||
                GuiElem::new(library_scroll_box),
 | 
			
		||||
                GuiElem::new(filter_button),
 | 
			
		||||
                GuiElem::new(FilterPanel::new(Rc::clone(&search_is_case_sensitive))),
 | 
			
		||||
            ],
 | 
			
		||||
            search_artist: String::new(),
 | 
			
		||||
            search_artist_regex: None,
 | 
			
		||||
@ -81,6 +115,10 @@ impl LibraryBrowser {
 | 
			
		||||
            search_album_regex: None,
 | 
			
		||||
            search_song: String::new(),
 | 
			
		||||
            search_song_regex: None,
 | 
			
		||||
            filter_target_state,
 | 
			
		||||
            filter_state: 0.0,
 | 
			
		||||
            search_is_case_sensitive,
 | 
			
		||||
            search_was_case_sensitive: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -104,16 +142,26 @@ impl GuiElemTrait for LibraryBrowser {
 | 
			
		||||
        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;
 | 
			
		||||
        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 v = &mut self.children[0].try_as_mut::<TextField>().unwrap().children[0]
 | 
			
		||||
                .try_as_mut::<Label>()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .content;
 | 
			
		||||
            if self.search_artist != *v.get_text() {
 | 
			
		||||
            if rebuild_regex || v.will_redraw() && self.search_artist != *v.get_text() {
 | 
			
		||||
                search_changed = true;
 | 
			
		||||
                self.search_artist = v.get_text().clone();
 | 
			
		||||
                self.search_artist_regex = search_regex_new(&self.search_artist).ok();
 | 
			
		||||
                self.search_artist_regex =
 | 
			
		||||
                    search_regex_new(&self.search_artist, !case_sensitive).ok();
 | 
			
		||||
                *v.color() = if self.search_artist_regex.is_some() {
 | 
			
		||||
                    Color::WHITE
 | 
			
		||||
                } else {
 | 
			
		||||
@ -126,10 +174,11 @@ impl GuiElemTrait for LibraryBrowser {
 | 
			
		||||
                .try_as_mut::<Label>()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .content;
 | 
			
		||||
            if self.search_album != *v.get_text() {
 | 
			
		||||
            if rebuild_regex || v.will_redraw() && self.search_album != *v.get_text() {
 | 
			
		||||
                search_changed = true;
 | 
			
		||||
                self.search_album = v.get_text().clone();
 | 
			
		||||
                self.search_album_regex = search_regex_new(&self.search_album).ok();
 | 
			
		||||
                self.search_album_regex =
 | 
			
		||||
                    search_regex_new(&self.search_album, !case_sensitive).ok();
 | 
			
		||||
                *v.color() = if self.search_album_regex.is_some() {
 | 
			
		||||
                    Color::WHITE
 | 
			
		||||
                } else {
 | 
			
		||||
@ -146,10 +195,10 @@ impl GuiElemTrait for LibraryBrowser {
 | 
			
		||||
                .try_as_mut::<Label>()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .content;
 | 
			
		||||
            if self.search_song != *v.get_text() {
 | 
			
		||||
            if rebuild_regex || v.will_redraw() && self.search_song != *v.get_text() {
 | 
			
		||||
                search_changed = true;
 | 
			
		||||
                self.search_song = v.get_text().clone();
 | 
			
		||||
                self.search_song_regex = search_regex_new(&self.search_song).ok();
 | 
			
		||||
                self.search_song_regex = search_regex_new(&self.search_song, !case_sensitive).ok();
 | 
			
		||||
                *v.color() = if self.search_song_regex.is_some() {
 | 
			
		||||
                    Color::WHITE
 | 
			
		||||
                } else {
 | 
			
		||||
@ -157,6 +206,44 @@ impl GuiElemTrait for LibraryBrowser {
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // filter
 | 
			
		||||
        let filter_target_state = self
 | 
			
		||||
            .filter_target_state
 | 
			
		||||
            .load(std::sync::atomic::Ordering::Relaxed);
 | 
			
		||||
        let draw_filter = if filter_target_state && self.filter_state != 1.0 {
 | 
			
		||||
            if let Some(h) = &info.helper {
 | 
			
		||||
                h.request_redraw();
 | 
			
		||||
            }
 | 
			
		||||
            self.filter_state += (1.0 - self.filter_state) * 0.2;
 | 
			
		||||
            if self.filter_state > 0.999 {
 | 
			
		||||
                self.filter_state = 1.0;
 | 
			
		||||
            }
 | 
			
		||||
            true
 | 
			
		||||
        } else if !filter_target_state && self.filter_state != 0.0 {
 | 
			
		||||
            if let Some(h) = &info.helper {
 | 
			
		||||
                h.request_redraw();
 | 
			
		||||
            }
 | 
			
		||||
            self.filter_state *= 0.8;
 | 
			
		||||
            if self.filter_state < 0.001 {
 | 
			
		||||
                self.filter_state = 0.0;
 | 
			
		||||
            }
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        };
 | 
			
		||||
        if draw_filter {
 | 
			
		||||
            let y = LP_LIB1 + (LP_LIB1S - LP_LIB1) * self.filter_state;
 | 
			
		||||
            self.children[3]
 | 
			
		||||
                .try_as_mut::<ScrollBox>()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .config_mut()
 | 
			
		||||
                .pos = Rectangle::new(Vec2::new(0.0, y), Vec2::new(1.0, LP_LIB2));
 | 
			
		||||
            let filter_panel = self.children[5].try_as_mut::<FilterPanel>().unwrap();
 | 
			
		||||
            filter_panel.config_mut().pos =
 | 
			
		||||
                Rectangle::new(Vec2::new(0.0, LP_LIB1), Vec2::new(1.0, y));
 | 
			
		||||
            filter_panel.config.enabled = self.filter_state > 0.0;
 | 
			
		||||
        }
 | 
			
		||||
        // -
 | 
			
		||||
        if self.config.redraw || search_changed || info.pos.size() != self.config.pixel_pos.size() {
 | 
			
		||||
            self.config.redraw = false;
 | 
			
		||||
            self.update_list(&info.database, info.line_height);
 | 
			
		||||
@ -190,6 +277,7 @@ impl LibraryBrowser {
 | 
			
		||||
                    )),
 | 
			
		||||
                    artist_height,
 | 
			
		||||
                ));
 | 
			
		||||
                if self.search_album.is_empty() {
 | 
			
		||||
                    for song_id in &artist.singles {
 | 
			
		||||
                        if let Some(song) = db.songs().get(song_id) {
 | 
			
		||||
                            if self.search_song.is_empty()
 | 
			
		||||
@ -212,6 +300,7 @@ impl LibraryBrowser {
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                for album_id in &artist.albums {
 | 
			
		||||
                    if let Some(album) = db.albums().get(album_id) {
 | 
			
		||||
                        if self.search_album.is_empty()
 | 
			
		||||
@ -545,3 +634,120 @@ impl GuiElemTrait for ListSong {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
struct FilterPanel {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
    children: Vec<GuiElem>,
 | 
			
		||||
    line_height: f32,
 | 
			
		||||
}
 | 
			
		||||
const FP_CASESENS_N: &'static str = "Switch to case-sensitive search";
 | 
			
		||||
const FP_CASESENS_Y: &'static str = "Switch to case-insensitive search";
 | 
			
		||||
impl FilterPanel {
 | 
			
		||||
    pub fn new(search_is_case_sensitive: Rc<AtomicBool>) -> Self {
 | 
			
		||||
        let is_case_sensitive = search_is_case_sensitive.load(std::sync::atomic::Ordering::Relaxed);
 | 
			
		||||
        Self {
 | 
			
		||||
            config: GuiElemCfg::default().disabled(),
 | 
			
		||||
            children: vec![GuiElem::new(ScrollBox::new(
 | 
			
		||||
                GuiElemCfg::default(),
 | 
			
		||||
                crate::gui_base::ScrollBoxSizeUnit::Pixels,
 | 
			
		||||
                vec![
 | 
			
		||||
                    (
 | 
			
		||||
                        GuiElem::new(Button::new(
 | 
			
		||||
                            GuiElemCfg::default(),
 | 
			
		||||
                            move |button| {
 | 
			
		||||
                                let is_case_sensitive = !search_is_case_sensitive
 | 
			
		||||
                                    .load(std::sync::atomic::Ordering::Relaxed);
 | 
			
		||||
                                search_is_case_sensitive
 | 
			
		||||
                                    .store(is_case_sensitive, std::sync::atomic::Ordering::Relaxed);
 | 
			
		||||
                                *button
 | 
			
		||||
                                    .children()
 | 
			
		||||
                                    .next()
 | 
			
		||||
                                    .unwrap()
 | 
			
		||||
                                    .try_as_mut::<Label>()
 | 
			
		||||
                                    .unwrap()
 | 
			
		||||
                                    .content
 | 
			
		||||
                                    .text() = if is_case_sensitive {
 | 
			
		||||
                                    FP_CASESENS_Y.to_owned()
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    FP_CASESENS_N.to_owned()
 | 
			
		||||
                                };
 | 
			
		||||
                                vec![]
 | 
			
		||||
                            },
 | 
			
		||||
                            vec![GuiElem::new(Label::new(
 | 
			
		||||
                                GuiElemCfg::default(),
 | 
			
		||||
                                if is_case_sensitive {
 | 
			
		||||
                                    FP_CASESENS_Y.to_owned()
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    FP_CASESENS_N.to_owned()
 | 
			
		||||
                                },
 | 
			
		||||
                                Color::GRAY,
 | 
			
		||||
                                None,
 | 
			
		||||
                                Vec2::new(0.5, 0.5),
 | 
			
		||||
                            ))],
 | 
			
		||||
                        )),
 | 
			
		||||
                        1.0,
 | 
			
		||||
                    ),
 | 
			
		||||
                    (
 | 
			
		||||
                        GuiElem::new(Button::new(
 | 
			
		||||
                            GuiElemCfg::default(),
 | 
			
		||||
                            |button| {
 | 
			
		||||
                                let text = button
 | 
			
		||||
                                    .children()
 | 
			
		||||
                                    .next()
 | 
			
		||||
                                    .unwrap()
 | 
			
		||||
                                    .try_as_mut::<Label>()
 | 
			
		||||
                                    .unwrap()
 | 
			
		||||
                                    .content
 | 
			
		||||
                                    .text();
 | 
			
		||||
                                *text = if text.len() > 20 {
 | 
			
		||||
                                    "Click for RegEx help".to_owned()
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    "Click to close RegEx help\ntest\nyay".to_owned()
 | 
			
		||||
                                };
 | 
			
		||||
                                vec![]
 | 
			
		||||
                            },
 | 
			
		||||
                            vec![GuiElem::new(Label::new(
 | 
			
		||||
                                GuiElemCfg::default(),
 | 
			
		||||
                                "Click for RegEx help".to_owned(),
 | 
			
		||||
                                Color::GRAY,
 | 
			
		||||
                                None,
 | 
			
		||||
                                Vec2::new(0.5, 0.0),
 | 
			
		||||
                            ))],
 | 
			
		||||
                        )),
 | 
			
		||||
                        1.0,
 | 
			
		||||
                    ),
 | 
			
		||||
                ],
 | 
			
		||||
            ))],
 | 
			
		||||
            line_height: 0.0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl GuiElemTrait for FilterPanel {
 | 
			
		||||
    fn draw(&mut self, info: &mut DrawInfo, _g: &mut speedy2d::Graphics2D) {
 | 
			
		||||
        if info.line_height != self.line_height {
 | 
			
		||||
            for (_, h) in &mut self.children[0].try_as_mut::<ScrollBox>().unwrap().children {
 | 
			
		||||
                *h = info.line_height;
 | 
			
		||||
            }
 | 
			
		||||
            self.line_height = info.line_height;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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.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<dyn GuiElemTrait> {
 | 
			
		||||
        Box::new(self.clone())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ use speedy2d::{color::Color, dimen::Vec2, shape::Rectangle, window::MouseButton}
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    gui::{adjust_area, adjust_pos, GuiAction, GuiCover, GuiElem, GuiElemCfg, GuiElemTrait},
 | 
			
		||||
    gui_text::Label,
 | 
			
		||||
    gui_text::{AdvancedLabel, Content, Label},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@ -25,6 +25,7 @@ pub struct CurrentSong {
 | 
			
		||||
    prev_song: Option<SongId>,
 | 
			
		||||
    cover_pos: Rectangle,
 | 
			
		||||
    covers: VecDeque<(CoverId, Option<(bool, Instant)>)>,
 | 
			
		||||
    text_updated: Option<Instant>,
 | 
			
		||||
}
 | 
			
		||||
impl CurrentSong {
 | 
			
		||||
    pub fn new(config: GuiElemCfg) -> Self {
 | 
			
		||||
@ -38,19 +39,39 @@ impl CurrentSong {
 | 
			
		||||
                    None,
 | 
			
		||||
                    Vec2::new(0.0, 1.0),
 | 
			
		||||
                )),
 | 
			
		||||
                GuiElem::new(Label::new(
 | 
			
		||||
                GuiElem::new(AdvancedLabel::new(
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.5), (1.0, 1.0))),
 | 
			
		||||
                    "".to_owned(),
 | 
			
		||||
                    Color::from_int_rgb(120, 120, 120),
 | 
			
		||||
                    None,
 | 
			
		||||
                    Vec2::new(0.0, 0.0),
 | 
			
		||||
                    vec![],
 | 
			
		||||
                )),
 | 
			
		||||
            ],
 | 
			
		||||
            cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
 | 
			
		||||
            covers: VecDeque::new(),
 | 
			
		||||
            prev_song: None,
 | 
			
		||||
            text_updated: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set_idle_mode(&mut self, idle_mode: f32) {
 | 
			
		||||
        // ...
 | 
			
		||||
    }
 | 
			
		||||
    fn color_title(a: f32) -> Color {
 | 
			
		||||
        Self::color_with_alpha(&Color::WHITE, a)
 | 
			
		||||
    }
 | 
			
		||||
    fn color_artist(a: f32) -> Color {
 | 
			
		||||
        Color::from_rgba(0.32, 0.20, 0.49, a)
 | 
			
		||||
    }
 | 
			
		||||
    fn color_album(a: f32) -> Color {
 | 
			
		||||
        Color::from_rgba(0.03, 0.24, 0.18, a)
 | 
			
		||||
    }
 | 
			
		||||
    fn color_by(a: f32) -> Color {
 | 
			
		||||
        Self::color_with_alpha(&Color::DARK_GRAY, a)
 | 
			
		||||
    }
 | 
			
		||||
    fn color_on(a: f32) -> Color {
 | 
			
		||||
        Self::color_with_alpha(&Color::DARK_GRAY, a)
 | 
			
		||||
    }
 | 
			
		||||
    fn color_with_alpha(c: &Color, a: f32) -> Color {
 | 
			
		||||
        Color::from_rgba(c.r(), c.g(), c.b(), a)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl GuiElemTrait for CurrentSong {
 | 
			
		||||
    fn config(&self) -> &GuiElemCfg {
 | 
			
		||||
@ -127,39 +148,130 @@ impl GuiElemTrait for CurrentSong {
 | 
			
		||||
                                .as_ref()
 | 
			
		||||
                                .and_then(|id| info.database.albums().get(id)),
 | 
			
		||||
                        ) {
 | 
			
		||||
                            (None, None) => String::new(),
 | 
			
		||||
                            (Some(artist), None) => format!("by {}", artist.name),
 | 
			
		||||
                            (None, Some(album)) => {
 | 
			
		||||
                                if let Some(artist) = info.database.artists().get(&album.artist) {
 | 
			
		||||
                                    format!("on {} by {}", album.name, artist.name)
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    format!("on {}", album.name)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            (Some(artist), Some(album)) => {
 | 
			
		||||
                                format!("by {} on {}", artist.name, album.name)
 | 
			
		||||
                            }
 | 
			
		||||
                            (None, None) => vec![],
 | 
			
		||||
                            (Some(artist), None) => vec![
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new("by ".to_owned(), Self::color_by(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new(artist.name.to_owned(), Self::color_artist(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                            ],
 | 
			
		||||
                            (None, Some(album)) => vec![
 | 
			
		||||
                                (Content::new(String::new(), Color::TRANSPARENT), 0.0, 1.0),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new("on ".to_owned(), Self::color_on(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new(album.name.to_owned(), Self::color_album(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                            ],
 | 
			
		||||
                            (Some(artist), Some(album)) => vec![
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new("by ".to_owned(), Self::color_by(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new(
 | 
			
		||||
                                        format!("{} ", artist.name),
 | 
			
		||||
                                        Self::color_artist(0.0),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new("on ".to_owned(), Self::color_on(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                                (
 | 
			
		||||
                                    Content::new(album.name.to_owned(), Self::color_album(0.0)),
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                    1.0,
 | 
			
		||||
                                ),
 | 
			
		||||
                            ],
 | 
			
		||||
                        };
 | 
			
		||||
                        (song.title.clone(), sub)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        (
 | 
			
		||||
                            "< song not in db >".to_owned(),
 | 
			
		||||
                            "maybe restart the client to resync the database?".to_owned(),
 | 
			
		||||
                            vec![(
 | 
			
		||||
                                Content::new(
 | 
			
		||||
                                    "you may need to restart the client to resync the database"
 | 
			
		||||
                                        .to_owned(),
 | 
			
		||||
                                    Color::from_rgb(0.8, 0.5, 0.5),
 | 
			
		||||
                                ),
 | 
			
		||||
                                1.0,
 | 
			
		||||
                                1.0,
 | 
			
		||||
                            )],
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    (String::new(), String::new())
 | 
			
		||||
                    (String::new(), vec![])
 | 
			
		||||
                };
 | 
			
		||||
                *self.children[0]
 | 
			
		||||
                    .try_as_mut::<Label>()
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .content
 | 
			
		||||
                    .text() = name;
 | 
			
		||||
                *self.children[1]
 | 
			
		||||
                self.children[1]
 | 
			
		||||
                    .try_as_mut::<AdvancedLabel>()
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .content = subtext;
 | 
			
		||||
                self.text_updated = Some(Instant::now());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(updated) = &self.text_updated {
 | 
			
		||||
            if let Some(h) = &info.helper {
 | 
			
		||||
                h.request_redraw();
 | 
			
		||||
            }
 | 
			
		||||
            let prog = updated.elapsed().as_secs_f32();
 | 
			
		||||
            *self.children[0]
 | 
			
		||||
                .try_as_mut::<Label>()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .content
 | 
			
		||||
                    .text() = subtext;
 | 
			
		||||
                .color() = Self::color_title((prog / 1.5).min(1.0));
 | 
			
		||||
            let subtext = self.children[1].try_as_mut::<AdvancedLabel>().unwrap();
 | 
			
		||||
            match subtext.content.len() {
 | 
			
		||||
                2 => {
 | 
			
		||||
                    *subtext.content[0].0.color() = Self::color_by(prog.min(1.0));
 | 
			
		||||
                    *subtext.content[1].0.color() =
 | 
			
		||||
                        Self::color_artist((prog.max(0.5) - 0.5).min(1.0));
 | 
			
		||||
                    if prog >= 1.5 {
 | 
			
		||||
                        self.text_updated = None;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                3 => {
 | 
			
		||||
                    *subtext.content[0].0.color() = Self::color_on(prog.min(1.0));
 | 
			
		||||
                    *subtext.content[1].0.color() =
 | 
			
		||||
                        Self::color_album((prog.max(0.5) - 0.5).min(1.0));
 | 
			
		||||
                    if prog >= 1.5 {
 | 
			
		||||
                        self.text_updated = None;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                4 => {
 | 
			
		||||
                    *subtext.content[0].0.color() = Self::color_by(prog.min(1.0));
 | 
			
		||||
                    *subtext.content[1].0.color() =
 | 
			
		||||
                        Self::color_artist((prog.max(0.5) - 0.5).min(1.0));
 | 
			
		||||
                    *subtext.content[2].0.color() = Self::color_on((prog.max(1.0) - 1.0).min(1.0));
 | 
			
		||||
                    *subtext.content[3].0.color() =
 | 
			
		||||
                        Self::color_album((prog.max(1.5) - 1.5).min(1.0));
 | 
			
		||||
                    if prog >= 2.5 {
 | 
			
		||||
                        self.text_updated = None;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => {
 | 
			
		||||
                    self.text_updated = None;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // drawing stuff
 | 
			
		||||
 | 
			
		||||
@ -36,13 +36,17 @@ pub struct QueueViewer {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
    children: Vec<GuiElem>,
 | 
			
		||||
}
 | 
			
		||||
const QP_QUEUE1: f32 = 0.0;
 | 
			
		||||
const QP_QUEUE2: f32 = 0.95;
 | 
			
		||||
const QP_INV1: f32 = QP_QUEUE2;
 | 
			
		||||
const QP_INV2: f32 = 1.0;
 | 
			
		||||
impl QueueViewer {
 | 
			
		||||
    pub fn new(config: GuiElemCfg) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            config,
 | 
			
		||||
            children: vec![
 | 
			
		||||
                GuiElem::new(ScrollBox::new(
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0665), (1.0, 1.0))),
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2))),
 | 
			
		||||
                    crate::gui_base::ScrollBoxSizeUnit::Pixels,
 | 
			
		||||
                    vec![(
 | 
			
		||||
                        GuiElem::new(Label::new(
 | 
			
		||||
@ -56,10 +60,10 @@ impl QueueViewer {
 | 
			
		||||
                    )],
 | 
			
		||||
                )),
 | 
			
		||||
                GuiElem::new(QueueEmptySpaceDragHandler::new(GuiElemCfg::at(
 | 
			
		||||
                    Rectangle::from_tuples((0.0, 0.0665), (1.0, 1.0)),
 | 
			
		||||
                    Rectangle::from_tuples((0.0, QP_QUEUE1), (1.0, QP_QUEUE2)),
 | 
			
		||||
                ))),
 | 
			
		||||
                GuiElem::new(Panel::new(
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.0665))),
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, QP_INV1), (1.0, QP_INV2))),
 | 
			
		||||
                    vec![
 | 
			
		||||
                        GuiElem::new(
 | 
			
		||||
                            QueueLoop::new(
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ impl GuiScreen {
 | 
			
		||||
        scroll_sensitivity_pages: f64,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            config: config.w_keyboard_watch(),
 | 
			
		||||
            config: config.w_keyboard_watch().w_mouse(),
 | 
			
		||||
            children: vec![
 | 
			
		||||
                GuiElem::new(StatusBar::new(
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.9), (1.0, 1.0))),
 | 
			
		||||
@ -95,7 +95,7 @@ impl GuiScreen {
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.0, 0.0), (1.0, 0.9))),
 | 
			
		||||
                    vec![
 | 
			
		||||
                        GuiElem::new(Button::new(
 | 
			
		||||
                            GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))),
 | 
			
		||||
                            GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (0.875, 0.03))),
 | 
			
		||||
                            |_| vec![GuiAction::OpenSettings(true)],
 | 
			
		||||
                            vec![GuiElem::new(Label::new(
 | 
			
		||||
                                GuiElemCfg::default(),
 | 
			
		||||
@ -106,7 +106,7 @@ impl GuiScreen {
 | 
			
		||||
                            ))],
 | 
			
		||||
                        )),
 | 
			
		||||
                        GuiElem::new(Button::new(
 | 
			
		||||
                            GuiElemCfg::at(Rectangle::from_tuples((0.75, 0.0), (1.0, 0.03))),
 | 
			
		||||
                            GuiElemCfg::at(Rectangle::from_tuples((0.875, 0.0), (1.0, 0.03))),
 | 
			
		||||
                            |_| vec![GuiAction::Exit],
 | 
			
		||||
                            vec![GuiElem::new(Label::new(
 | 
			
		||||
                                GuiElemCfg::default(),
 | 
			
		||||
@ -124,6 +124,29 @@ impl GuiScreen {
 | 
			
		||||
                            (0.5, 0.03),
 | 
			
		||||
                            (1.0, 1.0),
 | 
			
		||||
                        )))),
 | 
			
		||||
                        GuiElem::new(Button::new(
 | 
			
		||||
                            GuiElemCfg::at(Rectangle::from_tuples((0.5, 0.0), (0.75, 0.03))),
 | 
			
		||||
                            |_| {
 | 
			
		||||
                                vec![GuiAction::SendToServer(
 | 
			
		||||
                                    musicdb_lib::server::Command::QueueUpdate(
 | 
			
		||||
                                        vec![],
 | 
			
		||||
                                        musicdb_lib::data::queue::QueueContent::Folder(
 | 
			
		||||
                                            0,
 | 
			
		||||
                                            vec![],
 | 
			
		||||
                                            String::new(),
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .into(),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )]
 | 
			
		||||
                            },
 | 
			
		||||
                            vec![GuiElem::new(Label::new(
 | 
			
		||||
                                GuiElemCfg::default(),
 | 
			
		||||
                                "Clear Queue".to_string(),
 | 
			
		||||
                                Color::WHITE,
 | 
			
		||||
                                None,
 | 
			
		||||
                                Vec2::new(0.5, 0.5),
 | 
			
		||||
                            ))],
 | 
			
		||||
                        )),
 | 
			
		||||
                    ],
 | 
			
		||||
                )),
 | 
			
		||||
            ],
 | 
			
		||||
@ -203,7 +226,11 @@ impl GuiElemTrait for GuiScreen {
 | 
			
		||||
        self.not_idle();
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
    fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
 | 
			
		||||
    fn mouse_down(&mut self, _button: speedy2d::window::MouseButton) -> Vec<GuiAction> {
 | 
			
		||||
        self.not_idle();
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
    fn draw(&mut self, info: &mut DrawInfo, _g: &mut Graphics2D) {
 | 
			
		||||
        // idle stuff
 | 
			
		||||
        if self.prev_mouse_pos != info.mouse_pos {
 | 
			
		||||
            self.prev_mouse_pos = info.mouse_pos;
 | 
			
		||||
@ -289,6 +316,7 @@ pub struct StatusBar {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
    children: Vec<GuiElem>,
 | 
			
		||||
    idle_mode: f32,
 | 
			
		||||
    idle_prev: f32,
 | 
			
		||||
}
 | 
			
		||||
impl StatusBar {
 | 
			
		||||
    pub fn new(config: GuiElemCfg, playing: bool) -> Self {
 | 
			
		||||
@ -301,17 +329,31 @@ impl StatusBar {
 | 
			
		||||
                )))),
 | 
			
		||||
                GuiElem::new(PlayPauseToggle::new(
 | 
			
		||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.85, 0.0), (0.95, 1.0))),
 | 
			
		||||
                    false,
 | 
			
		||||
                )),
 | 
			
		||||
                GuiElem::new(Panel::with_background(
 | 
			
		||||
                    GuiElemCfg::default(),
 | 
			
		||||
                    vec![],
 | 
			
		||||
                    Color::BLACK,
 | 
			
		||||
                    playing,
 | 
			
		||||
                )),
 | 
			
		||||
                GuiElem::new(Panel::new(GuiElemCfg::default(), vec![])),
 | 
			
		||||
            ],
 | 
			
		||||
            idle_mode: 0.0,
 | 
			
		||||
            idle_prev: 0.0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const fn index_current_song() -> usize {
 | 
			
		||||
        0
 | 
			
		||||
    }
 | 
			
		||||
    const fn index_play_pause_toggle() -> usize {
 | 
			
		||||
        1
 | 
			
		||||
    }
 | 
			
		||||
    const fn index_bgpanel() -> usize {
 | 
			
		||||
        2
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set_background(&mut self, bg: Option<Color>) {
 | 
			
		||||
        self.children[Self::index_bgpanel()]
 | 
			
		||||
            .inner
 | 
			
		||||
            .any_mut()
 | 
			
		||||
            .downcast_mut::<Panel>()
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .background = bg;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl GuiElemTrait for StatusBar {
 | 
			
		||||
    fn config(&self) -> &GuiElemCfg {
 | 
			
		||||
@ -333,6 +375,8 @@ impl GuiElemTrait for StatusBar {
 | 
			
		||||
        Box::new(self.clone())
 | 
			
		||||
    }
 | 
			
		||||
    fn draw(&mut self, info: &mut DrawInfo, g: &mut Graphics2D) {
 | 
			
		||||
        // the line that separates this section from the rest of the ui.
 | 
			
		||||
        // fades away when idle_mode approaches 1.0
 | 
			
		||||
        if self.idle_mode < 1.0 {
 | 
			
		||||
            g.draw_line(
 | 
			
		||||
                info.pos.top_left(),
 | 
			
		||||
@ -341,5 +385,28 @@ impl GuiElemTrait for StatusBar {
 | 
			
		||||
                Color::from_rgba(1.0, 1.0, 1.0, 1.0 - self.idle_mode),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        if self.idle_mode != self.idle_prev {
 | 
			
		||||
            // if exiting the moving stage, set background to transparent.
 | 
			
		||||
            // if entering the moving stage, set background to black.
 | 
			
		||||
            if self.idle_mode == 1.0 || self.idle_mode == 0.0 {
 | 
			
		||||
                self.set_background(None);
 | 
			
		||||
            } else if self.idle_prev == 1.0 || self.idle_prev == 0.0 {
 | 
			
		||||
                self.set_background(Some(Color::BLACK));
 | 
			
		||||
            }
 | 
			
		||||
            // position the text
 | 
			
		||||
            let current_song = self.children[Self::index_current_song()]
 | 
			
		||||
                .inner
 | 
			
		||||
                .any_mut()
 | 
			
		||||
                .downcast_mut::<CurrentSong>()
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            current_song.set_idle_mode(self.idle_mode);
 | 
			
		||||
            let play_pause = self.children[Self::index_play_pause_toggle()]
 | 
			
		||||
                .inner
 | 
			
		||||
                .any_mut()
 | 
			
		||||
                .downcast_mut::<PlayPauseToggle>()
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            // - - - - -
 | 
			
		||||
            self.idle_prev = self.idle_mode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,14 @@ pub struct Content {
 | 
			
		||||
    formatted: Option<Rc<FormattedTextBlock>>,
 | 
			
		||||
}
 | 
			
		||||
impl Content {
 | 
			
		||||
    pub fn new(text: String, color: Color) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            text,
 | 
			
		||||
            color,
 | 
			
		||||
            background: None,
 | 
			
		||||
            formatted: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_text(&self) -> &String {
 | 
			
		||||
        &self.text
 | 
			
		||||
    }
 | 
			
		||||
@ -129,7 +137,7 @@ impl GuiElemTrait for Label {
 | 
			
		||||
 | 
			
		||||
// TODO! this, but requires keyboard events first
 | 
			
		||||
 | 
			
		||||
/// a single-line text fields for users to type text into.
 | 
			
		||||
/// a single-line text field for users to type text into.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct TextField {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
@ -247,3 +255,108 @@ impl GuiElemTrait for TextField {
 | 
			
		||||
        vec![]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// More advanced version of `Label`.
 | 
			
		||||
/// Allows stringing together multiple `Content`s in one line.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct AdvancedLabel {
 | 
			
		||||
    config: GuiElemCfg,
 | 
			
		||||
    children: Vec<GuiElem>,
 | 
			
		||||
    /// 0.0 => align to top/left
 | 
			
		||||
    /// 0.5 => center
 | 
			
		||||
    /// 1.0 => align to bottom/right
 | 
			
		||||
    pub align: Vec2,
 | 
			
		||||
    /// (Content, Size-Scale, Height)
 | 
			
		||||
    /// Size-Scale and Height should default to 1.0.
 | 
			
		||||
    pub content: Vec<(Content, f32, f32)>,
 | 
			
		||||
    /// the position from where content drawing starts.
 | 
			
		||||
    /// recalculated when layouting is performed.
 | 
			
		||||
    content_pos: Vec2,
 | 
			
		||||
    content_height: f32,
 | 
			
		||||
}
 | 
			
		||||
impl AdvancedLabel {
 | 
			
		||||
    pub fn new(config: GuiElemCfg, align: Vec2, content: Vec<(Content, f32, f32)>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            config,
 | 
			
		||||
            children: vec![],
 | 
			
		||||
            align,
 | 
			
		||||
            content,
 | 
			
		||||
            content_pos: Vec2::ZERO,
 | 
			
		||||
            content_height: 0.0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl GuiElemTrait for AdvancedLabel {
 | 
			
		||||
    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.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<dyn GuiElemTrait> {
 | 
			
		||||
        Box::new(self.clone())
 | 
			
		||||
    }
 | 
			
		||||
    fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
 | 
			
		||||
        if self.config.redraw
 | 
			
		||||
            || self.config.pixel_pos.size() != info.pos.size()
 | 
			
		||||
            || self.content.iter().any(|(c, _, _)| c.will_redraw())
 | 
			
		||||
        {
 | 
			
		||||
            self.config.redraw = false;
 | 
			
		||||
            let mut len = 0.0;
 | 
			
		||||
            let mut height = 0.0;
 | 
			
		||||
            for (c, scale, _) in &self.content {
 | 
			
		||||
                let mut size = info
 | 
			
		||||
                    .font
 | 
			
		||||
                    .layout_text(&c.text, 1.0, TextOptions::new())
 | 
			
		||||
                    .size();
 | 
			
		||||
                len += size.x * scale;
 | 
			
		||||
                if size.y * scale > height {
 | 
			
		||||
                    height = size.y * scale;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if len > 0.0 && height > 0.0 {
 | 
			
		||||
                let scale1 = info.pos.width() / len;
 | 
			
		||||
                let scale2 = info.pos.height() / height;
 | 
			
		||||
                let scale;
 | 
			
		||||
                self.content_pos = if scale1 < scale2 {
 | 
			
		||||
                    // use all available width
 | 
			
		||||
                    scale = scale1;
 | 
			
		||||
                    self.content_height = height * scale;
 | 
			
		||||
                    let pad = info.pos.height() - self.content_height;
 | 
			
		||||
                    Vec2::new(0.0, pad * self.align.y)
 | 
			
		||||
                } else {
 | 
			
		||||
                    // use all available height
 | 
			
		||||
                    scale = scale2;
 | 
			
		||||
                    self.content_height = info.pos.height();
 | 
			
		||||
                    let pad = info.pos.width() - len * scale;
 | 
			
		||||
                    Vec2::new(pad * self.align.x, 0.0)
 | 
			
		||||
                };
 | 
			
		||||
                for (c, s, _) in &mut self.content {
 | 
			
		||||
                    c.formatted = Some(info.font.layout_text(
 | 
			
		||||
                        &c.text,
 | 
			
		||||
                        scale * (*s),
 | 
			
		||||
                        TextOptions::new(),
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        let pos_y = info.pos.top_left().y + self.content_pos.y;
 | 
			
		||||
        let mut pos_x = info.pos.top_left().x + self.content_pos.x;
 | 
			
		||||
        for (c, _, h) in &self.content {
 | 
			
		||||
            if let Some(f) = &c.formatted {
 | 
			
		||||
                let y = pos_y + (self.content_height - f.height()) * h;
 | 
			
		||||
                g.draw_text(Vec2::new(pos_x, y), c.color, f);
 | 
			
		||||
                pos_x += f.width();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -140,12 +140,18 @@ fn main() {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    eprintln!("searching for covers...");
 | 
			
		||||
    let mut multiple_cover_options = vec![];
 | 
			
		||||
    let mut single_images = HashMap::new();
 | 
			
		||||
    for (i1, (_artist, (artist_id, albums))) in artists.iter().enumerate() {
 | 
			
		||||
        eprint!("\rartist {}/{}", i1 + 1, artists.len());
 | 
			
		||||
        for (_album, (album_id, album_dir)) in albums {
 | 
			
		||||
            if let Some(album_dir) = album_dir {
 | 
			
		||||
                if let Some(cover_id) = get_cover(&mut database, &lib_dir, album_dir) {
 | 
			
		||||
                if let Some(cover_id) = get_cover(
 | 
			
		||||
                    &mut database,
 | 
			
		||||
                    &lib_dir,
 | 
			
		||||
                    album_dir,
 | 
			
		||||
                    &mut multiple_cover_options,
 | 
			
		||||
                ) {
 | 
			
		||||
                    database.albums_mut().get_mut(album_id).unwrap().cover = Some(cover_id);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -158,7 +164,9 @@ fn main() {
 | 
			
		||||
                {
 | 
			
		||||
                    let cover_id = if let Some(cover_id) = single_images.get(dir) {
 | 
			
		||||
                        Some(*cover_id)
 | 
			
		||||
                    } else if let Some(cover_id) = get_cover(&mut database, &lib_dir, dir) {
 | 
			
		||||
                    } else if let Some(cover_id) =
 | 
			
		||||
                        get_cover(&mut database, &lib_dir, dir, &mut multiple_cover_options)
 | 
			
		||||
                    {
 | 
			
		||||
                        single_images.insert(dir.to_owned(), cover_id);
 | 
			
		||||
                        Some(cover_id)
 | 
			
		||||
                    } else {
 | 
			
		||||
@ -171,6 +179,13 @@ fn main() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    eprintln!();
 | 
			
		||||
    if !multiple_cover_options.is_empty() {
 | 
			
		||||
        eprintln!("> Found more than one cover in the following directories: ");
 | 
			
		||||
        for dir in multiple_cover_options {
 | 
			
		||||
            eprintln!(">> {}", dir.to_string_lossy());
 | 
			
		||||
        }
 | 
			
		||||
        eprintln!("> Default behavior is using the largest image file found.");
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(uka) = database.artists().get(&unknown_artist) {
 | 
			
		||||
        if uka.albums.is_empty() && uka.singles.is_empty() {
 | 
			
		||||
            database.artists_mut().remove(&unknown_artist);
 | 
			
		||||
@ -212,9 +227,15 @@ impl OnceNewline {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_cover(database: &mut Database, lib_dir: &str, abs_dir: impl AsRef<Path>) -> Option<CoverId> {
 | 
			
		||||
fn get_cover(
 | 
			
		||||
    database: &mut Database,
 | 
			
		||||
    lib_dir: &str,
 | 
			
		||||
    abs_dir: impl AsRef<Path>,
 | 
			
		||||
    multiple_options_list: &mut Vec<PathBuf>,
 | 
			
		||||
) -> Option<CoverId> {
 | 
			
		||||
    let mut multiple = false;
 | 
			
		||||
    let mut cover = None;
 | 
			
		||||
    if let Ok(files) = fs::read_dir(abs_dir) {
 | 
			
		||||
    if let Ok(files) = fs::read_dir(&abs_dir) {
 | 
			
		||||
        for file in files {
 | 
			
		||||
            if let Ok(file) = file {
 | 
			
		||||
                if let Ok(metadata) = file.metadata() {
 | 
			
		||||
@ -228,6 +249,9 @@ fn get_cover(database: &mut Database, lib_dir: &str, abs_dir: impl AsRef<Path>)
 | 
			
		||||
                                    .as_ref()
 | 
			
		||||
                                    .is_some_and(|(_, size)| *size < metadata.len())
 | 
			
		||||
                            {
 | 
			
		||||
                                if cover.is_some() {
 | 
			
		||||
                                    multiple = true;
 | 
			
		||||
                                }
 | 
			
		||||
                                cover = Some((path, metadata.len()));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@ -236,6 +260,9 @@ fn get_cover(database: &mut Database, lib_dir: &str, abs_dir: impl AsRef<Path>)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if multiple {
 | 
			
		||||
        multiple_options_list.push(abs_dir.as_ref().to_path_buf());
 | 
			
		||||
    }
 | 
			
		||||
    if let Some((path, _)) = cover {
 | 
			
		||||
        let rel_path = path.strip_prefix(&lib_dir).unwrap().to_path_buf();
 | 
			
		||||
        Some(database.add_cover_new(Cover {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user