icon_loader/
icon.rs

1mod icon_dir;
2mod icon_file;
3mod icon_theme;
4
5pub use icon_dir::{IconDir, IconSizeType};
6pub use icon_file::{IconFile, IconFileType};
7
8pub(crate) use icon_theme::IconThemes;
9
10/// Struct containing information about a themed icon.
11#[derive(Clone, Debug, Hash, PartialEq, Eq)]
12pub struct Icon {
13    icon_name: String,
14    theme_name: String,
15    files: Vec<IconFile>,
16}
17
18impl Icon {
19    /// Returns the associated icon's name.
20    pub fn icon_name(&self) -> &str {
21        &self.icon_name
22    }
23
24    /// Returns the associated icon's theme name.
25    pub fn theme_name(&self) -> &str {
26        &self.theme_name
27    }
28
29    /// Returns the icon files found for the associated icon.
30    pub fn files(&self) -> &[IconFile] {
31        &self.files
32    }
33
34    /// Returns the file of the associated icon that fits the given size best and has a scale of 1.
35    /// If there is no exact fit available, the next bigger one is chosen.
36    /// If there is no bigger one, the next smaller one is returned.
37    /// If that cannot be found, the scale restriction is ignored.
38    ///
39    /// # Arguments
40    ///
41    /// * `size` - The ideal size of the returned icon file.
42    ///
43    ///# Example
44    ///
45    /// ```
46    /// use std::ops::Deref;
47    /// use icon_loader::IconLoader;
48    ///
49    /// let loader = IconLoader::new();
50    /// if let Some(icon) = loader.load_icon("minimum") {
51    ///     let icon_file = icon.file_for_size(32);
52    /// };
53    /// ```
54    pub fn file_for_size(&self, size: u16) -> &IconFile {
55        self.file_for_size_scaled(size, 1)
56    }
57
58    /// Returns the file of the associated icon that fits the given size and scale best.
59    /// If there is no exact fit available, the next bigger size is chosen.
60    /// If there is no bigger fit with the given scale, the next smaller one is returned.
61    /// If no file with the preferred scale can be found, one with the size `size * scale` and scale 1 is looked for.
62    /// If that cannot be found, the scale restriction is ignored.
63    ///
64    /// # Arguments
65    ///
66    /// * `size` - The ideal size of the returned icon file.
67    /// * `scale` - The preferred scale of the returned icon file.
68    ///
69    ///# Example
70    ///
71    /// ```
72    /// use std::ops::Deref;
73    /// use icon_loader::IconLoader;
74    ///
75    /// let loader = IconLoader::new();
76    /// if let Some(icon) = loader.load_icon("minimum") {
77    ///     let icon_file = icon.file_for_size_scaled(32, 2);
78    /// };
79    /// ```
80    pub fn file_for_size_scaled(&self, size: u16, scale: u16) -> &IconFile {
81        if let Some(file) = self.file_for_size_filtered(size, |file| file.scale() == scale) {
82            return file;
83        }
84
85        if let Some(file) = self.file_for_size_filtered(size * scale, |file| file.scale() == 1) {
86            return file;
87        }
88
89        // If we don't filter, there is always at least one file on disk.
90        self.file_for_size_filtered(size, |_| true).unwrap()
91    }
92
93    /// Returns the file of the associated icon that fits the given size best and matches the provided filter.
94    /// If there is no exact fit available, the next bigger one is chosen.
95    /// If there is no bigger one, the next smaller one is returned.
96    /// Use this, if you want only files of type PNG or anything like that.
97    ///
98    /// # Arguments
99    ///
100    /// * `size` - The ideal size of the returned icon file.
101    /// * `filter` - A function that takes a reference to an [`IconFile`](icon::IconFile) and returns true, if it passes the test and false otherwise.
102    ///
103    /// # Example
104    ///
105    /// ```
106    /// use std::ops::Deref;
107    /// use icon_loader::{IconLoader, IconFileType};
108    ///
109    /// let loader = IconLoader::new();
110    /// if let Some(icon) = loader.load_icon("minimum") {
111    ///     let icon_file = icon.file_for_size_filtered(32, |file| file.icon_type() == IconFileType::PNG);
112    /// };
113    /// ```
114    pub fn file_for_size_filtered(
115        &self,
116        size: u16,
117        filter: impl Fn(&IconFile) -> bool,
118    ) -> Option<&IconFile> {
119        let files = self.files.iter().filter(|&file| filter(file));
120
121        // Try to return an exact fit.
122        if let Some(icon_file) = files.clone().find(|file| file.dir_info().size() == size) {
123            return Some(icon_file);
124        }
125
126        // Try to return a threshold fit.
127        if let Some(icon_file) = files
128            .clone()
129            .filter(|file| file.dir_info().size_type() == IconSizeType::Threshold)
130            .find(|file| {
131                size >= file.dir_info().size() - file.dir_info().threshold()
132                    && size <= file.dir_info().size() + file.dir_info().threshold()
133            })
134        {
135            return Some(icon_file);
136        }
137
138        // Try to return a slightly bigger fit.
139        if let Some(icon_file) = files
140            .clone()
141            .filter(|file| file.dir_info().size() > size)
142            .min_by_key(|file| file.dir_info().size())
143        {
144            return Some(icon_file);
145        }
146
147        // Return the biggest available.
148        files.max_by_key(|file| file.dir_info().size())
149    }
150
151    pub(crate) fn new(icon_name: String, theme_name: String, files: Vec<IconFile>) -> Option<Self> {
152        if icon_name.is_empty() || theme_name.is_empty() || files.is_empty() {
153            None
154        } else {
155            Some(Self {
156                files,
157                icon_name,
158                theme_name,
159            })
160        }
161    }
162}