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        Self {
45            symbolic: name.ends_with("-symbolic"),
46            name,
47            fallback: Some(IconFallback::Default),
48            size: None,
49            scale: None,
50            prefer_svg: false,
51        }
52    }
53
54    #[cfg(not(windows))]
55    #[must_use]
56    pub fn path(self) -> Option<PathBuf> {
57        let name = &*self.name;
58        let fallback = &self.fallback;
59        let locate = |theme: &str, name| {
60            let mut lookup = freedesktop_icons::lookup(name)
61                .with_theme(theme.as_ref())
62                .with_cache();
63
64            if let Some(scale) = self.scale {
65                lookup = lookup.with_scale(scale);
66            }
67
68            if let Some(size) = self.size {
69                lookup = lookup.with_size(size);
70            }
71
72            if self.prefer_svg {
73                lookup = lookup.force_svg();
74            }
75            lookup.find()
76        };
77
78        let theme = crate::icon_theme::DEFAULT.lock().unwrap();
79        let themes = if theme.as_ref() == crate::icon_theme::COSMIC {
80            vec![theme.as_ref()]
81        } else {
82            vec![theme.as_ref(), crate::icon_theme::COSMIC]
83        };
84
85        let mut result = themes.iter().find_map(|t| locate(t, name));
86
87        // On failure, attempt to locate fallback icon.
88        if result.is_none() {
89            if matches!(fallback, Some(IconFallback::Default)) {
90                for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
91                    result = themes.iter().find_map(|t| locate(t, new_name));
92                    if result.is_some() {
93                        break;
94                    }
95                }
96            } else if let Some(IconFallback::Names(fallbacks)) = fallback {
97                for fallback in fallbacks {
98                    result = themes.iter().find_map(|t| locate(t, fallback));
99                    if result.is_some() {
100                        break;
101                    }
102                }
103            }
104        }
105
106        result
107    }
108
109    #[cfg(windows)]
110    #[must_use]
111    pub fn path(self) -> Option<PathBuf> {
112        //TODO: implement icon lookup for Windows
113        None
114    }
115
116    #[inline]
117    pub fn handle(self) -> Handle {
118        Handle {
119            symbolic: self.symbolic,
120            data: super::Data::Name(self),
121        }
122    }
123
124    #[inline]
125    pub fn icon(self) -> Icon {
126        let size = self.size;
127
128        let icon = super::icon(self.handle());
129
130        match size {
131            Some(size) => icon.size(size),
132            None => icon,
133        }
134    }
135}
136
137impl From<Named> for Handle {
138    #[inline]
139    fn from(builder: Named) -> Self {
140        builder.handle()
141    }
142}
143
144impl From<Named> for Icon {
145    #[inline]
146    fn from(builder: Named) -> Self {
147        builder.icon()
148    }
149}
150
151impl<Message: 'static> From<Named> for crate::Element<'_, Message> {
152    #[inline]
153    fn from(builder: Named) -> Self {
154        builder.icon().into()
155    }
156}