cosmic/widget/icon/
named.rs1use super::{Handle, Icon};
5use std::borrow::Cow;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, Default, Hash)]
10pub enum IconFallback {
12 #[default]
13 Default,
15 Names(Vec<Cow<'static, str>>),
17}
18
19#[must_use]
20#[derive(derive_setters::Setters, Clone, Debug, Hash)]
21pub struct Named {
22 pub(super) name: Arc<str>,
24
25 pub fallback: Option<IconFallback>,
27
28 #[setters(strip_option)]
30 pub scale: Option<u16>,
31
32 #[setters(strip_option)]
34 pub size: Option<u16>,
35
36 pub symbolic: bool,
38
39 pub prefer_svg: bool,
41
42 #[setters(skip)]
44 pub extra_paths: Vec<PathBuf>,
45}
46
47impl Named {
48 pub fn new(name: impl Into<Arc<str>>) -> Self {
49 let name = name.into();
50 let symbolic = name.ends_with("-symbolic");
51 Self {
52 symbolic,
53 name,
54 fallback: Some(IconFallback::Default),
55 size: None,
56 scale: None,
57 prefer_svg: symbolic,
58 extra_paths: Vec::new(),
59 }
60 }
61
62 pub fn with_extra_paths(mut self, paths: Vec<PathBuf>) -> Self {
63 self.extra_paths = paths;
64 self
65 }
66
67 #[cfg(all(unix, not(target_os = "macos")))]
68 #[must_use]
69 pub fn path(self) -> Option<PathBuf> {
70 let name = &*self.name;
71 let fallback = &self.fallback;
72 let extra_paths = &self.extra_paths;
73 let locate = |theme: &str, name| {
74 let mut lookup = freedesktop_icons::lookup(name)
75 .with_theme(theme.as_ref())
76 .with_cache();
77
78 if !extra_paths.is_empty() {
79 lookup = lookup.with_extra_paths(extra_paths);
80 }
81
82 if let Some(scale) = self.scale {
83 lookup = lookup.with_scale(scale);
84 }
85
86 if let Some(size) = self.size {
87 lookup = lookup.with_size(size);
88 }
89
90 if self.prefer_svg {
91 lookup = lookup.force_svg();
92 }
93 lookup.find()
94 };
95
96 let theme = crate::icon_theme::DEFAULT.lock().unwrap();
97 let themes = if theme.as_ref() == crate::icon_theme::COSMIC {
98 vec![theme.as_ref()]
99 } else {
100 vec![theme.as_ref(), crate::icon_theme::COSMIC]
101 };
102
103 let mut result = themes.iter().find_map(|t| locate(t, name));
104
105 if result.is_none() {
107 if matches!(fallback, Some(IconFallback::Default)) {
108 for new_name in name.rmatch_indices('-').map(|(pos, _)| &name[..pos]) {
109 result = themes.iter().find_map(|t| locate(t, new_name));
110 if result.is_some() {
111 break;
112 }
113 }
114 } else if let Some(IconFallback::Names(fallbacks)) = fallback {
115 for fallback in fallbacks {
116 result = themes.iter().find_map(|t| locate(t, fallback));
117 if result.is_some() {
118 break;
119 }
120 }
121 }
122 }
123
124 result
125 }
126
127 #[cfg(any(not(unix), target_os = "macos"))]
128 #[must_use]
129 pub fn path(self) -> Option<PathBuf> {
130 None
132 }
133
134 #[inline]
135 pub fn handle(self) -> Handle {
136 let name = self.name.clone();
137 Handle {
138 symbolic: self.symbolic,
139 data: if let Some(path) = self.path() {
140 super::from_path(path).data
141 } else {
142 super::bundle::get(&name).unwrap_or_else(|| {
143 let bytes: &'static [u8] = &[];
144 super::Data::Svg(iced_core::svg::Handle::from_memory(bytes))
145 })
146 },
147 }
148 }
149
150 #[inline]
151 pub fn icon(self) -> Icon {
152 let size = self.size;
153
154 let icon = super::icon(self.handle());
155
156 match size {
157 Some(size) => icon.size(size),
158 None => icon,
159 }
160 }
161}
162
163impl From<Named> for Handle {
164 #[inline]
165 fn from(builder: Named) -> Self {
166 builder.handle()
167 }
168}
169
170impl From<Named> for Icon {
171 #[inline]
172 fn from(builder: Named) -> Self {
173 builder.icon()
174 }
175}
176
177impl<Message: 'static> From<Named> for crate::Element<'_, Message> {
178 #[inline]
179 fn from(builder: Named) -> Self {
180 builder.icon().into()
181 }
182}