1use alloc::{
27 string::{String, ToString},
28 vec::Vec,
29};
30
31use azul_css::{
32 system::SystemStyle,
33 props::basic::{FontRef, StyleFontFamily, StyleFontFamilyVec},
34 props::basic::length::FloatValue,
35 props::layout::{LayoutWidth, LayoutHeight},
36 props::property::CssProperty,
37 props::style::filter::{StyleFilter, StyleFilterVec, StyleColorMatrix},
38 props::style::text::StyleTextColor,
39 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
40 css::{Css, CssPropertyValue},
41};
42
43use azul_core::{
44 dom::{Dom, NodeData},
45 icon::IconProviderHandle,
46 refany::{OptionRefAny, RefAny},
47 resources::ImageRef,
48 styled_dom::StyledDom,
49};
50
51#[derive(Debug)]
60pub struct ImageIconData {
61 pub image: ImageRef,
62 pub width: f32,
64 pub height: f32,
66}
67
68#[derive(Debug)]
73pub struct FontIconData {
74 pub font: FontRef,
75 pub icon_char: String,
77}
78
79#[must_use] pub extern "C" fn default_icon_resolver(
94 icon_data: OptionRefAny,
95 original_icon_dom: &StyledDom,
96 system_style: &SystemStyle,
97) -> StyledDom {
98 let Some(mut data) = icon_data.into_option() else {
100 let mut dom = Dom::create_div();
101 return StyledDom::create(&mut dom, Css::empty());
102 };
103
104 if let Some(img) = data.downcast_ref::<ImageIconData>() {
106 return create_image_icon_from_original(&img, original_icon_dom, system_style);
107 }
108
109 if let Some(font_icon) = data.downcast_ref::<FontIconData>() {
111 return create_font_icon_from_original(&font_icon, original_icon_dom, system_style);
112 }
113
114 let mut dom = Dom::create_div();
116 StyledDom::create(&mut dom, Css::empty())
117}
118
119fn create_image_icon_from_original(
127 img: &ImageIconData,
128 original: &StyledDom,
129 system_style: &SystemStyle,
130) -> StyledDom {
131 let mut dom = Dom::create_image(img.image.clone());
132
133 if let Some(original_node) = original.node_data.as_ref().first() {
135 let mut props_vec = copy_appropriate_styles_vec(original_node);
136
137 let has_width = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Width(_)));
139 let has_height = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Height(_)));
140
141 if !has_width {
142 props_vec.push(CssPropertyWithConditions::simple(
143 CssProperty::width(LayoutWidth::px(img.width))
144 ));
145 }
146 if !has_height {
147 props_vec.push(CssPropertyWithConditions::simple(
148 CssProperty::height(LayoutHeight::px(img.height))
149 ));
150 }
151
152 apply_icon_style_filters(&mut props_vec, system_style);
154
155 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
156
157 if let Some(a11y) = original_node.get_accessibility_info() {
159 dom = dom.with_accessibility_info(a11y.clone());
160 }
161 } else {
162 let mut props_vec = vec![
164 CssPropertyWithConditions::simple(CssProperty::width(LayoutWidth::px(img.width))),
165 CssPropertyWithConditions::simple(CssProperty::height(LayoutHeight::px(img.height))),
166 ];
167
168 apply_icon_style_filters(&mut props_vec, system_style);
170
171 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
172 }
173
174 StyledDom::create(&mut dom, Css::empty())
175}
176
177fn create_font_icon_from_original(
183 font_icon: &FontIconData,
184 original: &StyledDom,
185 system_style: &SystemStyle,
186) -> StyledDom {
187 let mut dom = Dom::create_text(font_icon.icon_char.clone());
188
189 let font_prop = CssPropertyWithConditions::simple(
191 CssProperty::font_family(StyleFontFamilyVec::from_vec(vec![
192 StyleFontFamily::Ref(font_icon.font.clone())
193 ]))
194 );
195
196 if let Some(original_node) = original.node_data.as_ref().first() {
197 let mut props_vec = copy_appropriate_styles_vec(original_node);
198 props_vec.push(font_prop);
199
200 apply_font_icon_color(&mut props_vec, system_style);
202
203 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
204
205 if let Some(a11y) = original_node.get_accessibility_info() {
207 dom = dom.with_accessibility_info(a11y.clone());
208 }
209 } else {
210 let mut props_vec = vec![font_prop];
212
213 apply_font_icon_color(&mut props_vec, system_style);
215
216 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
217 }
218
219 StyledDom::create(&mut dom, Css::empty())
220}
221
222fn copy_appropriate_styles_vec(
225 original_node: &NodeData,
226) -> Vec<CssPropertyWithConditions> {
227 original_node
229 .get_style()
230 .iter_inline_properties()
231 .map(|(prop, conds)| CssPropertyWithConditions {
232 property: prop.clone(),
233 apply_if: conds.clone(),
234 })
235 .collect()
236}
237
238fn apply_icon_style_filters(
243 props_vec: &mut Vec<CssPropertyWithConditions>,
244 system_style: &SystemStyle,
245) {
246 let icon_style = &system_style.icon_style;
247
248 let mut filters = Vec::new();
250
251 if icon_style.prefer_grayscale {
254 let grayscale_matrix = StyleColorMatrix {
260 m0: FloatValue::new(0.2126),
261 m1: FloatValue::new(0.7152),
262 m2: FloatValue::new(0.0722),
263 m3: FloatValue::new(0.0),
264 m4: FloatValue::new(0.0),
265 m5: FloatValue::new(0.2126),
266 m6: FloatValue::new(0.7152),
267 m7: FloatValue::new(0.0722),
268 m8: FloatValue::new(0.0),
269 m9: FloatValue::new(0.0),
270 m10: FloatValue::new(0.2126),
271 m11: FloatValue::new(0.7152),
272 m12: FloatValue::new(0.0722),
273 m13: FloatValue::new(0.0),
274 m14: FloatValue::new(0.0),
275 m15: FloatValue::new(0.0),
276 m16: FloatValue::new(0.0),
277 m17: FloatValue::new(0.0),
278 m18: FloatValue::new(1.0),
279 m19: FloatValue::new(0.0),
280 };
281 filters.push(StyleFilter::ColorMatrix(grayscale_matrix));
282 }
283
284 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
286 filters.push(StyleFilter::Flood(*tint));
287 }
288
289 if !filters.is_empty() {
291 props_vec.push(CssPropertyWithConditions::simple(
292 CssProperty::Filter(CssPropertyValue::Exact(StyleFilterVec::from_vec(filters)))
293 ));
294 }
295}
296
297fn apply_font_icon_color(
303 props_vec: &mut Vec<CssPropertyWithConditions>,
304 system_style: &SystemStyle,
305) {
306 let icon_style = &system_style.icon_style;
307
308 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
310 props_vec.push(CssPropertyWithConditions::simple(
311 CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor { inner: *tint }))
312 ));
313 }
314 }
317
318pub fn register_image_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, image: ImageRef) {
322 let size = image.get_size();
324 let data = ImageIconData {
325 image,
326 width: size.width,
327 height: size.height,
328 };
329 provider.register_icon(pack_name, icon_name, RefAny::new(data));
330}
331
332#[cfg(feature = "zip")]
334pub fn register_icons_from_zip(provider: &mut IconProviderHandle, pack_name: &str, zip_bytes: &[u8]) {
335 for (icon_name, image, width, height) in load_images_from_zip(zip_bytes) {
336 let data = ImageIconData { image, width, height };
337 provider.register_icon(pack_name, &icon_name, RefAny::new(data));
338 }
339}
340
341#[cfg(not(feature = "zip"))]
342pub fn register_icons_from_zip(_provider: &mut IconProviderHandle, _pack_name: &str, _zip_bytes: &[u8]) {
343 }
345
346pub fn register_font_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, font: FontRef, icon_char: &str) {
348 let data = FontIconData {
349 font,
350 icon_char: icon_char.to_string()
351 };
352 provider.register_icon(pack_name, icon_name, RefAny::new(data));
353}
354
355#[cfg(all(feature = "zip", feature = "image_decoding"))]
361#[allow(clippy::cast_precision_loss)] fn load_images_from_zip(zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
363 use crate::zip::{ZipFile, ZipReadConfig};
364 use crate::image::decode::{decode_raw_image_from_any_bytes, ResultRawImageDecodeImageError};
365 use std::path::Path;
366
367 let mut result = Vec::new();
368 let config = ZipReadConfig::default();
369 let Ok(entries) = ZipFile::list(zip_bytes, &config) else {
370 return result;
371 };
372
373 for entry in &entries {
374 if entry.path.ends_with('/') { continue; } let Ok(Some(file_bytes)) = ZipFile::get_single_file(zip_bytes, entry, &config) else {
377 continue;
378 };
379
380 if let ResultRawImageDecodeImageError::Ok(raw_image) = decode_raw_image_from_any_bytes(&file_bytes) {
382 let path = Path::new(&entry.path);
384 let icon_name = path.file_stem()
385 .and_then(|s| s.to_str())
386 .unwrap_or("")
387 .to_string();
388
389 let width = raw_image.width as f32;
390 let height = raw_image.height as f32;
391
392 if let Some(image) = ImageRef::new_rawimage(raw_image) {
393 result.push((icon_name, image, width, height));
394 }
395 }
396 }
397
398 result
399}
400
401#[cfg(not(all(feature = "zip", feature = "image_decoding")))]
402fn load_images_from_zip(_zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
403 Vec::new()
404}
405
406#[cfg(feature = "icons")]
418pub fn register_material_icons(provider: &mut IconProviderHandle, font: &FontRef) {
419 use material_icons::{ALL_ICONS, icon_to_char, icon_to_html_name};
420
421 for icon in &ALL_ICONS {
423 let icon_char = icon_to_char(*icon);
424 let name = icon_to_html_name(icon);
425
426 let data = FontIconData {
427 font: font.clone(),
428 icon_char: icon_char.to_string(),
429 };
430 provider.register_icon("material-icons", name, RefAny::new(data));
431 }
432}
433
434#[cfg(not(feature = "icons"))]
435pub fn register_material_icons(_provider: &mut IconProviderHandle, _font: FontRef) {
436 }
438
439#[cfg(all(feature = "icons", feature = "text_layout"))]
454pub fn register_embedded_material_icons(
455 provider: &mut IconProviderHandle,
456 font_bytes: &[u8],
457) -> bool {
458 use crate::font::parsed::ParsedFont;
459 use crate::parsed_font_to_font_ref;
460
461 let mut warnings = Vec::new();
462 let Some(parsed_font) = ParsedFont::from_bytes(font_bytes, 0, &mut warnings) else {
463 return false;
464 };
465
466 let font_ref = parsed_font_to_font_ref(parsed_font);
467 register_material_icons(provider, &font_ref);
468
469 true
470}
471
472#[cfg(not(all(feature = "icons", feature = "text_layout")))]
473pub fn register_embedded_material_icons(
474 _provider: &mut IconProviderHandle,
475 _font_bytes: &[u8],
476) -> bool {
477 false
479}
480
481pub fn create_default_icon_provider() -> IconProviderHandle {
487 IconProviderHandle::with_resolver(default_icon_resolver)
488}
489
490#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_default_resolver_no_data() {
506 let style = SystemStyle::default();
507 let original = StyledDom::default();
508
509 let result = default_icon_resolver(OptionRefAny::None, &original, &style);
510
511 assert_eq!(result.node_data.as_ref().len(), 1);
513 }
514
515 #[test]
516 fn test_create_default_provider() {
517 let provider = create_default_icon_provider();
518 assert!(provider.list_packs().is_empty());
519 }
520}