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_cache: DashMap::new(),
32 icon_cache: DashMap::new(),
33 search_paths: Default::default(),
34 theme_name_provider: 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 let Some(icon) = self.icon_cache.get(icon_name) {
67 return icon.clone();
68 }
69
70 let mut searched_themes = Vec::new();
71
72 let icon = self
73 .find_icon(self.theme_name(), icon_name, &mut searched_themes)
74 .or_else(|| self.find_icon(self.fallback_theme_name(), icon_name, &mut searched_themes))
75 .map(Arc::new);
76
77 self.icon_cache.insert(icon_name.into(), icon.clone());
78
79 icon
80 }
81
82 pub fn theme_name(&self) -> &str {
86 &self.theme_name
87 }
88
89 pub fn fallback_theme_name(&self) -> &str {
93 &self.fallback_theme_name
94 }
95
96 pub fn search_paths(&self) -> Cow<[PathBuf]> {
98 self.search_paths.paths()
99 }
100
101 pub fn set_search_paths(&mut self, search_paths: impl Into<SearchPaths>) {
128 let search_paths = search_paths.into();
129
130 if self.search_paths == search_paths {
131 return;
132 }
133
134 self.search_paths = search_paths;
135 self.theme_cache.clear();
136 self.icon_cache.clear();
137 }
138
139 pub fn set_theme_name_provider(&mut self, theme_name_provider: impl Into<ThemeNameProvider>) {
166 let theme_name_provider = theme_name_provider.into();
167
168 if self.theme_name_provider == theme_name_provider {
169 return;
170 }
171
172 self.theme_name_provider = theme_name_provider;
173 }
174
175 pub fn update_theme_name(&mut self) -> Result<()> {
180 let theme_name = self.theme_name_provider.theme_name()?;
181
182 if self.theme_name == theme_name {
183 return Ok(());
184 }
185
186 if self.theme_exists(&theme_name) {
187 self.theme_name = theme_name;
188 self.icon_cache.clear();
189
190 Ok(())
191 } else {
192 Err(Error::theme_not_found(theme_name))
193 }
194 }
195
196 pub fn set_fallback_theme_name(&mut self, fallback_theme_name: impl Into<String>) {
200 let fallback_theme_name = fallback_theme_name.into();
201
202 if self.fallback_theme_name == fallback_theme_name {
203 return;
204 }
205
206 self.fallback_theme_name = fallback_theme_name;
207 self.icon_cache.clear();
208 }
209
210 pub fn theme_exists(&self, theme_name: impl AsRef<str>) -> bool {
212 let theme_name = theme_name.as_ref();
213
214 if theme_name.is_empty() {
215 return false;
216 }
217
218 !self.load_themes(theme_name).is_empty()
219 }
220
221 fn load_themes<'a>(
222 &'a self,
223 theme_name: impl AsRef<str> + Into<String>,
224 ) -> impl Deref<Target = IconThemes> + 'a {
225 let theme_name_ref = theme_name.as_ref();
226
227 if let Some(themes) = self.theme_cache.get(theme_name_ref) {
228 return themes;
229 }
230
231 let new_themes = IconThemes::find(theme_name_ref, &self.search_paths());
232
233 self.theme_cache
234 .entry(theme_name.into())
235 .insert(new_themes)
236 .downgrade()
237 }
238
239 fn find_icon(
240 &self,
241 theme_name: impl AsRef<str> + Into<String>,
242 icon_name: &str,
243 searched_themes: &mut Vec<String>,
244 ) -> Option<Icon> {
245 let theme_name_ref = theme_name.as_ref();
246
247 if theme_name_ref.is_empty() || icon_name.is_empty() {
248 return None;
249 }
250
251 let themes = self.load_themes(theme_name_ref);
252
253 if let Some(icon) = themes.find_icon(icon_name) {
254 return Some(icon);
255 }
256
257 let parents = themes.parents().to_vec();
259 drop(themes);
260
261 searched_themes.push(theme_name.into());
262
263 for parent in parents {
264 if !searched_themes.contains(&parent) {
265 if let Some(icon) = self.find_icon(parent, icon_name, searched_themes) {
266 return Some(icon);
267 }
268 }
269 }
270
271 None
272 }
273}
274
275impl Default for IconLoader {
276 fn default() -> Self {
277 IconLoader::new()
278 }
279}