cosmic/widget/icon/
named.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::{Handle, Icon};
5use std::{borrow::Cow, path::PathBuf, sync::Arc};
6
7#[derive(Debug, Clone, Default, Hash)]
8/// Fallback icon to use if the icon was not found.
9pub enum IconFallback {
10    #[default]
11    /// Default fallback using the icon name.
12    Default,
13    /// Fallback to specific icon names.
14    Names(Vec<Cow<'static, str>>),
15}
16
17#[must_use]
18#[derive(derive_setters::Setters, Clone, Debug, Hash)]
19pub struct Named {
20    /// Name of icon to locate in an XDG icon path.
21    pub(super) name: Arc<str>,
22
23    /// Checks for a fallback if the icon was not found.
24    pub fallback: Option<IconFallback>,
25
26    /// Restrict the lookup to a given scale.
27    #[setters(strip_option)]
28    pub scale: Option<u16>,
29
30    /// Restrict the lookup to a given size.
31    #[setters(strip_option)]
32    pub size: Option<u16>,
33
34    /// Whether the icon is symbolic or not.
35    pub symbolic: bool,
36
37    /// Prioritizes SVG over PNG
38    pub prefer_svg: bool,
39}
40
41impl Named {
42    pub fn new(name: impl Into<Arc<str>>) -> Self {
43        let name = name.into();
44        let symbolic = name.ends_with("-symbolic");
45        Self {
46            symbolic,
47            name,
48            fallback: Some(IconFallback::Default),
49            size: None,
50            scale: None,
51            prefer_svg: symbolic,
52        }
53    }
54
55    #[cfg(not(windows))]
56    #[must_use]
57    pub fn path(self) -> Option<PathBuf> {
58        let name = &*self.name;
59        let fallback = &self.fallback;
60        let locate = |theme: &str, name| {
61            let mut lookup = freedesktop_icons::lookup(name)
62                .with_theme(theme.as_ref())
63                .with_cache();
64
65            if let Some(scale) = self.scale {
66                lookup = lookup.with_scale(scale);
67            }
68
69            if let Some(size) = self.size {
70                lookup = lookup.with_size(size);
71            }
72
73            if self.prefer_svg {
74                lookup = lookup.force_svg();
75            }
76            lookup.find()
77        };
78
79        let theme = crate::icon_theme::DEFAULT.lock().unwrap();
80        let themes = if theme.as_ref() == crate::icon_theme::COSMIC {
81            vec![theme.as_ref()]
82        } else {
83            vec![theme.as_ref(), crate::icon_theme::COSMIC]
84        };
85
86        let mut result = themes.iter().find_map(|t| locate(t, name));
87
88        // On failure, attempt to locate fallback icon.
89        if result.is_none() {
90            if matches!(fallback, Some(IconFallback::Default)) {
91                for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
92                    result = themes.iter().find_map(|t| locate(t, new_name));
93                    if result.is_some() {
94                        break;
95                    }
96                }
97            } else if let Some(IconFallback::Names(fallbacks)) = fallback {
98                for fallback in fallbacks {
99                    result = themes.iter().find_map(|t| locate(t, fallback));
100                    if result.is_some() {
101                        break;
102                    }
103                }
104            }
105        }
106
107        result
108    }
109
110    #[cfg(windows)]
111    #[must_use]
112    pub fn path(self) -> Option<PathBuf> {
113        //TODO: implement icon lookup for Windows
114        None
115    }
116
117    #[inline]
118    pub fn handle(self) -> Handle {
119        Handle {
120            symbolic: self.symbolic,
121            data: super::Data::Name(self),
122        }
123    }
124
125    #[inline]
126    pub fn icon(self) -> Icon {
127        let size = self.size;
128
129        let icon = super::icon(self.handle());
130
131        match size {
132            Some(size) => icon.size(size),
133            None => icon,
134        }
135    }
136}
137
138impl From<Named> for Handle {
139    #[inline]
140    fn from(builder: Named) -> Self {
141        builder.handle()
142    }
143}
144
145impl From<Named> for Icon {
146    #[inline]
147    fn from(builder: Named) -> Self {
148        builder.icon()
149    }
150}
151
152impl<Message: 'static> From<Named> for crate::Element<'_, Message> {
153    #[inline]
154    fn from(builder: Named) -> Self {
155        builder.icon().into()
156    }
157}