diff --git a/src/lib.rs b/src/lib.rs index ad8aee9..b02512d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! let icons = icon::Icons::new(); //! //! let firefox: Option = icons.find_icon("firefox", 128, 1, "Adwaita"); -//! +//! //! println!("Firefox icon is at {:?}", firefox.unwrap().path) //! ``` //! @@ -23,29 +23,25 @@ //! //! 1. *Finding standalone icons and themes*: //! -//! Icons are found either in icon themes, or 'standalone' (outside a theme) in XDG base directories. +//! Icons are found either in icon themes or 'standalone' (outside a theme) in XDG base directories. //! While a number of directories should always be scanned for icons, the user or application is //! allowed to search additional directories as it sees fit. //! //! [IconSearch] handles this part, and is also the main entrypoint for `icon`. //! -//! 2. *Parsing icon themes*: +//! 2. *Parsing and resolving icon themes*: //! //! Each icon theme lives in a directory in the root of one or more of the "search directories". //! The name of its directory is called the theme's _internal name_, and in it lies the theme's //! definition, `index.theme`. //! -//! To find icons in a theme, its `index.theme` file must be parsed to understand the directory -//! structure within the theme itself. -//! This is handled by [theme::ThemeInfo]. +//! Icon themes also can declare other icon themes from which they inherit. For that reason, +//! we also need to do the additional work of figuring out the inheritance tree of each theme, +//! removing (transitive) duplicates from the inheritance tree, etc. //! -//! 3a. *Find just one icon* (oneshot): +//! 3. *Finding icons*: //! -//! // TODO -//! -//! 3b. *Find many icons*: -//! -//! // TODO +//! Once a full picture of icons and theme 'graphs' is obtained, we can start looking up icons. //! //! # Alternative crates //! diff --git a/src/search.rs b/src/search.rs index 5b338cb..e50dacd 100644 --- a/src/search.rs +++ b/src/search.rs @@ -64,7 +64,7 @@ pub mod states { /// ``` /// use icon::IconSearch; /// -/// let icons = IconSearch::default() +/// let icons = IconSearch::new() /// // (optional) add directories to search /// .add_directories(["/some/additional/directory/"]) /// // find icons and folders @@ -92,7 +92,7 @@ impl IconSearch { /// - `/usr/share/pixmaps` /// /// If you wish to add directories to those, use this function and then [`add_directories`](Self::add_directories). - pub fn default() -> Self { + pub fn new() -> Self { ::default() } @@ -118,7 +118,7 @@ impl IconSearch { /// ``` /// use icon::IconSearch; /// - /// let dirs = IconSearch::default() + /// let dirs = IconSearch::new() /// .add_directories(["/home/root/.icons"]) /// .search() /// .icons(); @@ -202,8 +202,7 @@ impl IconSearch { /// /// Contained search directories are lost. pub fn into_icon_locations(self) -> IconLocations { - let icons = self.icon_locations.expect("guaranteed by type-state"); - icons + self.icon_locations.expect("guaranteed by type-state") } // -- STAGE 3: We have icon theme candidates, so it's time to resolve them. @@ -249,7 +248,7 @@ impl IconLocations { /// Prefer following the normal flow instead: /// ```rust /// use icon::IconSearch; - /// let search = IconSearch::default() + /// let search = IconSearch::new() /// .search(); /// /// let locations = search.into_icon_locations(); @@ -296,6 +295,7 @@ impl IconLocations { return; } + #[allow(clippy::manual_ok_err)] // clippy doesn't see the #[cfg] let info = match locations.load_single_theme(name) { Ok(d) => Some(d), Err(_e) => { @@ -325,12 +325,12 @@ impl IconLocations { // collect all required themes: for theme_name in theme_names { let theme_name = theme_name.as_ref(); - collect_themes(theme_name.as_ref(), &self, &mut themes); + collect_themes(theme_name, self, &mut themes); } // make 100% sure we have `hicolor`, for the half-impossible edge-case of only collecting // themes that does not have hicolor in their inheritance tree - collect_themes("hicolor".as_ref(), &self, &mut themes); + collect_themes("hicolor".as_ref(), self, &mut themes); // of course, the user might be cursed and not have `hicolor` installed at all! // that is troubling, but we'll see that it is handled correctly below. @@ -419,7 +419,7 @@ impl IconLocations { let parents = &theme_chains[theme_idx]; let parents = parents - .into_iter() + .iter() .skip(1) // the first in the chain is the theme itself, which we'll ignore—it's not a parent. .copied() // unwrap OK because, by the topological order, all of these parents @@ -514,7 +514,7 @@ impl Default for IconSearch { xdg.data_home .into_iter() - .chain(xdg.data_dirs.into_iter()) + .chain(xdg.data_dirs) .map(|data_dir| data_dir.join("icons")) .for_each(|dir| directories.push(dir)); @@ -532,7 +532,7 @@ mod test { #[test] fn test_standard_usage() { - let _icons = IconSearch::default() + let _icons = IconSearch::new() .add_directories(["/this/path/probably/doesnt/exist/but/who/cares/"]) .search() .icons(); @@ -542,7 +542,7 @@ mod test { #[test] fn test_find_standard_theme_and_icon() { - let dirs = IconSearch::default(); + let dirs = IconSearch::new(); let locations = dirs.find_icon_locations(); diff --git a/src/theme.rs b/src/theme.rs index 2407e74..a432207 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -7,6 +7,18 @@ use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::sync::Arc; +/// Main struct to locate icon files. +/// +/// Create this using [`Icons::new`] for the standard configuration, or use [`IconSearch`] if you +/// wish to tune where icons can be found. +/// +/// # Example +/// +/// ```rust +/// use icon::Icons; +/// +/// Icons::new().find_icon("firefox", 32, 1, "hicolor"); +/// ``` pub struct Icons { pub standalone_icons: Vec, pub themes: HashMap>, @@ -18,22 +30,34 @@ impl Icons { /// This function collects all standalone icons and icon themes on the system. /// To configure what directories are searched, use [`IconSearch`] instead. pub fn new() -> Self { - IconSearch::default().search().icons() + IconSearch::new().search().icons() } + /// Access a known icon theme by name pub fn theme(&self, theme_name: &str) -> Option> { let theme_name: &OsStr = theme_name.as_ref(); self.themes.get(theme_name).cloned() } + /// Like [`find_icon`], with `theme` being `"hicolor"`, which is the default icon theme. pub fn find_default_icon(&self, icon_name: &str, size: u32, scale: u32) -> Option { self.find_icon(icon_name, size, scale, "hicolor") } /// Look up an icon by name, size, scale and theme. /// - /// If the icon is not found in the theme, its parents are checked. - /// If no theme by the given name exists, the `"hicolor"` theme (default theme) is checked. + /// - If no theme by the given name exists, the `"hicolor"` theme (default theme) is used instead. + /// - If the icon is not found in the provided theme, its parents are checked. + /// - If the icon is not found in any of the themes, the standalone icon list is checked. + /// + /// # Icon matching + /// + /// This function will return an icon matching the specified size and scale exactly if it exists. + /// Otherwise, an icon with the smallest "distance" (in icon size) is returned. + /// + /// This will only return `None` if no icon by the specified name exists in the specified theme + /// and its parents, and no standalone icon by the same name exists either. + /// pub fn find_icon( &self, icon_name: &str, @@ -47,6 +71,12 @@ impl Icons { .or_else(|| self.find_standalone_icon(icon_name)) } + /// Look up a standalone icon by name. + /// + /// "Standalone" icons are icons that live outside icon themes, residing at the root in the + /// search directories instead. + /// + /// These icons do not have any size or scalability information attached to them. pub fn find_standalone_icon(&self, icon_name: &str) -> Option { self.standalone_icons .iter() @@ -55,6 +85,12 @@ impl Icons { } } +impl Default for Icons { + fn default() -> Self { + Self::new() + } +} + pub struct Theme { pub info: ThemeInfo, pub inherits_from: Vec>, @@ -74,8 +110,9 @@ impl Theme { }) } + // find an icon in this theme only, not checking parents. fn find_icon_here(&self, icon_name: &str, size: u32, scale: u32) -> Option { - const EXTENSIONS: [&'static str; 3] = ["png", "xmp", "svg"]; + const EXTENSIONS: [&str; 3] = ["png", "xmp", "svg"]; let file_names = EXTENSIONS.map(|ext| format!("{icon_name}.{ext}")); let base_dirs = &self.info.base_dirs; @@ -83,7 +120,7 @@ impl Theme { let sub_dirs = &self.info.index.directories; // first, try to find an exact icon size match: let exact_sub_dirs = sub_dirs - .into_iter() + .iter() .filter(|sub_dir| sub_dir.matches_size(size, scale)); for base_dir in base_dirs { @@ -456,7 +493,7 @@ mod test { "lstopo", "signon-ui", ]; - + let mut time_taken = Duration::ZERO; let mut n = 0; @@ -480,13 +517,13 @@ mod test { } let then = Instant::now(); - + // TODO: perhaps our system should expose a way to construct a "composed theme" filter, // for cases where you want to search a multitude (or all) themes let icon = icons .find_icon(icon_name, 32, 1, "gnome") .or_else(|| icons.find_icon(icon_name, 32, 1, "breeze")); - + time_taken += Instant::now() - then; n += 1;