mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-11-04 05:16:17 +01:00 
			
		
		
		
	tags can now be used
filldb: Year and Genre will be read from MP3/ID3 tags and added as Tags.
client: Tags can (and will) be displayed in the status bar.
client: StatusBar text can be configured via ~/.config/musicdb-client/config_gui.toml
client: Added a proper default config file at src/config_gui.toml
        (note: this is in src/ so that include_bytes! can use a path without /, so it will compile on windows)
client: users will need to add the new `[text]` section to their gui_config.toml!
			
			
This commit is contained in:
		
							parent
							
								
									c6b75180bb
								
							
						
					
					
						commit
						f429f17876
					
				
							
								
								
									
										32
									
								
								musicdb-client/src/config_gui.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								musicdb-client/src/config_gui.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					font = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[text]
 | 
				
			||||||
 | 
					# define the text displayed in the application.
 | 
				
			||||||
 | 
					# escape sequences:
 | 
				
			||||||
 | 
					# \t: song title
 | 
				
			||||||
 | 
					# \a: album name
 | 
				
			||||||
 | 
					# \A: artist name
 | 
				
			||||||
 | 
					# \s1.0;: set the scale (to the default: 1.0)
 | 
				
			||||||
 | 
					# \h1.0;: set the height-alignment (to the default: 1.0 / align text to be on one baseline)
 | 
				
			||||||
 | 
					# \cRRGGBB: set color to this hex value.
 | 
				
			||||||
 | 
					# \cFFFFFF: default color (white)
 | 
				
			||||||
 | 
					# \<char>: <char> (\\ => \, \# => #, \% => %, ...)
 | 
				
			||||||
 | 
					# custom properties:
 | 
				
			||||||
 | 
					# %<mode><search text>%
 | 
				
			||||||
 | 
					# %_word% returns the first property that includes "word"
 | 
				
			||||||
 | 
					# %>Year=% returns the end of the first property that starts with "Year=",
 | 
				
			||||||
 | 
					#          so if a song has "Year=2019", this would return "2019".
 | 
				
			||||||
 | 
					# %=Value% returns something if a property "Value" is found.
 | 
				
			||||||
 | 
					# IF:
 | 
				
			||||||
 | 
					# ?<condition>#<then>#<else>#
 | 
				
			||||||
 | 
					# If <condition> is not empty, the entire block will be replaced by the value generated by <then>.
 | 
				
			||||||
 | 
					# If <condition> is empty, the entire block will be replaced by the value generated by <else>.
 | 
				
			||||||
 | 
					# Examples:
 | 
				
			||||||
 | 
					# ?\A#by \A##
 | 
				
			||||||
 | 
					# If we know the artist's name, write "by " followed by the name,
 | 
				
			||||||
 | 
					# if not, don't write anything (## -> <else> is empty)
 | 
				
			||||||
 | 
					# ?\t#\t#(no title found)#
 | 
				
			||||||
 | 
					# If we know the title, write it. If not, write "(no title found)" instead.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					status_bar = '''\t
 | 
				
			||||||
 | 
					\s0.5;?\A#\c505050by \c593D6E\A##?\a#?\A# ##\c505050on \c264524\a##?%>Year=%#\c808080 (%>Year=%)##'''
 | 
				
			||||||
@ -28,7 +28,7 @@ use speedy2d::{
 | 
				
			|||||||
    Graphics2D,
 | 
					    Graphics2D,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{gui_screen::GuiScreen, gui_wrappers::WithFocusHotkey};
 | 
					use crate::{gui_screen::GuiScreen, gui_wrappers::WithFocusHotkey, textcfg};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum GuiEvent {
 | 
					pub enum GuiEvent {
 | 
				
			||||||
    Refresh,
 | 
					    Refresh,
 | 
				
			||||||
@ -50,6 +50,7 @@ pub fn main(
 | 
				
			|||||||
    let mut scroll_pixels_multiplier = 1.0;
 | 
					    let mut scroll_pixels_multiplier = 1.0;
 | 
				
			||||||
    let mut scroll_lines_multiplier = 3.0;
 | 
					    let mut scroll_lines_multiplier = 3.0;
 | 
				
			||||||
    let mut scroll_pages_multiplier = 0.75;
 | 
					    let mut scroll_pages_multiplier = 0.75;
 | 
				
			||||||
 | 
					    let status_bar_text;
 | 
				
			||||||
    match std::fs::read_to_string(&config_file) {
 | 
					    match std::fs::read_to_string(&config_file) {
 | 
				
			||||||
        Ok(cfg) => {
 | 
					        Ok(cfg) => {
 | 
				
			||||||
            if let Ok(table) = cfg.parse::<toml::Table>() {
 | 
					            if let Ok(table) = cfg.parse::<toml::Table>() {
 | 
				
			||||||
@ -85,8 +86,26 @@ pub fn main(
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    scroll_pages_multiplier = v;
 | 
					                    scroll_pages_multiplier = v;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                if let Some(t) = table.get("text").and_then(|v| v.as_table()) {
 | 
				
			||||||
 | 
					                    if let Some(v) = t.get("status_bar").and_then(|v| v.as_str()) {
 | 
				
			||||||
 | 
					                        match v.parse() {
 | 
				
			||||||
 | 
					                            Ok(v) => status_bar_text = v,
 | 
				
			||||||
 | 
					                            Err(e) => {
 | 
				
			||||||
 | 
					                                eprintln!("[toml] `text.status_bar couldn't be parsed: {e}`");
 | 
				
			||||||
 | 
					                                std::process::exit(30);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        eprintln!("[toml] missing the required `text.status_bar` string value.");
 | 
				
			||||||
 | 
					                        std::process::exit(30);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    eprintln!("[toml] missing the required `[text]` section!");
 | 
				
			||||||
 | 
					                    std::process::exit(30);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                eprintln!("Couldn't parse config file {config_file:?} as toml!");
 | 
					                eprintln!("Couldn't parse config file {config_file:?} as toml!");
 | 
				
			||||||
 | 
					                std::process::exit(30);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(e) => {
 | 
					        Err(e) => {
 | 
				
			||||||
@ -94,7 +113,9 @@ pub fn main(
 | 
				
			|||||||
            if let Some(p) = config_file.parent() {
 | 
					            if let Some(p) = config_file.parent() {
 | 
				
			||||||
                _ = std::fs::create_dir_all(p);
 | 
					                _ = std::fs::create_dir_all(p);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _ = std::fs::write(&config_file, "font = ''");
 | 
					            if std::fs::write(&config_file, include_bytes!("config_gui.toml")).is_ok() {
 | 
				
			||||||
 | 
					                eprintln!("[info] created a default config file.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            std::process::exit(25);
 | 
					            std::process::exit(25);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -126,9 +147,14 @@ pub fn main(
 | 
				
			|||||||
        scroll_pixels_multiplier,
 | 
					        scroll_pixels_multiplier,
 | 
				
			||||||
        scroll_lines_multiplier,
 | 
					        scroll_lines_multiplier,
 | 
				
			||||||
        scroll_pages_multiplier,
 | 
					        scroll_pages_multiplier,
 | 
				
			||||||
 | 
					        GuiConfig { status_bar_text },
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct GuiConfig {
 | 
				
			||||||
 | 
					    pub status_bar_text: textcfg::TextBuilder,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Gui {
 | 
					pub struct Gui {
 | 
				
			||||||
    pub event_sender: UserEventSender<GuiEvent>,
 | 
					    pub event_sender: UserEventSender<GuiEvent>,
 | 
				
			||||||
    pub database: Arc<Mutex<Database>>,
 | 
					    pub database: Arc<Mutex<Database>>,
 | 
				
			||||||
@ -150,6 +176,7 @@ pub struct Gui {
 | 
				
			|||||||
    pub scroll_pixels_multiplier: f64,
 | 
					    pub scroll_pixels_multiplier: f64,
 | 
				
			||||||
    pub scroll_lines_multiplier: f64,
 | 
					    pub scroll_lines_multiplier: f64,
 | 
				
			||||||
    pub scroll_pages_multiplier: f64,
 | 
					    pub scroll_pages_multiplier: f64,
 | 
				
			||||||
 | 
					    pub gui_config: Option<GuiConfig>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl Gui {
 | 
					impl Gui {
 | 
				
			||||||
    fn new(
 | 
					    fn new(
 | 
				
			||||||
@ -163,6 +190,7 @@ impl Gui {
 | 
				
			|||||||
        scroll_pixels_multiplier: f64,
 | 
					        scroll_pixels_multiplier: f64,
 | 
				
			||||||
        scroll_lines_multiplier: f64,
 | 
					        scroll_lines_multiplier: f64,
 | 
				
			||||||
        scroll_pages_multiplier: f64,
 | 
					        scroll_pages_multiplier: f64,
 | 
				
			||||||
 | 
					        gui_config: GuiConfig,
 | 
				
			||||||
    ) -> Self {
 | 
					    ) -> Self {
 | 
				
			||||||
        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 {
 | 
				
			||||||
@ -227,6 +255,7 @@ impl Gui {
 | 
				
			|||||||
            scroll_pixels_multiplier,
 | 
					            scroll_pixels_multiplier,
 | 
				
			||||||
            scroll_lines_multiplier,
 | 
					            scroll_lines_multiplier,
 | 
				
			||||||
            scroll_pages_multiplier,
 | 
					            scroll_pages_multiplier,
 | 
				
			||||||
 | 
					            gui_config: Some(gui_config),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -430,6 +459,7 @@ pub struct DrawInfo<'a> {
 | 
				
			|||||||
        Dragging,
 | 
					        Dragging,
 | 
				
			||||||
        Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
 | 
					        Option<Box<dyn FnMut(&mut DrawInfo, &mut Graphics2D)>>,
 | 
				
			||||||
    )>,
 | 
					    )>,
 | 
				
			||||||
 | 
					    pub gui_config: &'a GuiConfig,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Generic wrapper over anything that implements GuiElemTrait
 | 
					/// Generic wrapper over anything that implements GuiElemTrait
 | 
				
			||||||
@ -817,6 +847,7 @@ impl WindowHandler<GuiEvent> for Gui {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        let mut dblock = self.database.lock().unwrap();
 | 
					        let mut dblock = self.database.lock().unwrap();
 | 
				
			||||||
        let mut covers = self.covers.take().unwrap();
 | 
					        let mut covers = self.covers.take().unwrap();
 | 
				
			||||||
 | 
					        let cfg = self.gui_config.take().unwrap();
 | 
				
			||||||
        let mut info = DrawInfo {
 | 
					        let mut info = DrawInfo {
 | 
				
			||||||
            actions: Vec::with_capacity(0),
 | 
					            actions: Vec::with_capacity(0),
 | 
				
			||||||
            pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()),
 | 
					            pos: Rectangle::new(Vec2::ZERO, self.size.into_f32()),
 | 
				
			||||||
@ -830,6 +861,7 @@ impl WindowHandler<GuiEvent> for Gui {
 | 
				
			|||||||
            child_has_keyboard_focus: true,
 | 
					            child_has_keyboard_focus: true,
 | 
				
			||||||
            line_height: self.line_height,
 | 
					            line_height: self.line_height,
 | 
				
			||||||
            dragging: self.dragging.take(),
 | 
					            dragging: self.dragging.take(),
 | 
				
			||||||
 | 
					            gui_config: &cfg,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        self.gui.draw(&mut info, graphics);
 | 
					        self.gui.draw(&mut info, graphics);
 | 
				
			||||||
        let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0));
 | 
					        let actions = std::mem::replace(&mut info.actions, Vec::with_capacity(0));
 | 
				
			||||||
@ -864,6 +896,7 @@ impl WindowHandler<GuiEvent> for Gui {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        // cleanup
 | 
					        // cleanup
 | 
				
			||||||
        drop(info);
 | 
					        drop(info);
 | 
				
			||||||
 | 
					        self.gui_config = Some(cfg);
 | 
				
			||||||
        self.covers = Some(covers);
 | 
					        self.covers = Some(covers);
 | 
				
			||||||
        drop(dblock);
 | 
					        drop(dblock);
 | 
				
			||||||
        for a in actions {
 | 
					        for a in actions {
 | 
				
			||||||
 | 
				
			|||||||
@ -31,20 +31,11 @@ impl CurrentSong {
 | 
				
			|||||||
    pub fn new(config: GuiElemCfg) -> Self {
 | 
					    pub fn new(config: GuiElemCfg) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            config,
 | 
					            config,
 | 
				
			||||||
            children: vec![
 | 
					            children: vec![GuiElem::new(AdvancedLabel::new(
 | 
				
			||||||
                GuiElem::new(Label::new(
 | 
					                GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (1.0, 1.0))),
 | 
				
			||||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.0), (1.0, 0.5))),
 | 
					                Vec2::new(0.0, 0.5),
 | 
				
			||||||
                    "".to_owned(),
 | 
					 | 
				
			||||||
                    Color::from_int_rgb(180, 180, 210),
 | 
					 | 
				
			||||||
                    None,
 | 
					 | 
				
			||||||
                    Vec2::new(0.0, 1.0),
 | 
					 | 
				
			||||||
                )),
 | 
					 | 
				
			||||||
                GuiElem::new(AdvancedLabel::new(
 | 
					 | 
				
			||||||
                    GuiElemCfg::at(Rectangle::from_tuples((0.4, 0.5), (1.0, 1.0))),
 | 
					 | 
				
			||||||
                    Vec2::new(0.0, 0.0),
 | 
					 | 
				
			||||||
                vec![],
 | 
					                vec![],
 | 
				
			||||||
                )),
 | 
					            ))],
 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
 | 
					            cover_pos: Rectangle::new(Vec2::ZERO, Vec2::ZERO),
 | 
				
			||||||
            covers: VecDeque::new(),
 | 
					            covers: VecDeque::new(),
 | 
				
			||||||
            prev_song: None,
 | 
					            prev_song: None,
 | 
				
			||||||
@ -140,139 +131,34 @@ impl GuiElemTrait for CurrentSong {
 | 
				
			|||||||
            // redraw
 | 
					            // redraw
 | 
				
			||||||
            if self.config.redraw {
 | 
					            if self.config.redraw {
 | 
				
			||||||
                self.config.redraw = false;
 | 
					                self.config.redraw = false;
 | 
				
			||||||
                let (name, subtext) = if let Some(song) = new_song {
 | 
					                if let Some(song) = new_song {
 | 
				
			||||||
                    if let Some(song) = info.database.get_song(&song) {
 | 
					                    let status_bar_text = info
 | 
				
			||||||
                        let sub = match (
 | 
					                        .gui_config
 | 
				
			||||||
                            info.database.artists().get(&song.artist),
 | 
					                        .status_bar_text
 | 
				
			||||||
                            song.album
 | 
					                        .gen(&info.database, info.database.get_song(&song));
 | 
				
			||||||
                                .as_ref()
 | 
					                    self.children[0]
 | 
				
			||||||
                                .and_then(|id| info.database.albums().get(id)),
 | 
					 | 
				
			||||||
                        ) {
 | 
					 | 
				
			||||||
                            (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(),
 | 
					 | 
				
			||||||
                            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(), vec![])
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                *self.children[0]
 | 
					 | 
				
			||||||
                    .try_as_mut::<Label>()
 | 
					 | 
				
			||||||
                    .unwrap()
 | 
					 | 
				
			||||||
                    .content
 | 
					 | 
				
			||||||
                    .text() = name;
 | 
					 | 
				
			||||||
                self.children[1]
 | 
					 | 
				
			||||||
                        .try_as_mut::<AdvancedLabel>()
 | 
					                        .try_as_mut::<AdvancedLabel>()
 | 
				
			||||||
                        .unwrap()
 | 
					                        .unwrap()
 | 
				
			||||||
                    .content = subtext;
 | 
					                        .content = status_bar_text;
 | 
				
			||||||
                    self.text_updated = Some(Instant::now());
 | 
					                    self.text_updated = Some(Instant::now());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if let Some(updated) = &self.text_updated {
 | 
					        if let Some(updated) = &self.text_updated {
 | 
				
			||||||
            if let Some(h) = &info.helper {
 | 
					            if let Some(h) = &info.helper {
 | 
				
			||||||
                h.request_redraw();
 | 
					                h.request_redraw();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let prog = updated.elapsed().as_secs_f32();
 | 
					            let mut prog = updated.elapsed().as_secs_f32();
 | 
				
			||||||
            *self.children[0]
 | 
					            if prog >= 1.0 {
 | 
				
			||||||
                .try_as_mut::<Label>()
 | 
					                prog = 1.0;
 | 
				
			||||||
 | 
					                self.text_updated = None;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            self.children[0]
 | 
				
			||||||
 | 
					                .try_as_mut::<AdvancedLabel>()
 | 
				
			||||||
                .unwrap()
 | 
					                .unwrap()
 | 
				
			||||||
                .content
 | 
					                .content
 | 
				
			||||||
                .color() = Self::color_title((prog / 1.5).min(1.0));
 | 
					                .iter_mut()
 | 
				
			||||||
            let subtext = self.children[1].try_as_mut::<AdvancedLabel>().unwrap();
 | 
					                .count();
 | 
				
			||||||
            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
 | 
					        // drawing stuff
 | 
				
			||||||
        if self.config.pixel_pos.size() != info.pos.size() {
 | 
					        if self.config.pixel_pos.size() != info.pos.size() {
 | 
				
			||||||
 | 
				
			|||||||
@ -268,21 +268,19 @@ pub struct AdvancedLabel {
 | 
				
			|||||||
    pub align: Vec2,
 | 
					    pub align: Vec2,
 | 
				
			||||||
    /// (Content, Size-Scale, Height)
 | 
					    /// (Content, Size-Scale, Height)
 | 
				
			||||||
    /// Size-Scale and Height should default to 1.0.
 | 
					    /// Size-Scale and Height should default to 1.0.
 | 
				
			||||||
    pub content: Vec<(Content, f32, f32)>,
 | 
					    pub content: Vec<Vec<(Content, f32, f32)>>,
 | 
				
			||||||
    /// the position from where content drawing starts.
 | 
					    /// the position from where content drawing starts.
 | 
				
			||||||
    /// recalculated when layouting is performed.
 | 
					    /// recalculated when layouting is performed.
 | 
				
			||||||
    content_pos: Vec2,
 | 
					    content_pos: Vec2,
 | 
				
			||||||
    content_height: f32,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl AdvancedLabel {
 | 
					impl AdvancedLabel {
 | 
				
			||||||
    pub fn new(config: GuiElemCfg, align: Vec2, content: Vec<(Content, f32, f32)>) -> Self {
 | 
					    pub fn new(config: GuiElemCfg, align: Vec2, content: Vec<Vec<(Content, f32, f32)>>) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            config,
 | 
					            config,
 | 
				
			||||||
            children: vec![],
 | 
					            children: vec![],
 | 
				
			||||||
            align,
 | 
					            align,
 | 
				
			||||||
            content,
 | 
					            content,
 | 
				
			||||||
            content_pos: Vec2::ZERO,
 | 
					            content_pos: Vec2::ZERO,
 | 
				
			||||||
            content_height: 0.0,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -308,13 +306,19 @@ impl GuiElemTrait for AdvancedLabel {
 | 
				
			|||||||
    fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
 | 
					    fn draw(&mut self, info: &mut crate::gui::DrawInfo, g: &mut speedy2d::Graphics2D) {
 | 
				
			||||||
        if self.config.redraw
 | 
					        if self.config.redraw
 | 
				
			||||||
            || self.config.pixel_pos.size() != info.pos.size()
 | 
					            || self.config.pixel_pos.size() != info.pos.size()
 | 
				
			||||||
            || self.content.iter().any(|(c, _, _)| c.will_redraw())
 | 
					            || self
 | 
				
			||||||
 | 
					                .content
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .any(|v| v.iter().any(|(c, _, _)| c.will_redraw()))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            self.config.redraw = false;
 | 
					            self.config.redraw = false;
 | 
				
			||||||
 | 
					            let mut max_len = 0.0;
 | 
				
			||||||
 | 
					            let mut total_height = 0.0;
 | 
				
			||||||
 | 
					            for line in &self.content {
 | 
				
			||||||
                let mut len = 0.0;
 | 
					                let mut len = 0.0;
 | 
				
			||||||
                let mut height = 0.0;
 | 
					                let mut height = 0.0;
 | 
				
			||||||
            for (c, scale, _) in &self.content {
 | 
					                for (c, scale, _) in line {
 | 
				
			||||||
                let mut size = info
 | 
					                    let size = info
 | 
				
			||||||
                        .font
 | 
					                        .font
 | 
				
			||||||
                        .layout_text(&c.text, 1.0, TextOptions::new())
 | 
					                        .layout_text(&c.text, 1.0, TextOptions::new())
 | 
				
			||||||
                        .size();
 | 
					                        .size();
 | 
				
			||||||
@ -323,24 +327,29 @@ impl GuiElemTrait for AdvancedLabel {
 | 
				
			|||||||
                        height = size.y * scale;
 | 
					                        height = size.y * scale;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            if len > 0.0 && height > 0.0 {
 | 
					                if len > max_len {
 | 
				
			||||||
                let scale1 = info.pos.width() / len;
 | 
					                    max_len = len;
 | 
				
			||||||
                let scale2 = info.pos.height() / height;
 | 
					                }
 | 
				
			||||||
 | 
					                total_height += height;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if max_len > 0.0 && total_height > 0.0 {
 | 
				
			||||||
 | 
					                let scale1 = info.pos.width() / max_len;
 | 
				
			||||||
 | 
					                let scale2 = info.pos.height() / total_height;
 | 
				
			||||||
                let scale;
 | 
					                let scale;
 | 
				
			||||||
                self.content_pos = if scale1 < scale2 {
 | 
					                self.content_pos = if scale1 < scale2 {
 | 
				
			||||||
                    // use all available width
 | 
					                    // use all available width
 | 
				
			||||||
                    scale = scale1;
 | 
					                    scale = scale1;
 | 
				
			||||||
                    self.content_height = height * scale;
 | 
					                    Vec2::new(
 | 
				
			||||||
                    let pad = info.pos.height() - self.content_height;
 | 
					                        0.0,
 | 
				
			||||||
                    Vec2::new(0.0, pad * self.align.y)
 | 
					                        (info.pos.height() - (total_height * scale)) * self.align.y,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    // use all available height
 | 
					                    // use all available height
 | 
				
			||||||
                    scale = scale2;
 | 
					                    scale = scale2;
 | 
				
			||||||
                    self.content_height = info.pos.height();
 | 
					                    Vec2::new((info.pos.width() - (max_len * scale)) * self.align.x, 0.0)
 | 
				
			||||||
                    let pad = info.pos.width() - len * scale;
 | 
					 | 
				
			||||||
                    Vec2::new(pad * self.align.x, 0.0)
 | 
					 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                for (c, s, _) in &mut self.content {
 | 
					                for line in &mut self.content {
 | 
				
			||||||
 | 
					                    for (c, s, _) in line {
 | 
				
			||||||
                        c.formatted = Some(info.font.layout_text(
 | 
					                        c.formatted = Some(info.font.layout_text(
 | 
				
			||||||
                            &c.text,
 | 
					                            &c.text,
 | 
				
			||||||
                            scale * (*s),
 | 
					                            scale * (*s),
 | 
				
			||||||
@ -349,14 +358,25 @@ impl GuiElemTrait for AdvancedLabel {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        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;
 | 
					        let pos_x_start = info.pos.top_left().x + self.content_pos.x;
 | 
				
			||||||
        for (c, _, h) in &self.content {
 | 
					        let mut pos_y = info.pos.top_left().y + self.content_pos.y;
 | 
				
			||||||
 | 
					        for line in &self.content {
 | 
				
			||||||
 | 
					            let mut pos_x = pos_x_start;
 | 
				
			||||||
 | 
					            let height = line
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .filter_map(|v| v.0.formatted.as_ref())
 | 
				
			||||||
 | 
					                .map(|f| f.height())
 | 
				
			||||||
 | 
					                .reduce(f32::max)
 | 
				
			||||||
 | 
					                .unwrap_or(0.0);
 | 
				
			||||||
 | 
					            for (c, _, h) in line {
 | 
				
			||||||
                if let Some(f) = &c.formatted {
 | 
					                if let Some(f) = &c.formatted {
 | 
				
			||||||
                let y = pos_y + (self.content_height - f.height()) * h;
 | 
					                    let y = pos_y + (height - f.height()) * h;
 | 
				
			||||||
                    g.draw_text(Vec2::new(pos_x, y), c.color, f);
 | 
					                    g.draw_text(Vec2::new(pos_x, y), c.color, f);
 | 
				
			||||||
                    pos_x += f.width();
 | 
					                    pos_x += f.width();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            pos_y += height;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -42,6 +42,7 @@ mod gui_settings;
 | 
				
			|||||||
mod gui_text;
 | 
					mod gui_text;
 | 
				
			||||||
#[cfg(feature = "speedy2d")]
 | 
					#[cfg(feature = "speedy2d")]
 | 
				
			||||||
mod gui_wrappers;
 | 
					mod gui_wrappers;
 | 
				
			||||||
 | 
					mod textcfg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy)]
 | 
					#[derive(Clone, Copy)]
 | 
				
			||||||
enum Mode {
 | 
					enum Mode {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										326
									
								
								musicdb-client/src/textcfg.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								musicdb-client/src/textcfg.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,326 @@
 | 
				
			|||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    fmt::Display,
 | 
				
			||||||
 | 
					    iter::Peekable,
 | 
				
			||||||
 | 
					    str::{Chars, FromStr},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use musicdb_lib::data::{database::Database, song::Song, GeneralData, SongId};
 | 
				
			||||||
 | 
					use speedy2d::color::Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::gui_text::Content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct TextBuilder(pub Vec<TextPart>);
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum TextPart {
 | 
				
			||||||
 | 
					    LineBreak,
 | 
				
			||||||
 | 
					    SetColor(Color),
 | 
				
			||||||
 | 
					    SetScale(f32),
 | 
				
			||||||
 | 
					    SetHeightAlign(f32),
 | 
				
			||||||
 | 
					    // - - - - -
 | 
				
			||||||
 | 
					    Literal(String),
 | 
				
			||||||
 | 
					    SongTitle,
 | 
				
			||||||
 | 
					    AlbumName,
 | 
				
			||||||
 | 
					    ArtistName,
 | 
				
			||||||
 | 
					    /// Searches for a tag with exactly the provided value.
 | 
				
			||||||
 | 
					    /// Returns nothing or one of the following characters:
 | 
				
			||||||
 | 
					    /// `s` for Song, `a` for Album, and `A` for Artist.
 | 
				
			||||||
 | 
					    TagEq(String),
 | 
				
			||||||
 | 
					    /// Searches for a tag which starts with the provided string, then returns the end of it.
 | 
				
			||||||
 | 
					    /// If the search string is the entire tag, returns an empty string (which is not `nothing` because it is a TextPart::Literal, so it counts as `something` in an `if`).
 | 
				
			||||||
 | 
					    TagEnd(String),
 | 
				
			||||||
 | 
					    /// Searches for a tag which contains the provided string, then returns that tag's value.
 | 
				
			||||||
 | 
					    TagContains(String),
 | 
				
			||||||
 | 
					    /// If `1` is something, uses `2`.
 | 
				
			||||||
 | 
					    /// If `1` is nothing, uses `3`.
 | 
				
			||||||
 | 
					    If(TextBuilder, TextBuilder, TextBuilder),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl TextBuilder {
 | 
				
			||||||
 | 
					    pub fn gen(&self, db: &Database, current_song: Option<&Song>) -> Vec<Vec<(Content, f32, f32)>> {
 | 
				
			||||||
 | 
					        let mut out = vec![];
 | 
				
			||||||
 | 
					        let mut line = vec![];
 | 
				
			||||||
 | 
					        self.gen_to(db, current_song, &mut out, &mut line, &mut 1.0, &mut 1.0);
 | 
				
			||||||
 | 
					        if !line.is_empty() {
 | 
				
			||||||
 | 
					            out.push(line)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        out
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub fn gen_to(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        db: &Database,
 | 
				
			||||||
 | 
					        current_song: Option<&Song>,
 | 
				
			||||||
 | 
					        out: &mut Vec<Vec<(Content, f32, f32)>>,
 | 
				
			||||||
 | 
					        line: &mut Vec<(Content, f32, f32)>,
 | 
				
			||||||
 | 
					        scale: &mut f32,
 | 
				
			||||||
 | 
					        align: &mut f32,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        let mut color = Color::WHITE;
 | 
				
			||||||
 | 
					        macro_rules! push {
 | 
				
			||||||
 | 
					            ($e:expr) => {
 | 
				
			||||||
 | 
					                line.push((Content::new($e, color), *scale, *align))
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        fn all_general<'a>(
 | 
				
			||||||
 | 
					            db: &'a Database,
 | 
				
			||||||
 | 
					            current_song: &'a Option<&'a Song>,
 | 
				
			||||||
 | 
					        ) -> [Option<&'a GeneralData>; 3] {
 | 
				
			||||||
 | 
					            if let Some(s) = current_song {
 | 
				
			||||||
 | 
					                if let Some(al) = s.album.and_then(|id| db.albums().get(&id)) {
 | 
				
			||||||
 | 
					                    if let Some(a) = db.artists().get(&s.artist) {
 | 
				
			||||||
 | 
					                        [Some(&s.general), Some(&al.general), Some(&a.general)]
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        [Some(&s.general), Some(&al.general), None]
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if let Some(a) = db.artists().get(&s.artist) {
 | 
				
			||||||
 | 
					                    [Some(&s.general), None, Some(&a.general)]
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    [Some(&s.general), None, None]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                [None, None, None]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for part in &self.0 {
 | 
				
			||||||
 | 
					            match part {
 | 
				
			||||||
 | 
					                TextPart::LineBreak => out.push(std::mem::replace(line, vec![])),
 | 
				
			||||||
 | 
					                TextPart::SetColor(c) => color = *c,
 | 
				
			||||||
 | 
					                TextPart::SetScale(v) => *scale = *v,
 | 
				
			||||||
 | 
					                TextPart::SetHeightAlign(v) => *align = *v,
 | 
				
			||||||
 | 
					                TextPart::Literal(s) => push!(s.to_owned()),
 | 
				
			||||||
 | 
					                TextPart::SongTitle => {
 | 
				
			||||||
 | 
					                    if let Some(s) = current_song {
 | 
				
			||||||
 | 
					                        push!(s.title.to_owned());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::AlbumName => {
 | 
				
			||||||
 | 
					                    if let Some(s) = current_song {
 | 
				
			||||||
 | 
					                        if let Some(album) = s.album.and_then(|id| db.albums().get(&id)) {
 | 
				
			||||||
 | 
					                            push!(album.name.to_owned());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::ArtistName => {
 | 
				
			||||||
 | 
					                    if let Some(s) = current_song {
 | 
				
			||||||
 | 
					                        if let Some(artist) = db.artists().get(&s.artist) {
 | 
				
			||||||
 | 
					                            push!(artist.name.to_owned());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::TagEq(p) => {
 | 
				
			||||||
 | 
					                    for (i, gen) in all_general(db, ¤t_song).into_iter().enumerate() {
 | 
				
			||||||
 | 
					                        if let Some(_) = gen.and_then(|gen| gen.tags.iter().find(|t| *t == p)) {
 | 
				
			||||||
 | 
					                            push!(match i {
 | 
				
			||||||
 | 
					                                0 => 's',
 | 
				
			||||||
 | 
					                                1 => 'a',
 | 
				
			||||||
 | 
					                                2 => 'A',
 | 
				
			||||||
 | 
					                                _ => unreachable!("array length should be 3"),
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            .to_string());
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::TagEnd(p) => {
 | 
				
			||||||
 | 
					                    for gen in all_general(db, ¤t_song) {
 | 
				
			||||||
 | 
					                        if let Some(t) =
 | 
				
			||||||
 | 
					                            gen.and_then(|gen| gen.tags.iter().find(|t| t.starts_with(p)))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            push!(t[p.len()..].to_owned());
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::TagContains(p) => {
 | 
				
			||||||
 | 
					                    for gen in all_general(db, ¤t_song) {
 | 
				
			||||||
 | 
					                        if let Some(t) = gen.and_then(|gen| gen.tags.iter().find(|t| t.contains(p)))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            push!(t.to_owned());
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TextPart::If(condition, yes, no) => {
 | 
				
			||||||
 | 
					                    if !condition.gen(db, current_song).is_empty() {
 | 
				
			||||||
 | 
					                        yes.gen_to(db, current_song, out, line, scale, align);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        no.gen_to(db, current_song, out, line, scale, align);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl FromStr for TextBuilder {
 | 
				
			||||||
 | 
					    type Err = TextBuilderParseError;
 | 
				
			||||||
 | 
					    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
				
			||||||
 | 
					        Self::from_chars(&mut s.chars())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl TextBuilder {
 | 
				
			||||||
 | 
					    fn from_chars(chars: &mut Chars) -> Result<Self, TextBuilderParseError> {
 | 
				
			||||||
 | 
					        let mut vec = vec![];
 | 
				
			||||||
 | 
					        let mut current = String::new();
 | 
				
			||||||
 | 
					        macro_rules! done {
 | 
				
			||||||
 | 
					            () => {
 | 
				
			||||||
 | 
					                if !current.is_empty() {
 | 
				
			||||||
 | 
					                    // if it starts with at least one space, replace the first space with
 | 
				
			||||||
 | 
					                    // a No-Break space, as recommended in `https://github.com/QuantumBadger/Speedy2D/issues/45`,
 | 
				
			||||||
 | 
					                    // to avoid an issue where leading whitespaces are removed when drawing text.
 | 
				
			||||||
 | 
					                    if current.starts_with(' ') {
 | 
				
			||||||
 | 
					                        current = current.replacen(' ', "\u{00A0}", 1);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    vec.push(TextPart::Literal(std::mem::replace(
 | 
				
			||||||
 | 
					                        &mut current,
 | 
				
			||||||
 | 
					                        String::new(),
 | 
				
			||||||
 | 
					                    )));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            if let Some(ch) = chars.next() {
 | 
				
			||||||
 | 
					                match ch {
 | 
				
			||||||
 | 
					                    '\n' => {
 | 
				
			||||||
 | 
					                        done!();
 | 
				
			||||||
 | 
					                        vec.push(TextPart::LineBreak);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    '\\' => match chars.next() {
 | 
				
			||||||
 | 
					                        None => current.push('\\'),
 | 
				
			||||||
 | 
					                        Some('t') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::SongTitle);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some('a') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::AlbumName);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some('A') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::ArtistName);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some('s') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::SetScale({
 | 
				
			||||||
 | 
					                                let mut str = String::new();
 | 
				
			||||||
 | 
					                                loop {
 | 
				
			||||||
 | 
					                                    match chars.next() {
 | 
				
			||||||
 | 
					                                        None | Some(';') => break,
 | 
				
			||||||
 | 
					                                        Some(c) => str.push(c),
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                if let Ok(v) = str.parse() {
 | 
				
			||||||
 | 
					                                    v
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    return Err(TextBuilderParseError::CouldntParse(
 | 
				
			||||||
 | 
					                                        str,
 | 
				
			||||||
 | 
					                                        "number (float)".to_string(),
 | 
				
			||||||
 | 
					                                    ));
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some('h') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::SetHeightAlign({
 | 
				
			||||||
 | 
					                                let mut str = String::new();
 | 
				
			||||||
 | 
					                                loop {
 | 
				
			||||||
 | 
					                                    match chars.next() {
 | 
				
			||||||
 | 
					                                        None | Some(';') => break,
 | 
				
			||||||
 | 
					                                        Some(c) => str.push(c),
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                if let Ok(v) = str.parse() {
 | 
				
			||||||
 | 
					                                    v
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    return Err(TextBuilderParseError::CouldntParse(
 | 
				
			||||||
 | 
					                                        str,
 | 
				
			||||||
 | 
					                                        "number (float)".to_string(),
 | 
				
			||||||
 | 
					                                    ));
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some('c') => {
 | 
				
			||||||
 | 
					                            done!();
 | 
				
			||||||
 | 
					                            vec.push(TextPart::SetColor({
 | 
				
			||||||
 | 
					                                let mut str = String::new();
 | 
				
			||||||
 | 
					                                for _ in 0..6 {
 | 
				
			||||||
 | 
					                                    if let Some(ch) = chars.next() {
 | 
				
			||||||
 | 
					                                        str.push(ch);
 | 
				
			||||||
 | 
					                                    } else {
 | 
				
			||||||
 | 
					                                        return Err(TextBuilderParseError::TooFewCharsForColor);
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                if let Ok(i) = u32::from_str_radix(&str, 16) {
 | 
				
			||||||
 | 
					                                    Color::from_hex_rgb(i)
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    return Err(TextBuilderParseError::ColorNotHex);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        Some(ch) => current.push(ch),
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    '%' => {
 | 
				
			||||||
 | 
					                        done!();
 | 
				
			||||||
 | 
					                        let mode = if let Some(ch) = chars.next() {
 | 
				
			||||||
 | 
					                            ch
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            return Err(TextBuilderParseError::UnclosedPercent);
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                        loop {
 | 
				
			||||||
 | 
					                            match chars.next() {
 | 
				
			||||||
 | 
					                                Some('%') => {
 | 
				
			||||||
 | 
					                                    let s = std::mem::replace(&mut current, String::new());
 | 
				
			||||||
 | 
					                                    vec.push(match mode {
 | 
				
			||||||
 | 
					                                        '=' => TextPart::TagEq(s),
 | 
				
			||||||
 | 
					                                        '>' => TextPart::TagEnd(s),
 | 
				
			||||||
 | 
					                                        '_' => TextPart::TagContains(s),
 | 
				
			||||||
 | 
					                                        c => return Err(TextBuilderParseError::TagModeUnknown(c)),
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                    break;
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                Some(ch) => current.push(ch),
 | 
				
			||||||
 | 
					                                None => return Err(TextBuilderParseError::UnclosedPercent),
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    '?' => {
 | 
				
			||||||
 | 
					                        done!();
 | 
				
			||||||
 | 
					                        vec.push(TextPart::If(
 | 
				
			||||||
 | 
					                            Self::from_chars(chars)?,
 | 
				
			||||||
 | 
					                            Self::from_chars(chars)?,
 | 
				
			||||||
 | 
					                            Self::from_chars(chars)?,
 | 
				
			||||||
 | 
					                        ));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    '#' => break,
 | 
				
			||||||
 | 
					                    ch => current.push(ch),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        done!();
 | 
				
			||||||
 | 
					        Ok(Self(vec))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum TextBuilderParseError {
 | 
				
			||||||
 | 
					    UnclosedPercent,
 | 
				
			||||||
 | 
					    TagModeUnknown(char),
 | 
				
			||||||
 | 
					    TooFewCharsForColor,
 | 
				
			||||||
 | 
					    ColorNotHex,
 | 
				
			||||||
 | 
					    CouldntParse(String, String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl Display for TextBuilderParseError {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::UnclosedPercent => write!(
 | 
				
			||||||
 | 
					                f,
 | 
				
			||||||
 | 
					                "Unclosed %: Syntax is %<mode><search>%, where <mode> is _, >, or =."
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Self::TagModeUnknown(mode) => {
 | 
				
			||||||
 | 
					                write!(f, "Unknown tag mode '{mode}': Allowed are only _, > or =.")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Self::TooFewCharsForColor => write!(f, "Too few chars for color: Syntax is \\cRRGGBB."),
 | 
				
			||||||
 | 
					            Self::ColorNotHex => write!(f, "Color value wasn't a hex number! Syntax is \\cRRGGBB, where R, G, and B are values from 0-9 and A-F (hex 0-F)."),
 | 
				
			||||||
 | 
					            Self::CouldntParse(v, t) => write!(f, "Couldn't parse value '{v}' to type '{t}'."),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							@ -58,6 +58,13 @@ fn main() {
 | 
				
			|||||||
    eprintln!("searching for artists...");
 | 
					    eprintln!("searching for artists...");
 | 
				
			||||||
    let mut artists = HashMap::new();
 | 
					    let mut artists = HashMap::new();
 | 
				
			||||||
    for song in songs {
 | 
					    for song in songs {
 | 
				
			||||||
 | 
					        let mut general = GeneralData::default();
 | 
				
			||||||
 | 
					        if let Some(year) = song.1.year() {
 | 
				
			||||||
 | 
					            general.tags.push(format!("Year={year}"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let Some(genre) = song.1.genre_parsed() {
 | 
				
			||||||
 | 
					            general.tags.push(format!("Genre={genre}"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        let (artist_id, album_id) = if let Some(artist) = song
 | 
					        let (artist_id, album_id) = if let Some(artist) = song
 | 
				
			||||||
            .1
 | 
					            .1
 | 
				
			||||||
            .album_artist()
 | 
					            .album_artist()
 | 
				
			||||||
@ -135,7 +142,7 @@ fn main() {
 | 
				
			|||||||
            artist: artist_id,
 | 
					            artist: artist_id,
 | 
				
			||||||
            more_artists: vec![],
 | 
					            more_artists: vec![],
 | 
				
			||||||
            cover: None,
 | 
					            cover: None,
 | 
				
			||||||
            general: GeneralData::default(),
 | 
					            general,
 | 
				
			||||||
            cached_data: Arc::new(Mutex::new(None)),
 | 
					            cached_data: Arc::new(Mutex::new(None)),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user