Docs, style, lints.

This commit is contained in:
Ridan Vandenbergh 2025-06-21 13:16:38 +02:00
parent 06c0eec062
commit 0b3e9a9987
No known key found for this signature in database
3 changed files with 65 additions and 32 deletions

View file

@ -8,7 +8,7 @@
//! let icons = icon::Icons::new();
//!
//! let firefox: Option<icon::IconFile> = 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
//!

View file

@ -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<Initial> {
/// - `/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 {
<Self as Default>::default()
}
@ -118,7 +118,7 @@ impl IconSearch<Initial> {
/// ```
/// use icon::IconSearch;
///
/// let dirs = IconSearch::default()
/// let dirs = IconSearch::new()
/// .add_directories(["/home/root/.icons"])
/// .search()
/// .icons();
@ -202,8 +202,7 @@ impl IconSearch<LocationsFound> {
///
/// 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();

View file

@ -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<IconFile>,
pub themes: HashMap<OsString, Arc<Theme>>,
@ -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<Arc<Theme>> {
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<IconFile> {
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<IconFile> {
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<Arc<Theme>>,
@ -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<IconFile> {
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;