1use std::{borrow::Cow, ops::Deref, path::PathBuf, sync::Arc};
2
3use crate::{
4 error::{Error, Result},
5 icon::{Icon, IconThemes},
6 search_paths::SearchPaths,
7 theme_name_provider::ThemeNameProvider,
8};
9
10use dashmap::DashMap;
11
12#[derive(Debug)]
16pub struct IconLoader {
17 theme_name: String,
18 fallback_theme_name: String,
19 theme_cache: DashMap<String, IconThemes>,
20 icon_cache: DashMap<String, Option<Arc<Icon>>>,
21 search_paths: SearchPaths,
22 theme_name_provider: ThemeNameProvider,
23}
24
25impl IconLoader {
26 pub fn new() -> Self {
28 IconLoader {
29 theme_name: String::from("hicolor"),
30 fallback_theme_name: String::from("hicolor"),
31 theme_name_provider: Default::default(),
32 theme_cache: Default::default(),
33 icon_cache: Default::default(),
34 search_paths: Default::default(),
35 }
36 }
37
38 #[cfg(feature = "kde")]
41 pub fn new_kde() -> Result<Self> {
42 let mut loader = Self::new();
43 loader.set_theme_name_provider(ThemeNameProvider::KDE);
44 loader.update_theme_name()?;
45
46 Ok(loader)
47 }
48
49 #[cfg(feature = "gtk")]
52 pub fn new_gtk() -> Result<Self> {
53 let mut loader = Self::new();
54 loader.set_theme_name_provider(ThemeNameProvider::GTK);
55 loader.update_theme_name()?;
56
57 Ok(loader)
58 }
59
60 pub fn load_icon(&self, icon_name: impl AsRef<str>) -> Option<Arc<Icon>> {
64 let icon_name = icon_name.as_ref();
65
66 if !self.icon_cache.contains_key(icon_name) {
67 let mut searched_themes = Vec::new();
68
69 let icon = self
70 .find_icon(self.theme_name(), icon_name, &mut searched_themes)
71 .or_else(|| {
72 self.find_icon(self.fallback_theme_name(), icon_name, &mut searched_themes)
73 })
74 .map(Arc::new);
75
76 self.icon_cache.insert(icon_name.into(), icon);
77 }
78
79 self.icon_cache.get(icon_name).unwrap().value().clone()
81 }
82
83 pub fn theme_name(&self) -> &str {
87 &self.theme_name
88 }
89
90 pub fn fallback_theme_name(&self) -> &str {
94 &self.fallback_theme_name
95 }
96
97 pub fn search_paths(&self) -> Cow<[PathBuf]> {
99 self.search_paths.paths()
100 }
101
102 pub fn set_search_paths(&mut self, search_paths: impl Into<SearchPaths>) {
129 let search_paths = search_paths.into();
130
131 if self.search_paths == search_paths {
132 return;
133 }
134
135 self.search_paths = search_paths;
136 self.theme_cache.clear();
137 self.icon_cache.clear();
138 }
139
140 pub fn set_theme_name_provider(&mut self, theme_name_provider: impl Into<ThemeNameProvider>) {
167 let theme_name_provider = theme_name_provider.into();
168
169 if self.theme_name_provider == theme_name_provider {
170 return;
171 }
172
173 self.theme_name_provider = theme_name_provider;
174 }
175
176 pub fn update_theme_name(&mut self) -> Result<()> {
181 let theme_name = self.theme_name_provider.theme_name()?;
182
183 if self.theme_name == theme_name {
184 return Ok(());
185 }
186
187 if self.theme_exists(&theme_name) {
188 self.theme_name = theme_name;
189 self.icon_cache.clear();
190
191 Ok(())
192 } else {
193 Err(Error::theme_not_found(theme_name))
194 }
195 }
196
197 pub fn set_fallback_theme_name(&mut self, fallback_theme_name: impl Into<String>) {
201 let fallback_theme_name = fallback_theme_name.into();
202
203 if self.fallback_theme_name == fallback_theme_name {
204 return;
205 }
206
207 self.fallback_theme_name = fallback_theme_name;
208 self.icon_cache.clear();
209 }
210
211 pub fn theme_exists(&self, theme_name: impl AsRef<str>) -> bool {
213 let theme_name = theme_name.as_ref();
214
215 if theme_name.is_empty() {
216 return false;
217 }
218
219 !self.load_themes(theme_name).is_empty()
220 }
221
222 fn load_themes<'a>(&'a self, theme_name: &'a str) -> impl Deref<Target = IconThemes> + 'a {
223 if !self.theme_cache.contains_key(theme_name) {
224 let new_themes = IconThemes::find(theme_name, &self.search_paths());
225
226 self.theme_cache.insert(theme_name.into(), new_themes);
227 }
228
229 self.theme_cache.get(theme_name).unwrap()
231 }
232
233 fn find_icon(
234 &self,
235 theme_name: &str,
236 icon_name: &str,
237 searched_themes: &mut Vec<String>,
238 ) -> Option<Icon> {
239 if theme_name.is_empty() || icon_name.is_empty() {
240 return None;
241 }
242
243 searched_themes.push(theme_name.into());
244
245 let themes = self.load_themes(theme_name);
246
247 if let Some(icon) = themes.find_icon(icon_name) {
248 return Some(icon);
249 }
250
251 for parent in themes.parents() {
252 if !searched_themes.contains(parent) {
253 if let Some(icon) = self.find_icon(parent, icon_name, searched_themes) {
254 return Some(icon);
255 }
256 }
257 }
258
259 None
260 }
261}
262
263impl Default for IconLoader {
264 fn default() -> Self {
265 IconLoader::new()
266 }
267}