1use alloc::{
39 boxed::Box,
40 collections::BTreeMap,
41 string::{String, ToString},
42 sync::Arc,
43 vec::Vec,
44};
45use core::fmt;
46
47#[cfg(feature = "std")]
48use std::sync::Mutex;
49
50#[cfg(not(feature = "std"))]
51use spin::Mutex;
52
53use azul_css::{AzString, system::SystemStyle};
54
55use crate::{
56 dom::{Dom, NodeType},
57 refany::{OptionRefAny, RefAny},
58 styled_dom::StyledDom,
59};
60
61const IMAGE_ICON_DATA_TYPE_NAME: &str = "ImageIconData";
63const FONT_ICON_DATA_TYPE_NAME: &str = "FontIconData";
64
65pub type IconResolverCallbackType = extern "C" fn(
80 icon_data: OptionRefAny,
81 original_icon_dom: &StyledDom,
82 system_style: &SystemStyle,
83) -> StyledDom;
84
85pub extern "C" fn default_icon_resolver(
87 _icon_data: OptionRefAny,
88 _original_icon_dom: &StyledDom,
89 _system_style: &SystemStyle,
90) -> StyledDom {
91 StyledDom::default()
93}
94
95#[derive(Clone)]
99pub struct IconProviderInner {
100 pub icons: BTreeMap<String, BTreeMap<String, RefAny>>,
103 pub resolver: IconResolverCallbackType,
105}
106
107impl Default for IconProviderInner {
108 fn default() -> Self {
109 Self {
110 icons: BTreeMap::new(),
111 resolver: default_icon_resolver,
112 }
113 }
114}
115
116#[repr(C)]
130pub struct IconProviderHandle {
131 pub inner: Box<IconProviderInner>,
133}
134
135impl Clone for IconProviderHandle {
136 fn clone(&self) -> Self {
137 Self { inner: Box::new((*self.inner).clone()) }
138 }
139}
140
141impl fmt::Debug for IconProviderHandle {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 let pack_count = self.inner.icons.len();
144 let icon_count: usize = self.inner.icons.values().map(|p| p.len()).sum();
145
146 f.debug_struct("IconProviderHandle")
147 .field("pack_count", &pack_count)
148 .field("icon_count", &icon_count)
149 .finish()
150 }
151}
152
153impl Default for IconProviderHandle {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl IconProviderHandle {
160 pub fn new() -> Self {
166 Self {
167 inner: Box::new(IconProviderInner {
168 icons: BTreeMap::new(),
169 resolver: default_icon_resolver,
170 })
171 }
172 }
173
174 pub fn with_resolver(resolver: IconResolverCallbackType) -> Self {
176 Self {
177 inner: Box::new(IconProviderInner {
178 icons: BTreeMap::new(),
179 resolver,
180 })
181 }
182 }
183
184 pub(crate) fn into_shared(self) -> Arc<Mutex<IconProviderInner>> {
189 Arc::new(Mutex::new(*self.inner))
190 }
191
192 pub fn set_resolver(&mut self, resolver: IconResolverCallbackType) {
194 self.inner.resolver = resolver;
195 }
196
197 pub fn register_icon(&mut self, pack_name: &str, icon_name: &str, data: RefAny) {
201 let pack = self.inner.icons
202 .entry(pack_name.to_string())
203 .or_default();
204 pack.insert(icon_name.to_lowercase(), data);
205 }
206
207 pub fn unregister_icon(&mut self, pack_name: &str, icon_name: &str) {
209 if let Some(pack) = self.inner.icons.get_mut(pack_name) {
210 pack.remove(&icon_name.to_lowercase());
211 if pack.is_empty() {
212 self.inner.icons.remove(pack_name);
213 }
214 }
215 }
216
217 pub fn unregister_pack(&mut self, pack_name: &str) {
219 self.inner.icons.remove(pack_name);
220 }
221
222 fn lookup_with_pack(&self, icon_name: &str) -> Option<(&str, &RefAny)> {
224 let icon_name_lower = icon_name.to_lowercase();
225 for (pack_name, pack) in self.inner.icons.iter() {
226 if let Some(data) = pack.get(&icon_name_lower) {
227 return Some((pack_name.as_str(), data));
228 }
229 }
230 None
231 }
232
233 pub fn lookup(&self, icon_name: &str) -> Option<RefAny> {
235 self.lookup_with_pack(icon_name).map(|(_, data)| data.clone())
236 }
237
238 pub fn has_icon(&self, icon_name: &str) -> bool {
240 let icon_name_lower = icon_name.to_lowercase();
241 self.inner.icons.values().any(|p| p.contains_key(&icon_name_lower))
242 }
243
244 pub fn list_packs(&self) -> Vec<String> {
246 self.inner.icons.keys().cloned().collect()
247 }
248
249 pub fn list_icons_in_pack(&self, pack_name: &str) -> Vec<String> {
251 self.inner.icons.get(pack_name)
252 .map(|pack| pack.keys().cloned().collect())
253 .unwrap_or_default()
254 }
255
256 pub fn debug_lookup(&self, icon_name: &str) -> AzString {
258 let icon_name_lower = icon_name.to_lowercase();
259
260 let mut result = format!("Debug lookup for icon '{}' (normalized: '{}'):\n", icon_name, icon_name_lower);
261
262 result.push_str(&format!(" Total packs: {}\n", self.inner.icons.len()));
264 for (pack_name, pack) in self.inner.icons.iter() {
265 result.push_str(&format!(" Pack '{}': {} icons\n", pack_name, pack.len()));
266 for (name, _) in pack.iter() {
267 result.push_str(&format!(" - {}\n", name));
268 }
269 }
270
271 match self.lookup_with_pack(icon_name) {
273 Some((pack, data)) => {
274 result.push_str(&format!("\n FOUND in pack '{}'\n", pack));
275 let type_name = data.get_type_name();
276 result.push_str(&format!(" RefAny type_name: '{}'\n", type_name.as_str()));
277
278 let debug_info = data.sharing_info.debug_get_refcount_copied();
279 result.push_str(&format!(" RefAny size: {} bytes\n", debug_info._internal_layout_size));
280
281 let type_str = type_name.as_str();
282 if type_str.contains(IMAGE_ICON_DATA_TYPE_NAME) {
283 result.push_str(" RefAny type: ImageIconData (image-based icon)\n");
284 } else if type_str.contains(FONT_ICON_DATA_TYPE_NAME) {
285 result.push_str(" RefAny type: FontIconData (font-based icon)\n");
286 } else {
287 result.push_str(&format!(" RefAny type: UNKNOWN ('{}')\n", type_str));
288 }
289 }
290 None => {
291 result.push_str("\n NOT FOUND in any pack\n");
292 }
293 }
294
295 AzString::from(result)
296 }
297}
298
299#[derive(Clone)]
304pub struct SharedIconProvider {
305 inner: Arc<Mutex<IconProviderInner>>,
306}
307
308impl SharedIconProvider {
309 pub fn from_handle(handle: IconProviderHandle) -> Self {
311 Self { inner: handle.into_shared() }
312 }
313
314 pub fn resolve(
316 &self,
317 original_icon_dom: &StyledDom,
318 icon_name: &str,
319 system_style: &SystemStyle,
320 ) -> StyledDom {
321 let (resolver, lookup_result) = {
322 let guard = match self.inner.lock() {
323 Ok(g) => g,
324 Err(_) => return StyledDom::default(),
325 };
326
327 let resolver = guard.resolver;
328 let icon_name_lower = icon_name.to_lowercase();
329
330 let lookup_result = guard.icons.values()
331 .find_map(|pack| pack.get(&icon_name_lower).cloned());
332
333 (resolver, lookup_result)
334 };
335
336 resolver(lookup_result.into(), original_icon_dom, system_style)
337 }
338
339 pub fn lookup(&self, icon_name: &str) -> Option<RefAny> {
341 let icon_name_lower = icon_name.to_lowercase();
342 self.inner.lock().ok().and_then(|guard| {
343 for pack in guard.icons.values() {
344 if let Some(data) = pack.get(&icon_name_lower) {
345 return Some(data.clone());
346 }
347 }
348 None
349 })
350 }
351
352 pub fn has_icon(&self, icon_name: &str) -> bool {
354 let icon_name_lower = icon_name.to_lowercase();
355 self.inner.lock()
356 .map(|guard| guard.icons.values().any(|p| p.contains_key(&icon_name_lower)))
357 .unwrap_or(false)
358 }
359}
360
361struct CollectedIcon {
365 node_idx: usize,
367 icon_name: AzString,
369}
370
371struct IconReplacement {
373 node_idx: usize,
375 replacement: StyledDom,
377}
378
379fn collect_icon_nodes(styled_dom: &StyledDom) -> Vec<CollectedIcon> {
381 let mut icons = Vec::new();
382
383 let node_data = styled_dom.node_data.as_ref();
384 for (idx, node) in node_data.iter().enumerate() {
385 if let NodeType::Icon(icon_name) = node.get_node_type() {
386 icons.push(CollectedIcon {
387 node_idx: idx,
388 icon_name: icon_name.clone_self(),
389 });
390 }
391 }
392
393 icons
394}
395
396fn extract_single_node_styled_dom(styled_dom: &StyledDom, node_idx: usize) -> StyledDom {
399 use crate::dom::{NodeDataVec, DomId};
400 use crate::id::NodeId;
401 use crate::styled_dom::{
402 StyledNodeVec, NodeHierarchyItemIdVec, TagIdToNodeIdMappingVec,
403 NodeHierarchyItemVec, NodeHierarchyItem, NodeHierarchyItemId,
404 ParentWithNodeDepthVec, ParentWithNodeDepth,
405 };
406 use crate::style::{CascadeInfoVec, CascadeInfo};
407 use crate::prop_cache::{CssPropertyCachePtr, CssPropertyCache};
408
409 let node_data = styled_dom.node_data.as_ref();
410 let styled_nodes = styled_dom.styled_nodes.as_ref();
411
412 if node_idx >= node_data.len() {
413 return StyledDom::default();
414 }
415
416 let single_node = node_data[node_idx].clone();
418 let single_styled = if node_idx < styled_nodes.len() {
419 styled_nodes[node_idx].clone()
420 } else {
421 crate::styled_dom::StyledNode::default()
422 };
423
424 StyledDom {
425 root: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
426 node_hierarchy: NodeHierarchyItemVec::from_vec(vec![NodeHierarchyItem {
427 parent: 0,
428 previous_sibling: 0,
429 next_sibling: 0,
430 last_child: 0,
431 }]),
432 node_data: NodeDataVec::from_vec(vec![single_node]),
433 styled_nodes: StyledNodeVec::from_vec(vec![single_styled]),
434 cascade_info: CascadeInfoVec::from_vec(vec![CascadeInfo { index_in_parent: 0, is_last_child: true }]),
435 nodes_with_window_callbacks: NodeHierarchyItemIdVec::from_vec(Vec::new()),
436 nodes_with_datasets: NodeHierarchyItemIdVec::from_vec(Vec::new()),
437 tag_ids_to_node_ids: TagIdToNodeIdMappingVec::from_vec(Vec::new()),
438 non_leaf_nodes: ParentWithNodeDepthVec::from_vec(Vec::new()),
439 css_property_cache: CssPropertyCachePtr::new(CssPropertyCache::empty(1)),
440 dom_id: DomId::ROOT_ID,
441 }
442}
443
444fn resolve_collected_icons(
446 icons: &[CollectedIcon],
447 styled_dom: &StyledDom,
448 provider: &SharedIconProvider,
449 system_style: &SystemStyle,
450) -> Vec<IconReplacement> {
451 icons.iter().map(|icon| {
452 let original_icon_dom = extract_single_node_styled_dom(styled_dom, icon.node_idx);
454 let replacement = provider.resolve(&original_icon_dom, icon.icon_name.as_str(), system_style);
455 IconReplacement {
456 node_idx: icon.node_idx,
457 replacement,
458 }
459 }).collect()
460}
461
462fn is_single_node_replacement(replacement: &StyledDom) -> bool {
464 replacement.node_data.as_ref().len() == 1
465}
466
467fn apply_single_node_replacement(
469 styled_dom: &mut StyledDom,
470 node_idx: usize,
471 replacement: &StyledDom,
472) {
473 if replacement.node_data.as_ref().is_empty() {
474 let node_data = styled_dom.node_data.as_mut();
476 if let Some(node) = node_data.get_mut(node_idx) {
477 node.set_node_type(NodeType::Div);
478 }
479 } else {
480 let replacement_root = &replacement.node_data.as_ref()[0];
482 let replacement_node_type = replacement_root.get_node_type().clone();
483
484 let node_data = styled_dom.node_data.as_mut();
485 if let Some(node) = node_data.get_mut(node_idx) {
486 node.set_node_type(replacement_node_type);
488
489 node.set_style(replacement_root.get_style().clone());
491
492 if let Some(a11y) = replacement_root.get_accessibility_info() {
494 node.set_accessibility_info(*a11y.clone());
495 }
496 }
497
498 if let Some(replacement_styled) = replacement.styled_nodes.as_ref().first() {
500 let styled_nodes = styled_dom.styled_nodes.as_mut();
501 if let Some(styled) = styled_nodes.get_mut(node_idx) {
502 *styled = replacement_styled.clone();
503 }
504 }
505 }
506}
507
508fn apply_multi_node_replacement(
510 styled_dom: &mut StyledDom,
511 node_idx: usize,
512 replacement: StyledDom,
513) {
514 let replacement_len = replacement.node_data.as_ref().len();
515 if replacement_len == 0 {
516 let node_data = styled_dom.node_data.as_mut();
517 if let Some(node) = node_data.get_mut(node_idx) {
518 node.set_node_type(NodeType::Div);
519 }
520 return;
521 }
522
523 apply_single_node_replacement(styled_dom, node_idx, &replacement);
525
526 if replacement_len > 1 {
527 #[cfg(debug_assertions)]
529 eprintln!(
530 "Warning: Icon replacement has {} nodes, only root node used.",
531 replacement_len
532 );
533 }
534}
535
536pub fn resolve_icons_in_styled_dom(
545 styled_dom: &mut StyledDom,
546 provider: &SharedIconProvider,
547 system_style: &SystemStyle,
548) {
549 let icons = collect_icon_nodes(styled_dom);
551
552 if icons.is_empty() {
553 return;
554 }
555
556 let replacements = resolve_collected_icons(&icons, styled_dom, provider, system_style);
559
560 for replacement in replacements.into_iter().rev() {
562 if is_single_node_replacement(&replacement.replacement) ||
563 replacement.replacement.node_data.as_ref().is_empty() {
564 apply_single_node_replacement(
565 styled_dom,
566 replacement.node_idx,
567 &replacement.replacement
568 );
569 } else {
570 apply_multi_node_replacement(
571 styled_dom,
572 replacement.node_idx,
573 replacement.replacement
574 );
575 }
576 }
577}
578
579impl_option!(
582 IconProviderHandle,
583 OptionIconProviderHandle,
584 [Clone]
585);