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