cosmic/widget/icon/
named.rs1use super::{Handle, Icon};
5use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc};
6
7#[derive(Debug, Clone, Default, Hash)]
8pub enum IconFallback {
10 #[default]
11 Default,
13 Names(Vec<Cow<'static, str>>),
15}
16
17#[must_use]
18#[derive(derive_setters::Setters, Clone, Debug, Hash)]
19pub struct Named {
20 pub(super) name: Arc<str>,
22
23 pub fallback: Option<IconFallback>,
25
26 #[setters(strip_option)]
28 pub scale: Option<u16>,
29
30 #[setters(strip_option)]
32 pub size: Option<u16>,
33
34 pub symbolic: bool,
36
37 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(all(unix, not(target_os = "macos")))]
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 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(any(not(unix), target_os = "macos"))]
111 #[must_use]
112 pub fn path(self) -> Option<PathBuf> {
113 None
115 }
116
117 #[inline]
118 pub fn handle(self) -> Handle {
119 let name = self.name.clone();
120 Handle {
121 symbolic: self.symbolic,
122 data: if let Some(path) = self.path() {
123 if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
124 super::Data::Svg(iced_core::svg::Handle::from_path(path))
125 } else {
126 super::Data::Image(iced_core::image::Handle::from_path(path))
127 }
128 } else {
129 super::bundle::get(&name).unwrap_or_else(|| {
130 let bytes: &'static [u8] = &[];
131 super::Data::Svg(iced_core::svg::Handle::from_memory(bytes))
132 })
133 },
134 }
135 }
136
137 #[inline]
138 pub fn icon(self) -> Icon {
139 let size = self.size;
140
141 let icon = super::icon(self.handle());
142
143 match size {
144 Some(size) => icon.size(size),
145 None => icon,
146 }
147 }
148}
149
150impl From<Named> for Handle {
151 #[inline]
152 fn from(builder: Named) -> Self {
153 builder.handle()
154 }
155}
156
157impl From<Named> for Icon {
158 #[inline]
159 fn from(builder: Named) -> Self {
160 builder.icon()
161 }
162}
163
164impl<Message: 'static> From<Named> for crate::Element<'_, Message> {
165 #[inline]
166 fn from(builder: Named) -> Self {
167 builder.icon().into()
168 }
169}