1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
mod icon_dir;
mod icon_file;
mod icon_theme;

pub use icon_dir::{IconDir, IconSizeType};
pub use icon_file::{IconFile, IconFileType};
pub use icon_theme::error::{Error, Result};

pub(crate) use icon_theme::IconThemes;

/// Struct containing information about a themed icon.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Icon {
    icon_name: String,
    theme_name: String,
    files: Vec<IconFile>,
}

impl Icon {
    /// Returns the associated icon's name.
    pub fn icon_name(&self) -> &str {
        &self.icon_name
    }

    /// Returns the associated icon's theme name.
    pub fn theme_name(&self) -> &str {
        &self.theme_name
    }

    /// Returns the icon files found for the associated icon.
    pub fn files(&self) -> &[IconFile] {
        &self.files
    }

    /// Returns the file of the associated icon that fits the given size best and has a scale of 1.
    /// If there is no exact fit available, the next bigger one is chosen.
    /// If there is no bigger one, the next smaller one is returned.
    /// If that cannot be found, the scale restriction is ignored.
    ///
    /// # Arguments
    ///
    /// * `size` - The ideal size of the returned icon file.
    ///
    ///# Example
    ///
    /// ```
    /// use icon_loader::IconLoader;
    ///
    /// let loader = IconLoader::new();
    /// if let Ok(icon) = loader.load_icon("minimum") {
    ///     let icon_file = icon.file_for_size(32);
    /// }
    /// ```
    pub fn file_for_size(&self, size: u16) -> &IconFile {
        self.file_for_size_scaled(size, 1)
    }

    /// Returns the file of the associated icon that fits the given size and scale best.
    /// If there is no exact fit available, the next bigger size is chosen.
    /// If there is no bigger fit with the given scale, the next smaller one is returned.
    /// If no file with the preferred scale can be found, one with the size `size * scale` and scale 1 is looked for.
    /// If that cannot be found, the scale restriction is ignored.
    ///
    /// # Arguments
    ///
    /// * `size` - The ideal size of the returned icon file.
    /// * `scale` - The preferred scale of the returned icon file.
    ///
    ///# Example
    ///
    /// ```
    /// use icon_loader::IconLoader;
    ///
    /// let loader = IconLoader::new();
    /// if let Ok(icon) = loader.load_icon("minimum") {
    ///     let icon_file = icon.file_for_size_scaled(32, 2);
    /// }
    /// ```
    pub fn file_for_size_scaled(&self, size: u16, scale: u16) -> &IconFile {
        if let Some(file) = self.file_for_size_filtered(size, |file| file.scale() == scale) {
            return file;
        }

        if size != 1 {
            if let Some(file) = self.file_for_size_filtered(size * scale, |file| file.scale() == 1)
            {
                return file;
            }
        }

        // If we don't filter, there is always at least one file on disk.
        self.file_for_size_filtered(size, |_| true).unwrap()
    }

    /// Returns the file of the associated icon that fits the given size best and matches the provided filter.
    /// If there is no exact fit available, the next bigger one is chosen.
    /// If there is no bigger one, the next smaller one is returned.
    /// Use this, if you want only files of type PNG or anything like that.
    ///
    /// # Arguments
    ///
    /// * `size` - The ideal size of the returned icon file.
    /// * `filter` - A function that takes a reference to an [`IconFile`] and returns true, if it passes the test and false otherwise.
    ///
    /// # Example
    ///
    /// ```
    /// use icon_loader::{IconLoader, IconFileType};
    ///
    /// let loader = IconLoader::new();
    /// if let Ok(icon) = loader.load_icon("minimum") {
    ///     let icon_file = icon.file_for_size_filtered(32, |file| file.icon_type() == IconFileType::PNG);
    /// }
    /// ```
    ///
    /// [`IconFile`]: struct.IconFile.html
    pub fn file_for_size_filtered(
        &self,
        size: u16,
        filter: impl Fn(&IconFile) -> bool,
    ) -> Option<&IconFile> {
        let files = self.files.iter().filter(|&file| filter(file));

        // Try to return an exact fit.
        if let Some(icon_file) = files.clone().find(|file| file.dir_info().size() == size) {
            return Some(icon_file);
        }

        // Try to return a threshold fit.
        if let Some(icon_file) = files
            .clone()
            .filter(|file| file.dir_info().size_type() == IconSizeType::Threshold)
            .find(|file| {
                size >= file.dir_info().size() - file.dir_info().threshold()
                    && size <= file.dir_info().size() + file.dir_info().threshold()
            })
        {
            return Some(icon_file);
        }

        // Try to return a slightly bigger fit.
        if let Some(icon_file) = files
            .clone()
            .filter(|file| file.dir_info().size() > size)
            .min_by_key(|file| file.dir_info().size())
        {
            return Some(icon_file);
        }

        // Return the biggest available.
        files.max_by_key(|file| file.dir_info().size())
    }

    pub(crate) fn new(icon_name: String, theme_name: String, files: Vec<IconFile>) -> Option<Self> {
        if icon_name.is_empty() || theme_name.is_empty() || files.is_empty() {
            None
        } else {
            Some(Self {
                files,
                icon_name,
                theme_name,
            })
        }
    }
}