mirror of
https://github.com/danbulant/icon
synced 2026-06-17 05:11:20 +00:00
Implement resolve and resolve_only to collect theme candidates into theme graphs
This commit is contained in:
parent
ee9af9c9aa
commit
20b552cebd
4 changed files with 241 additions and 331 deletions
323
Cargo.lock
generated
323
Cargo.lock
generated
|
|
@ -2,77 +2,6 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "file-lock"
|
||||
version = "2.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "040b48f80a749da50292d0f47a1e2d5bf1d772f52836c07f64bfccc62ba6e664"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freedesktop_entry_parser"
|
||||
version = "1.3.0"
|
||||
|
|
@ -83,62 +12,21 @@ dependencies = [
|
|||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
|
||||
[[package]]
|
||||
name = "icon"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"freedesktop_entry_parser",
|
||||
"icon-cache",
|
||||
"once_map",
|
||||
"log",
|
||||
"thiserror 2.0.12",
|
||||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icon-cache"
|
||||
version = "0.1.1"
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5b7947ecb08aeac6a28bf829b1324d24e3f340d124531e2a90f7a5862a7c367"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"file-lock",
|
||||
"memmap2",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
|
|
@ -146,15 +34,6 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
|
@ -171,47 +50,6 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_map"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd2cae3bec3936bbed1ccc5a3343b3738858182419f9c0522c7260c80c430b0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"hashbrown",
|
||||
"parking_lot",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
|
|
@ -230,45 +68,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
|
|
@ -326,122 +125,8 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
freedesktop_entry_parser = "1.3.0"
|
||||
icon-cache = "0.1.1"
|
||||
once_map = "0.4.21"
|
||||
thiserror = "2.0.12"
|
||||
xdg = "3.0.0"
|
||||
log = { version = "0.4.27", optional = true }
|
||||
|
||||
[features]
|
||||
"log" = ["dep:log"]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use crate::icon::IconFile;
|
||||
use crate::theme::{ThemeDescriptor, ThemeParseError};
|
||||
use crate::theme::{OwnedThemeDescriptor, Theme, ThemeDescriptor, ThemeParseError};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Icons and icon themes are looked for in a set of directories.
|
||||
///
|
||||
|
|
@ -99,7 +100,191 @@ pub struct IconLocations {
|
|||
}
|
||||
|
||||
impl IconLocations {
|
||||
pub fn theme<S>(&self, internal_name: S) -> std::io::Result<ThemeDescriptor<'_>>
|
||||
pub fn resolve(self) -> Vec<Arc<Theme<'static>>> {
|
||||
let names = self.themes_directories.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
self.resolve_only(names)
|
||||
}
|
||||
|
||||
pub fn resolve_only<I, S>(self, theme_names: I) -> Vec<Arc<Theme<'static>>>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
// Icon themes may transitively depend on the same icon theme many times.
|
||||
// This is a bit of an issue, as when an exhaustive icon lookup would be implemented naively,
|
||||
// users may end up searching the same icon theme multiple times.
|
||||
// To accommodate this, either one has to keep a list of visited icon themes every time they
|
||||
// perform a lookup, or avoid the issue altogether by removing redundant parents up-front.
|
||||
|
||||
// That second option is what this function does, paying a (rather small) one-time cost to
|
||||
// make the rest of the API cleaner and smaller by guaranteeing that the returned icon themes
|
||||
// have dependencies that form a direct acyclic graph without redundant paths.
|
||||
|
||||
fn collect_themes(
|
||||
name: &OsStr,
|
||||
locations: &IconLocations,
|
||||
themes: &mut HashMap<OsString, Option<ThemeDescriptor>>,
|
||||
) {
|
||||
// Skip if we already have this theme.
|
||||
if themes.contains_key(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let descriptor = match locations.theme_description(name) {
|
||||
Ok(d) => Some(d),
|
||||
Err(_e) => {
|
||||
#[cfg(feature = "log")]
|
||||
log::debug!("skipping theme candidate {name:?} because {_e}");
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
let descriptor = themes.entry(name.to_os_string()).insert_entry(descriptor);
|
||||
|
||||
let Some(descriptor) = descriptor.get() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let parents = descriptor.index.inherits.clone();
|
||||
|
||||
// Collect all parents of this theme:
|
||||
for parent in parents {
|
||||
collect_themes(parent.as_ref().as_ref(), locations, themes);
|
||||
}
|
||||
}
|
||||
|
||||
// Map from theme names to their descriptor:
|
||||
let mut themes = HashMap::new();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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.
|
||||
|
||||
// let's prune theme candidates that have no description (meaning they weren't themes, or
|
||||
// were invalid)
|
||||
// we'll also split them up, as `theme_chains` borrows names from `theme_names`,
|
||||
// but we need to mutate theme_descriptions later (during the borrow) to avoid
|
||||
// cloning the descriptions
|
||||
let (theme_names, mut theme_descriptions): (Vec<_>, Vec<_>) = themes
|
||||
.into_iter()
|
||||
.flat_map(|(key, value)| value.map(|v| (key, Some(v))))
|
||||
.unzip();
|
||||
|
||||
// the Options are there just so we can take descriptions out of the vec without messing up the order.
|
||||
debug_assert!(theme_descriptions.iter().all(Option::is_some));
|
||||
|
||||
// do we even have hicolor?
|
||||
// if not, there's no use in inserting hicolor into the inheritance tree later
|
||||
let hicolor_idx = theme_names.iter().position(|name| name == "hicolor");
|
||||
|
||||
// Time to find the optimal ancestry for each theme.
|
||||
// as hicolor _should_ have all icons by default, and all themes depend on hicolor at some depth,
|
||||
// DFS would de facto end up in hicolor before ever trying the second theme in an Inherits set.
|
||||
// therefore BFS is the only sensible option, but the spec doesn't define this.
|
||||
|
||||
// indexed by the position in our theme_names/theme_descriptions vecs
|
||||
let number_of_themes = theme_names.len();
|
||||
let mut theme_chains = Vec::<Vec<usize>>::with_capacity(number_of_themes);
|
||||
|
||||
for theme_idx in 0..number_of_themes {
|
||||
let mut chain = Vec::from([theme_idx]);
|
||||
|
||||
let mut cursor = 0;
|
||||
while let Some(node_idx) = chain.get(cursor).copied() {
|
||||
cursor += 1;
|
||||
|
||||
let Some(Some(description)) = theme_descriptions.get(node_idx) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for parent in &description.index.inherits {
|
||||
let Some(parent_idx) = theme_names
|
||||
.iter()
|
||||
.position(|name| *name.as_os_str() == **parent)
|
||||
else {
|
||||
// this parent was invalid
|
||||
continue;
|
||||
};
|
||||
|
||||
if !chain.contains(&parent_idx) {
|
||||
chain.push(parent_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the spec: "If no theme is specified, implementations are required to add the
|
||||
// "hicolor" theme to the inheritance tree."
|
||||
if let Some(hicolor_idx) = hicolor_idx {
|
||||
if !chain.contains(&hicolor_idx) {
|
||||
chain.push(hicolor_idx);
|
||||
}
|
||||
}
|
||||
|
||||
theme_chains.push(chain);
|
||||
}
|
||||
|
||||
// at this point `theme_chains` contains a _topological order_ for each theme's parents,
|
||||
// meaning we can easily iterate over it, constructing `Theme`s, assuming at every point
|
||||
// that each parent already has a `Theme` created for it :)
|
||||
|
||||
// again indexed by theme indices, None values mean the theme hasn't been processed yet.
|
||||
// the goal is that, by the end of the for loop, that this only contains `Some`s.
|
||||
// we rely on the topological order of chains to always have all the prerequisite themes
|
||||
// present already in this map!
|
||||
let mut full_themes = vec![None::<Arc<Theme>>; number_of_themes];
|
||||
|
||||
for chain in &theme_chains {
|
||||
// go from last theme to first, as all dependencies are "forward" in the chain:
|
||||
for theme_idx in chain.iter().copied().rev() {
|
||||
let theme_desc = theme_descriptions[theme_idx].take();
|
||||
|
||||
let Some(theme_desc) = theme_desc else {
|
||||
// the option was None, meaning this theme was processed already :-)
|
||||
continue;
|
||||
};
|
||||
|
||||
let parents = &theme_chains[theme_idx];
|
||||
let parents = parents
|
||||
.into_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
|
||||
// should already be present in the array:
|
||||
.map(|parent_idx| Arc::clone(full_themes[parent_idx].as_ref().unwrap()))
|
||||
.collect();
|
||||
|
||||
let theme = Theme {
|
||||
description: theme_desc,
|
||||
parents,
|
||||
};
|
||||
|
||||
full_themes[theme_idx] = Some(Arc::new(theme));
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(full_themes.iter().all(Option::is_some));
|
||||
|
||||
let full_themes: Vec<_> = full_themes.into_iter().map(Option::unwrap).collect();
|
||||
|
||||
// and so, we have reached the end of the Big Beautiful Function.
|
||||
// `full_themes` is a list of
|
||||
// - All themes requested,
|
||||
// - all themes required by the inheritance tree of those themes, without duplicates,
|
||||
// - and an optimal chain (inheritance tree search order) for each theme.
|
||||
|
||||
full_themes
|
||||
}
|
||||
|
||||
pub fn theme_description<S>(&self, internal_name: S) -> std::io::Result<OwnedThemeDescriptor>
|
||||
where
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
|
|
@ -121,8 +306,9 @@ impl IconLocations {
|
|||
S: AsRef<OsStr>,
|
||||
{
|
||||
let name = icon_name.as_ref();
|
||||
|
||||
self.standalone_icons.iter()
|
||||
|
||||
self.standalone_icons
|
||||
.iter()
|
||||
.find(|icon| icon.path.file_stem() == Some(name))
|
||||
}
|
||||
}
|
||||
|
|
@ -176,11 +362,28 @@ mod test {
|
|||
let dirs = SearchDirectories::default();
|
||||
|
||||
let locations = dirs.find_icon_locations();
|
||||
|
||||
let descriptor = locations.theme("Adwaita").unwrap();
|
||||
|
||||
let descriptor = locations.theme_description("Adwaita").unwrap();
|
||||
assert_eq!(descriptor.index.name, "Adwaita");
|
||||
|
||||
let icon = locations.standalone_icon("htop").unwrap();
|
||||
assert_eq!(icon.path.file_name(), Some("htop.png".as_ref()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2() {
|
||||
let result = SearchDirectories::default()
|
||||
.find_icon_locations()
|
||||
.theme_description("breeze")
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", result.index.inherits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let _dirs = SearchDirectories::default().find_icon_locations().resolve();
|
||||
|
||||
// it didn't panic.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/theme.rs
28
src/theme.rs
|
|
@ -1,12 +1,27 @@
|
|||
use crate::icon::IconFile;
|
||||
use crate::theme::ThemeParseError::MissingRequiredAttribute;
|
||||
use freedesktop_entry_parser::low_level::{EntryIter, SectionBytes};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type OwnedIcons = Icons<'static>;
|
||||
pub type OwnedThemeDescriptor = ThemeDescriptor<'static>;
|
||||
pub type OwnedThemeIndex = ThemeIndex<'static>;
|
||||
pub type OwnedDirectoryIndex = DirectoryIndex<'static>;
|
||||
|
||||
pub struct Icons<'a> {
|
||||
pub standalone_icons: Vec<IconFile>,
|
||||
pub themes: HashMap<OsString, Arc<Theme<'a>>>,
|
||||
}
|
||||
|
||||
pub struct Theme<'a> {
|
||||
pub description: ThemeDescriptor<'a>,
|
||||
pub parents: Vec<Arc<Theme<'a>>>,
|
||||
}
|
||||
|
||||
pub struct ThemeDescriptor<'a> {
|
||||
pub internal_name: String,
|
||||
pub base_dirs: Vec<PathBuf>,
|
||||
|
|
@ -50,7 +65,7 @@ impl ThemeDescriptor<'_> {
|
|||
index,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn into_owned(self) -> OwnedThemeDescriptor {
|
||||
theme_into_owned(self)
|
||||
}
|
||||
|
|
@ -90,11 +105,16 @@ impl<'a> ThemeIndex<'a> {
|
|||
let icon_theme_section: SectionBytes<'a> =
|
||||
entry.next().ok_or(ThemeParseError::NotAnIconTheme)??;
|
||||
let name: &'a str = find_attr_req(&icon_theme_section, "Name")?;
|
||||
let comment = find_attr_req(&icon_theme_section, "Comment")?;
|
||||
|
||||
// SPEC: `Comment` is required, but most icon theme developers can't actually be arsed to
|
||||
// include it! To make `icon` practical, we choose a default of an empty string instead.
|
||||
// let comment = find_attr_req(&icon_theme_section, "Comment")?;
|
||||
let comment = find_attr(&icon_theme_section, "Comment")?
|
||||
.unwrap_or("");
|
||||
// If no theme is specified, implementations are required to add the "hicolor" theme to the inheritance tree.
|
||||
let inherits = find_attr(&icon_theme_section, "Inherits")?
|
||||
.unwrap_or("hicolor")
|
||||
.split(',') // `inherits` is a comma-separated string list
|
||||
.iter()
|
||||
.flat_map(|s| s.split(',')) // `inherits` is a comma-separated string list
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
let directories = find_attr_req(&icon_theme_section, "Directories")?
|
||||
|
|
|
|||
Loading…
Reference in a new issue