mirror of
https://github.com/danbulant/icon
synced 2026-05-19 04:08:36 +00:00
Docs, style, lints.
This commit is contained in:
parent
06c0eec062
commit
0b3e9a9987
3 changed files with 65 additions and 32 deletions
20
src/lib.rs
20
src/lib.rs
|
|
@ -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
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
53
src/theme.rs
53
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<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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue