diff --git a/src/lib.rs b/src/lib.rs index 83e1f3a..68d2d0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,7 @@ pub fn list_themes() -> Vec<&'static str> { pub struct LookupBuilder<'a> { name: &'a str, cache: bool, + force_svg: bool, scale: u16, size: u16, theme: &'a str, @@ -174,6 +175,24 @@ impl<'a> LookupBuilder<'a> { self } + /// By default [`find`] will prioritize Png over Svg icon. + /// Use this if you need to prioritize Svg icons. This could be useful + /// if you need a modifiable icon, to match a user theme for instance. + /// + /// ## Example + /// ```rust + /// # fn main() { + /// use freedesktop_icons::lookup; + /// + /// let icon = lookup("firefox") + /// .force_svg() + /// .find(); + /// # } + pub fn force_svg(mut self) -> Self { + self.force_svg = true; + self + } + /// Execute the current lookup /// if no icon is found in the current theme fallback to /// `/usr/share/icons/hicolor` theme and then to `/usr/share/pixmaps`. @@ -186,6 +205,7 @@ impl<'a> LookupBuilder<'a> { Self { name, cache: false, + force_svg: false, scale: 1, size: 24, theme: "hicolor", @@ -212,12 +232,12 @@ impl<'a> LookupBuilder<'a> { // Then lookup in the given theme THEMES.get(self.theme).and_then(|icon_theme| { let icon = icon_theme - .try_get_icon(self.name, self.size, self.scale) + .try_get_icon(self.name, self.size, self.scale, self.force_svg) .or_else(|| { // Fallback to the parent themes recursively icon_theme.inherits().into_iter().find_map(|parent| { THEMES.get(parent).and_then(|parent| { - parent.try_get_icon(self.name, self.size, self.scale) + parent.try_get_icon(self.name, self.size, self.scale, self.force_svg) }) }) }) @@ -225,10 +245,10 @@ impl<'a> LookupBuilder<'a> { THEMES .get("hicolor") // Fallback to 'hicolor' - .and_then(|hicolor| hicolor.try_get_icon(self.name, self.size, self.scale)) + .and_then(|hicolor| hicolor.try_get_icon(self.name, self.size, self.scale, self.force_svg)) }) // Last chance, try to find the icon in "/usr/share/pixmaps" - .or_else(|| try_build_icon_path(self.name, "/usr/share/pixmaps")); + .or_else(|| try_build_icon_path(self.name, "/usr/share/pixmaps", self.force_svg)); if self.cache { self.store(self.theme, icon) diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 7e34a2c..cce085d 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -23,17 +23,17 @@ pub struct Theme { } impl Theme { - pub fn try_get_icon(&self, name: &str, size: u16, scale: u16) -> Option { - self.try_get_icon_exact_size(name, size, scale) - .or_else(|| self.try_get_icon_closest_size(name, size, scale)) + pub fn try_get_icon(&self, name: &str, size: u16, scale: u16, force_svg: bool) -> Option { + self.try_get_icon_exact_size(name, size, scale, force_svg) + .or_else(|| self.try_get_icon_closest_size(name, size, scale, force_svg)) } - fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option { + fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16, force_svg: bool) -> Option { self.match_size(size, scale) - .find_map(|path| try_build_icon_path(name, path)) + .find_map(|path| try_build_icon_path(name, path, force_svg)) } - fn match_size(&self, size: u16, scale: u16) -> impl Iterator + '_ { + fn match_size(&self, size: u16, scale: u16) -> impl Iterator + '_ { let dirs = self.get_all_directories(); dirs.filter(move |directory| directory.match_size(size, scale)) @@ -41,10 +41,10 @@ impl Theme { .map(|dir| self.path().join(dir)) } - fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16) -> Option { + fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16, force_svg: bool) -> Option { self.closest_match_size(size, scale) .iter() - .find_map(|path| try_build_icon_path(name, path)) + .find_map(|path| try_build_icon_path(name, path, force_svg)) } fn closest_match_size(&self, size: u16, scale: u16) -> Vec { @@ -61,26 +61,48 @@ impl Theme { } } -pub(super) fn try_build_icon_path>(name: &str, path: P) -> Option { +pub(super) fn try_build_icon_path>(name: &str, path: P, force_svg: bool) -> Option { + if force_svg { + try_build_svg(name, path.as_ref()) + } else { + try_build_png(name, path.as_ref()) + .or(try_build_svg(name, path.as_ref())) + .or(try_build_xmp(name, path.as_ref())) + + } +} + +fn try_build_svg>(name: &str, path: P) -> Option { + let path = path.as_ref(); + let svg = path.join(format!("{name}.svg")); + if svg.exists() { + Some(svg) + } else { + None + } +} + +fn try_build_png>(name: &str, path: P) -> Option { let path = path.as_ref(); let png = path.join(format!("{name}.png")); if png.exists() { - return Some(png); + Some(png) + } else { + None } - - let svg = path.join(format!("{name}.svg")); - if svg.exists() { - return Some(svg); - } - let xmp = path.join(format!("{name}.xmp")); - - if xmp.exists() { - return Some(xmp); - } - - None } +fn try_build_xmp>(name: &str, path: P) -> Option { + let path = path.as_ref(); + let xmp = path.join(format!("{name}.xmp")); + if xmp.exists() { + Some(xmp) + } else { + None + } +} + + // Iter through the base paths and get all theme directories pub(super) fn get_all_themes() -> Result> { let mut icon_themes = BTreeMap::new(); @@ -126,6 +148,8 @@ impl Debug for Theme { #[cfg(test)] mod test { + use std::path::PathBuf; + use speculoos::prelude::*; use crate::THEMES; #[test] @@ -133,7 +157,25 @@ mod test { let theme = THEMES.get("Adwaita").unwrap(); println!( "{:?}", - theme.try_get_icon_exact_size("edit-delete-symbolic", 24, 1) + theme.try_get_icon_exact_size("edit-delete-symbolic", 24, 1, false) ); } + + #[test] + fn should_get_png_first() { + let theme = THEMES.get("hicolor").unwrap(); + let icon = theme.try_get_icon_exact_size("blueman", 24, 1, true); + assert_that!(icon) + .is_some() + .is_equal_to(PathBuf::from("/usr/share/icons/hicolor/scalable/apps/blueman.svg")); + } + + #[test] + fn should_get_svg_first() { + let theme = THEMES.get("hicolor").unwrap(); + let icon = theme.try_get_icon_exact_size("blueman", 24, 1, false); + assert_that!(icon) + .is_some() + .is_equal_to(PathBuf::from("/usr/share/icons/hicolor/22x22/apps/blueman.png")); + } }