feat: improve cache, add ScaledDirectories lookup and parent lookup
This commit is contained in:
parent
bc903c1bcb
commit
73702f6049
3 changed files with 133 additions and 37 deletions
110
src/lib.rs
110
src/lib.rs
|
|
@ -34,6 +34,22 @@
|
||||||
//! .find();
|
//! .find();
|
||||||
//! # }
|
//! # }
|
||||||
//!```
|
//!```
|
||||||
|
//! **Cache:**
|
||||||
|
//!
|
||||||
|
//! If your application is going to repeat the same icon lookups multiple time
|
||||||
|
//! you can use the internal cache to improve performance.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # fn main() {
|
||||||
|
//! use freedesktop_icons::lookup;
|
||||||
|
//!
|
||||||
|
//! let icon = lookup("firefox")
|
||||||
|
//! .with_size(48)
|
||||||
|
//! .with_scale(2)
|
||||||
|
//! .with_theme("Arc")
|
||||||
|
//! .with_cache()
|
||||||
|
//! .find();
|
||||||
|
//! # }
|
||||||
use crate::cache::CACHE;
|
use crate::cache::CACHE;
|
||||||
use crate::theme::{try_build_icon_path, THEMES};
|
use crate::theme::{try_build_icon_path, THEMES};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -41,9 +57,36 @@ use std::path::PathBuf;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
|
/// Return the list of installed themes on the system
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```rust
|
||||||
|
/// # fn main() {
|
||||||
|
/// use freedesktop_icons::list_themes;
|
||||||
|
///
|
||||||
|
/// let themes: Vec<&str> = list_themes();
|
||||||
|
///
|
||||||
|
/// assert_eq!(themes, vec![
|
||||||
|
/// "Adwaita", "Arc", "Breeze Light", "HighContrast", "Papirus", "Papirus-Dark",
|
||||||
|
/// "Papirus-Light", "Breeze", "Breeze Dark", "Breeze", "ePapirus", "ePapirus-Dark", "Hicolor"
|
||||||
|
/// ])
|
||||||
|
/// # }
|
||||||
|
pub fn list_themes() -> Vec<&'static str> {
|
||||||
|
THEMES
|
||||||
|
.values()
|
||||||
|
.map(|path| &path.index)
|
||||||
|
.filter_map(|index| {
|
||||||
|
index
|
||||||
|
.section(Some("Icon Theme"))
|
||||||
|
.and_then(|section| section.get("Name"))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// The lookup builder struct, holding all the lookup query parameters.
|
/// The lookup builder struct, holding all the lookup query parameters.
|
||||||
pub struct LookupBuilder<'a> {
|
pub struct LookupBuilder<'a> {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
cache: bool,
|
||||||
scale: u16,
|
scale: u16,
|
||||||
size: u16,
|
size: u16,
|
||||||
theme: Option<&'a str>,
|
theme: Option<&'a str>,
|
||||||
|
|
@ -110,6 +153,26 @@ impl<'a> LookupBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store the result of the lookup in cache, subsequent
|
||||||
|
/// lookup will first try to retrieve get the cached icon.
|
||||||
|
/// This can drastically increase lookup performances for application
|
||||||
|
/// that repeat the same lookups, an application launcher for instance.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```rust
|
||||||
|
/// # fn main() {
|
||||||
|
/// use freedesktop_icons::lookup;
|
||||||
|
///
|
||||||
|
/// let icon = lookup("firefox")
|
||||||
|
/// .with_scale(2)
|
||||||
|
/// .with_cache()
|
||||||
|
/// .find();
|
||||||
|
/// # }
|
||||||
|
pub fn with_cache(mut self) -> Self {
|
||||||
|
self.cache = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute the current lookup
|
/// Execute the current lookup
|
||||||
/// if no icon is found in the current theme fallback to
|
/// if no icon is found in the current theme fallback to
|
||||||
/// `/usr/shar/hicolor` theme and then to `/usr/share/pixmaps`.
|
/// `/usr/shar/hicolor` theme and then to `/usr/share/pixmaps`.
|
||||||
|
|
@ -132,6 +195,7 @@ impl<'a> LookupBuilder<'a> {
|
||||||
fn new<'b: 'a>(name: &'b str) -> Self {
|
fn new<'b: 'a>(name: &'b str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
|
cache: false,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
size: 24,
|
size: 24,
|
||||||
theme: None,
|
theme: None,
|
||||||
|
|
@ -140,23 +204,45 @@ impl<'a> LookupBuilder<'a> {
|
||||||
|
|
||||||
// Recursively lookup for icon in the given theme and its parents
|
// Recursively lookup for icon in the given theme and its parents
|
||||||
fn lookup_in_theme(&self, theme: &str) -> Option<PathBuf> {
|
fn lookup_in_theme(&self, theme: &str) -> Option<PathBuf> {
|
||||||
let cached = CACHE.get(theme, self.size, self.scale, self.name);
|
// If cache is activated, attempt to get the icon there first
|
||||||
if cached.is_some() {
|
if self.cache {
|
||||||
return cached;
|
let cached = self.cache_lookup(theme);
|
||||||
|
if cached.is_some() {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Then lookup in the given theme
|
||||||
THEMES.get(theme).and_then(|icon_theme| {
|
THEMES.get(theme).and_then(|icon_theme| {
|
||||||
icon_theme
|
let icon = icon_theme
|
||||||
.try_get_icon(self.name, self.size, self.scale)
|
.try_get_icon(self.name, self.size, self.scale)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
icon_theme
|
// Fallback to the parent themes recursively
|
||||||
.inherits()
|
icon_theme.inherits().into_iter().find_map(|parent| {
|
||||||
.and_then(|parent| self.lookup_in_theme(parent))
|
THEMES.get(parent).and_then(|parent| {
|
||||||
})
|
parent.try_get_icon(self.name, self.size, self.scale)
|
||||||
.map(|icon| {
|
})
|
||||||
CACHE.insert(theme, self.size, self.scale, self.name, &icon);
|
})
|
||||||
icon
|
});
|
||||||
})
|
|
||||||
|
if self.cache {
|
||||||
|
self.store(theme, icon)
|
||||||
|
} else {
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cache_lookup(&self, theme: &str) -> Option<PathBuf> {
|
||||||
|
CACHE.get(theme, self.size, self.scale, self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn store(&self, theme: &str, icon: Option<PathBuf>) -> Option<PathBuf> {
|
||||||
|
icon.map(|icon| {
|
||||||
|
CACHE.insert(theme, self.size, self.scale, self.name, &icon);
|
||||||
|
icon
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ pub static THEMES: Lazy<BTreeMap<String, Theme>> =
|
||||||
Lazy::new(|| get_all_themes().expect("Failed to get theme paths"));
|
Lazy::new(|| get_all_themes().expect("Failed to get theme paths"));
|
||||||
|
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
path: ThemePath,
|
pub path: ThemePath,
|
||||||
index: Ini,
|
pub index: Ini,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
|
@ -30,18 +30,15 @@ impl Theme {
|
||||||
|
|
||||||
fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
||||||
self.match_size(size, scale)
|
self.match_size(size, scale)
|
||||||
.iter()
|
|
||||||
.find_map(|path| try_build_icon_path(name, path))
|
.find_map(|path| try_build_icon_path(name, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
fn match_size(&self, size: u16, scale: u16) -> impl Iterator<Item = PathBuf> + '_ {
|
||||||
let dirs = self.get_all_directories();
|
let dirs = self.get_all_directories();
|
||||||
|
|
||||||
dirs.iter()
|
dirs.filter(move |directory| directory.match_size(size, scale))
|
||||||
.filter(|directory| directory.match_size(size, scale))
|
|
||||||
.map(|dir| dir.name)
|
.map(|dir| dir.name)
|
||||||
.map(|dir| self.path().join(dir))
|
.map(|dir| self.path().join(dir))
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
||||||
|
|
@ -53,8 +50,7 @@ impl Theme {
|
||||||
fn closest_match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
fn closest_match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
||||||
let dirs = self.get_all_directories();
|
let dirs = self.get_all_directories();
|
||||||
|
|
||||||
dirs.iter()
|
dirs.filter(|directory| directory.directory_size_distance(size, scale) < i16::MAX)
|
||||||
.filter(|directory| directory.directory_size_distance(size, scale) < i16::MAX)
|
|
||||||
.map(|dir| dir.name)
|
.map(|dir| dir.name)
|
||||||
.map(|dir| self.path().join(dir))
|
.map(|dir| self.path().join(dir))
|
||||||
.collect()
|
.collect()
|
||||||
|
|
@ -100,18 +96,6 @@ pub(super) fn get_all_themes() -> Result<BTreeMap<String, Theme>> {
|
||||||
Ok(icon_themes)
|
Ok(icon_themes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme_names() -> Vec<&'static str> {
|
|
||||||
THEMES
|
|
||||||
.values()
|
|
||||||
.map(|path| &path.index)
|
|
||||||
.filter_map(|index| {
|
|
||||||
index
|
|
||||||
.section(Some("Icon Theme"))
|
|
||||||
.and_then(|section| section.get("Name"))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
|
fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@ use crate::theme::Theme;
|
||||||
use ini::Properties;
|
use ini::Properties;
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
pub(super) fn get_all_directories(&self) -> Vec<Directory> {
|
pub(super) fn get_all_directories(&self) -> impl Iterator<Item = Directory> {
|
||||||
self.directories()
|
self.directories()
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter_map(|name| self.get_directory(name))
|
.filter_map(|name| self.get_directory(name))
|
||||||
.collect()
|
.chain(
|
||||||
|
self.scaled_directories()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|name| self.get_directory(name)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use me
|
// TODO: use me
|
||||||
|
|
@ -22,9 +26,11 @@ impl Theme {
|
||||||
self.index.section(Some("Icon Theme"))
|
self.index.section(Some("Icon Theme"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inherits(&self) -> Option<&str> {
|
pub fn inherits(&self) -> Vec<&str> {
|
||||||
self.get_icon_theme_section()
|
self.get_icon_theme_section()
|
||||||
.and_then(|props| props.get("Inherits"))
|
.and_then(|props| props.get("Inherits"))
|
||||||
|
.map(|parents| parents.split(',').collect())
|
||||||
|
.unwrap_or(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn directories(&self) -> Vec<&str> {
|
fn directories(&self) -> Vec<&str> {
|
||||||
|
|
@ -69,3 +75,23 @@ impl Theme {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::THEMES;
|
||||||
|
use speculoos::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_get_theme_parents() {
|
||||||
|
let theme = THEMES.get("Arc").unwrap();
|
||||||
|
let parents = theme.inherits();
|
||||||
|
assert_that!(parents).is_equal_to(vec![
|
||||||
|
"Moka",
|
||||||
|
"Faba",
|
||||||
|
"elementary",
|
||||||
|
"Adwaita",
|
||||||
|
"gnome",
|
||||||
|
"hicolor",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue