use crate::{DirectoryIndex, IconSearch, Theme};
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct Icons {
pub standalone_icons: HashMap<String, IconFile>,
pub themes: HashMap<OsString, Arc<Theme>>,
}
impl Icons {
pub fn new() -> Self {
IconSearch::new().search().icons()
}
pub fn theme(&self, theme_name: &str) -> Option<Arc<Theme>> {
let theme_name: &OsStr = theme_name.as_ref();
self.themes.get(theme_name).cloned()
}
pub fn find_default_icon(&self, icon_name: &str, size: u32, scale: u32) -> Option<IconFile> {
self.find_icon(icon_name, size, scale, "hicolor")
}
pub fn find_icon(
&self,
icon_name: &str,
size: u32,
scale: u32,
theme: &str,
) -> Option<IconFile> {
if icon_name.is_empty() {
return None;
}
let theme = self.theme(theme).or_else(|| self.theme("hicolor"))?;
theme
.find_icon(icon_name, size, scale)
.or_else(|| self.find_standalone_icon(icon_name))
}
pub fn find_standalone_icon(&self, icon_name: &str) -> Option<IconFile> {
self.standalone_icons.get(icon_name).cloned()
}
pub fn find_all_icons(&self) -> impl Iterator<Item = (Arc<Theme>, &DirectoryIndex, IconFile)> {
self.find_all_icons_filtered(|_| true, |_| true, |_| true)
}
pub fn find_all_icons_filtered<'a>(
&'a self,
filter_theme: impl Fn(&Theme) -> bool + 'a,
filter_directory: impl Fn(&DirectoryIndex) -> bool + 'a,
filter_icon: impl Fn(&IconFile) -> bool + Clone + 'a,
) -> impl Iterator<Item = (Arc<Theme>, &'a DirectoryIndex, IconFile)> {
let themes = self
.themes
.values()
.filter(move |theme| filter_theme(theme.as_ref()));
let dirs = themes
.flat_map(|theme| {
std::iter::zip(
std::iter::repeat(theme),
theme.info.index.directories.iter(),
)
})
.filter(move |(_, dir)| filter_directory(dir));
dirs.flat_map(move |(theme, dir)| {
let filter_icon = filter_icon.clone();
let dir_file_iterator = theme
.info
.base_dirs
.iter()
.map(|base_dir| base_dir.join(&dir.directory_name))
.flat_map(|dir| dir.read_dir()) .flatten() .flatten() .flat_map(|dir_entry| IconFile::from_path_buf(dir_entry.path())) .filter(move |icon| filter_icon(icon));
std::iter::zip(std::iter::repeat((theme, dir)), dir_file_iterator)
})
.map(|((a, b), c)| (a.clone(), b, c))
}
}
impl Default for Icons {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct IconFile {
path: PathBuf,
file_type: FileType,
}
impl IconFile {
pub fn icon_name(&self) -> &str {
self.path
.file_stem()
.and_then(|s| s.to_str())
.expect("protected by type's constructor")
}
pub fn from_path(path: &Path) -> Option<IconFile> {
Self::from_path_buf(path.to_owned())
}
pub fn from_path_buf(path_buf: PathBuf) -> Option<IconFile> {
path_buf.file_stem()?;
let file_type = FileType::from_path_ext(&path_buf)?;
Some(IconFile {
path: path_buf,
file_type,
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn file_type(&self) -> FileType {
self.file_type
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FileType {
Png,
Xpm,
Svg,
}
impl FileType {
pub fn from_path_ext(path: &Path) -> Option<Self> {
let ext = path.extension()?;
let ext = ext.to_str()?;
if ext.eq_ignore_ascii_case("png") {
Some(FileType::Png)
} else if ext.eq_ignore_ascii_case("xpm") {
Some(FileType::Xpm)
} else if ext.eq_ignore_ascii_case("svg") {
Some(FileType::Svg)
} else {
None
}
}
pub fn ext(&self) -> &str {
match self {
FileType::Png => "png",
FileType::Xpm => "xpm",
FileType::Svg => "svg",
}
}
pub const fn types() -> [FileType; 3] {
[FileType::Png, FileType::Xpm, FileType::Svg]
}
}
impl Display for FileType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ext().to_owned())
}
}
#[cfg(test)]
mod test {
use crate::IconFile;
use crate::search::test::test_search;
use std::collections::HashMap;
#[test]
fn test_find_all_icons() {
let icons = test_search().search().icons();
let mut map: HashMap<String, Vec<IconFile>> = Default::default();
for (_, _, icon) in icons.find_all_icons() {
map.entry(icon.icon_name().to_owned())
.or_insert_with(Default::default)
.push(icon)
}
assert_eq!(map["beautiful sunset"].len(), 3);
assert_eq!(map["happy"].len(), 2);
assert_eq!(map["pixel"].len(), 1);
assert_eq!(
map["beautiful sunset"]
.iter()
.filter(|ico| ico.file_type() == crate::FileType::Xpm)
.count(),
1
);
}
}