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
51pub struct ImageIconData {
60 pub image: ImageRef,
61 pub width: f32,
63 pub height: f32,
65}
66
67pub struct FontIconData {
72 pub font: FontRef,
73 pub icon_char: String,
75}
76
77pub extern "C" fn default_icon_resolver(
92 icon_data: OptionRefAny,
93 original_icon_dom: &StyledDom,
94 system_style: &SystemStyle,
95) -> StyledDom {
96 let Some(mut data) = icon_data.into_option() else {
98 let mut dom = Dom::create_div();
99 return StyledDom::create(&mut dom, Css::empty());
100 };
101
102 if let Some(img) = data.downcast_ref::<ImageIconData>() {
104 return create_image_icon_from_original(&*img, original_icon_dom, system_style);
105 }
106
107 if let Some(font_icon) = data.downcast_ref::<FontIconData>() {
109 return create_font_icon_from_original(&*font_icon, original_icon_dom, system_style);
110 }
111
112 let mut dom = Dom::create_div();
114 StyledDom::create(&mut dom, Css::empty())
115}
116
117fn create_image_icon_from_original(
125 img: &ImageIconData,
126 original: &StyledDom,
127 system_style: &SystemStyle,
128) -> StyledDom {
129 let mut dom = Dom::create_image(img.image.clone());
130
131 if let Some(original_node) = original.node_data.as_ref().first() {
133 let mut props_vec = copy_appropriate_styles_vec(original_node);
134
135 let has_width = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Width(_)));
137 let has_height = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Height(_)));
138
139 if !has_width {
140 props_vec.push(CssPropertyWithConditions::simple(
141 CssProperty::width(LayoutWidth::px(img.width))
142 ));
143 }
144 if !has_height {
145 props_vec.push(CssPropertyWithConditions::simple(
146 CssProperty::height(LayoutHeight::px(img.height))
147 ));
148 }
149
150 apply_icon_style_filters(&mut props_vec, system_style);
152
153 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
154
155 if let Some(a11y) = original_node.get_accessibility_info() {
157 dom = dom.with_accessibility_info(*a11y.clone());
158 }
159 } else {
160 let mut props_vec = vec![
162 CssPropertyWithConditions::simple(CssProperty::width(LayoutWidth::px(img.width))),
163 CssPropertyWithConditions::simple(CssProperty::height(LayoutHeight::px(img.height))),
164 ];
165
166 apply_icon_style_filters(&mut props_vec, system_style);
168
169 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
170 }
171
172 StyledDom::create(&mut dom, Css::empty())
173}
174
175fn create_font_icon_from_original(
181 font_icon: &FontIconData,
182 original: &StyledDom,
183 system_style: &SystemStyle,
184) -> StyledDom {
185 let mut dom = Dom::create_text(font_icon.icon_char.clone());
186
187 let font_prop = CssPropertyWithConditions::simple(
189 CssProperty::font_family(StyleFontFamilyVec::from_vec(vec![
190 StyleFontFamily::Ref(font_icon.font.clone())
191 ]))
192 );
193
194 if let Some(original_node) = original.node_data.as_ref().first() {
195 let mut props_vec = copy_appropriate_styles_vec(original_node);
196 props_vec.push(font_prop);
197
198 apply_font_icon_color(&mut props_vec, system_style);
200
201 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
202
203 if let Some(a11y) = original_node.get_accessibility_info() {
205 dom = dom.with_accessibility_info(*a11y.clone());
206 }
207 } else {
208 let mut props_vec = vec![font_prop];
210
211 apply_font_icon_color(&mut props_vec, system_style);
213
214 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
215 }
216
217 StyledDom::create(&mut dom, Css::empty())
218}
219
220fn copy_appropriate_styles_vec(
223 original_node: &NodeData,
224) -> Vec<CssPropertyWithConditions> {
225 original_node
227 .get_style()
228 .iter_inline_properties()
229 .map(|(prop, conds)| CssPropertyWithConditions {
230 property: prop.clone(),
231 apply_if: conds.clone(),
232 })
233 .collect()
234}
235
236fn apply_icon_style_filters(
241 props_vec: &mut Vec<CssPropertyWithConditions>,
242 system_style: &SystemStyle,
243) {
244 let icon_style = &system_style.icon_style;
245
246 let mut filters = Vec::new();
248
249 if icon_style.prefer_grayscale {
252 let grayscale_matrix = StyleColorMatrix {
258 m0: FloatValue::new(0.2126),
259 m1: FloatValue::new(0.7152),
260 m2: FloatValue::new(0.0722),
261 m3: FloatValue::new(0.0),
262 m4: FloatValue::new(0.0),
263 m5: FloatValue::new(0.2126),
264 m6: FloatValue::new(0.7152),
265 m7: FloatValue::new(0.0722),
266 m8: FloatValue::new(0.0),
267 m9: FloatValue::new(0.0),
268 m10: FloatValue::new(0.2126),
269 m11: FloatValue::new(0.7152),
270 m12: FloatValue::new(0.0722),
271 m13: FloatValue::new(0.0),
272 m14: FloatValue::new(0.0),
273 m15: FloatValue::new(0.0),
274 m16: FloatValue::new(0.0),
275 m17: FloatValue::new(0.0),
276 m18: FloatValue::new(1.0),
277 m19: FloatValue::new(0.0),
278 };
279 filters.push(StyleFilter::ColorMatrix(grayscale_matrix));
280 }
281
282 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
284 filters.push(StyleFilter::Flood(*tint));
285 }
286
287 if !filters.is_empty() {
289 props_vec.push(CssPropertyWithConditions::simple(
290 CssProperty::Filter(CssPropertyValue::Exact(StyleFilterVec::from_vec(filters)))
291 ));
292 }
293}
294
295fn apply_font_icon_color(
301 props_vec: &mut Vec<CssPropertyWithConditions>,
302 system_style: &SystemStyle,
303) {
304 let icon_style = &system_style.icon_style;
305
306 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
308 props_vec.push(CssPropertyWithConditions::simple(
309 CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor { inner: *tint }))
310 ));
311 }
312 }
315
316pub fn register_image_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, image: ImageRef) {
320 let size = image.get_size();
322 let data = ImageIconData {
323 image,
324 width: size.width,
325 height: size.height,
326 };
327 provider.register_icon(pack_name, icon_name, RefAny::new(data));
328}
329
330#[cfg(feature = "zip_support")]
332pub fn register_icons_from_zip(provider: &mut IconProviderHandle, pack_name: &str, zip_bytes: &[u8]) {
333 for (icon_name, image, width, height) in load_images_from_zip(zip_bytes) {
334 let data = ImageIconData { image, width, height };
335 provider.register_icon(pack_name, &icon_name, RefAny::new(data));
336 }
337}
338
339#[cfg(not(feature = "zip_support"))]
340pub fn register_icons_from_zip(_provider: &mut IconProviderHandle, _pack_name: &str, _zip_bytes: &[u8]) {
341 }
343
344pub fn register_font_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, font: FontRef, icon_char: &str) {
346 let data = FontIconData {
347 font,
348 icon_char: icon_char.to_string()
349 };
350 provider.register_icon(pack_name, icon_name, RefAny::new(data));
351}
352
353#[cfg(all(feature = "zip_support", feature = "image_decoding"))]
359fn load_images_from_zip(zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
360 use crate::zip::{ZipFile, ZipReadConfig};
361 use crate::image::decode::{decode_raw_image_from_any_bytes, ResultRawImageDecodeImageError};
362 use std::path::Path;
363
364 let mut result = Vec::new();
365 let config = ZipReadConfig::default();
366 let entries = match ZipFile::list(zip_bytes, &config) {
367 Ok(e) => e,
368 Err(_) => return result,
369 };
370
371 for entry in entries.iter() {
372 if entry.path.ends_with('/') { continue; } let file_bytes = match ZipFile::get_single_file(zip_bytes, entry, &config) {
375 Ok(Some(b)) => b,
376 _ => continue,
377 };
378
379 if let ResultRawImageDecodeImageError::Ok(raw_image) = decode_raw_image_from_any_bytes(&file_bytes) {
381 let path = Path::new(&entry.path);
383 let icon_name = path.file_stem()
384 .and_then(|s| s.to_str())
385 .unwrap_or("")
386 .to_string();
387
388 let width = raw_image.width as f32;
389 let height = raw_image.height as f32;
390
391 if let Some(image) = ImageRef::new_rawimage(raw_image) {
392 result.push((icon_name, image, width, height));
393 }
394 }
395 }
396
397 result
398}
399
400#[cfg(not(all(feature = "zip_support", feature = "image_decoding")))]
401fn load_images_from_zip(_zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
402 Vec::new()
403}
404
405#[cfg(feature = "icons")]
417pub fn register_material_icons(provider: &mut IconProviderHandle, font: FontRef) {
418 use material_icons::{ALL_ICONS, icon_to_char, icon_to_html_name};
419
420 for icon in ALL_ICONS.iter() {
422 let icon_char = icon_to_char(*icon);
423 let name = icon_to_html_name(icon);
424
425 let data = FontIconData {
426 font: font.clone(),
427 icon_char: icon_char.to_string(),
428 };
429 provider.register_icon("material-icons", name, RefAny::new(data));
430 }
431}
432
433#[cfg(not(feature = "icons"))]
434pub fn register_material_icons(_provider: &mut IconProviderHandle, _font: FontRef) {
435 }
437
438#[cfg(all(feature = "icons", feature = "text_layout"))]
453pub fn register_embedded_material_icons(
454 provider: &mut IconProviderHandle,
455 font_bytes: &[u8],
456) -> bool {
457 use crate::font::parsed::ParsedFont;
458 use crate::parsed_font_to_font_ref;
459
460 let mut warnings = Vec::new();
461 let parsed_font = match ParsedFont::from_bytes(font_bytes, 0, &mut warnings) {
462 Some(f) => f,
463 None => {
464 return false;
465 }
466 };
467
468 let font_ref = parsed_font_to_font_ref(parsed_font);
469 register_material_icons(provider, font_ref);
470
471 true
472}
473
474#[cfg(not(all(feature = "icons", feature = "text_layout")))]
475pub fn register_embedded_material_icons(
476 _provider: &mut IconProviderHandle,
477 _font_bytes: &[u8],
478) -> bool {
479 false
481}
482
483pub fn create_default_icon_provider() -> IconProviderHandle {
489 IconProviderHandle::with_resolver(default_icon_resolver)
490}
491
492#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
507 fn test_default_resolver_no_data() {
508 let style = SystemStyle::default();
509 let original = StyledDom::default();
510
511 let result = default_icon_resolver(OptionRefAny::None, &original, &style);
512
513 assert_eq!(result.node_data.as_ref().len(), 1);
515 }
516
517 #[test]
518 fn test_create_default_provider() {
519 let provider = create_default_icon_provider();
520 assert!(provider.list_packs().is_empty());
521 }
522}