1use alloc::{
27 string::{String, ToString},
28 vec::Vec,
29};
30
31use azul_css::{
32 AzString, OptionString,
33 system::SystemStyle,
34 props::basic::{FontRef, StyleFontFamily, StyleFontFamilyVec},
35 props::basic::font::StyleFontSize,
36 props::basic::color::ColorU,
37 props::basic::length::FloatValue,
38 props::layout::{LayoutWidth, LayoutHeight},
39 props::property::CssProperty,
40 props::style::filter::{StyleFilter, StyleFilterVec, StyleColorMatrix},
41 props::style::text::StyleTextColor,
42 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
43 css::{Css, CssPropertyValue},
44};
45
46use azul_core::{
47 dom::{Dom, NodeData, NodeType, AccessibilityInfo, AccessibilityRole, OptionDomNodeId, AccessibilityStateVec},
48 icon::{IconProviderHandle, IconResolverCallbackType},
49 refany::{OptionRefAny, RefAny},
50 resources::ImageRef,
51 styled_dom::StyledDom,
52 window::OptionVirtualKeyCodeCombo,
53};
54
55pub struct ImageIconData {
61 pub image: ImageRef,
62 pub width: f32,
64 pub height: f32,
66}
67
68pub struct FontIconData {
70 pub font: FontRef,
71 pub icon_char: String,
73}
74
75pub extern "C" fn default_icon_resolver(
90 icon_data: OptionRefAny,
91 original_icon_dom: &StyledDom,
92 system_style: &SystemStyle,
93) -> StyledDom {
94 let Some(mut data) = icon_data.into_option() else {
96 let mut dom = Dom::create_div();
97 return StyledDom::create(&mut dom, Css::empty());
98 };
99
100 if let Some(img) = data.downcast_ref::<ImageIconData>() {
102 return create_image_icon_from_original(&*img, original_icon_dom, system_style);
103 }
104
105 if let Some(font_icon) = data.downcast_ref::<FontIconData>() {
107 return create_font_icon_from_original(&*font_icon, original_icon_dom, system_style);
108 }
109
110 let mut dom = Dom::create_div();
112 StyledDom::create(&mut dom, Css::empty())
113}
114
115fn create_image_icon_from_original(
123 img: &ImageIconData,
124 original: &StyledDom,
125 system_style: &SystemStyle,
126) -> StyledDom {
127 let mut dom = Dom::create_image(img.image.clone());
128
129 if let Some(original_node) = original.node_data.as_ref().first() {
131 let mut props_vec = copy_appropriate_styles_vec(original_node);
132
133 let has_width = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Width(_)));
135 let has_height = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Height(_)));
136
137 if !has_width {
138 props_vec.push(CssPropertyWithConditions::simple(
139 CssProperty::width(LayoutWidth::px(img.width))
140 ));
141 }
142 if !has_height {
143 props_vec.push(CssPropertyWithConditions::simple(
144 CssProperty::height(LayoutHeight::px(img.height))
145 ));
146 }
147
148 apply_icon_style_filters(&mut props_vec, system_style);
150
151 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
152
153 if let Some(a11y) = original_node.get_accessibility_info() {
155 dom = dom.with_accessibility_info(*a11y.clone());
156 }
157 } else {
158 let mut props_vec = vec![
160 CssPropertyWithConditions::simple(CssProperty::width(LayoutWidth::px(img.width))),
161 CssPropertyWithConditions::simple(CssProperty::height(LayoutHeight::px(img.height))),
162 ];
163
164 apply_icon_style_filters(&mut props_vec, system_style);
166
167 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
168 }
169
170 StyledDom::create(&mut dom, Css::empty())
171}
172
173fn create_font_icon_from_original(
179 font_icon: &FontIconData,
180 original: &StyledDom,
181 system_style: &SystemStyle,
182) -> StyledDom {
183 let mut dom = Dom::create_text(font_icon.icon_char.clone());
184
185 let font_prop = CssPropertyWithConditions::simple(
187 CssProperty::font_family(StyleFontFamilyVec::from_vec(vec![
188 StyleFontFamily::Ref(font_icon.font.clone())
189 ]))
190 );
191
192 if let Some(original_node) = original.node_data.as_ref().first() {
193 let mut props_vec = copy_appropriate_styles_vec(original_node);
194 props_vec.push(font_prop);
195
196 apply_font_icon_color(&mut props_vec, system_style);
198
199 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
200
201 if let Some(a11y) = original_node.get_accessibility_info() {
203 dom = dom.with_accessibility_info(*a11y.clone());
204 }
205 } else {
206 let mut props_vec = vec![font_prop];
208
209 apply_font_icon_color(&mut props_vec, system_style);
211
212 dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
213 }
214
215 StyledDom::create(&mut dom, Css::empty())
216}
217
218fn copy_appropriate_styles_vec(
221 original_node: &NodeData,
222) -> Vec<CssPropertyWithConditions> {
223 let original_props = original_node.get_css_props();
224 original_props.as_ref().iter().cloned().collect()
225}
226
227fn apply_icon_style_filters(
232 props_vec: &mut Vec<CssPropertyWithConditions>,
233 system_style: &SystemStyle,
234) {
235 let icon_style = &system_style.icon_style;
236
237 let mut filters = Vec::new();
239
240 if icon_style.prefer_grayscale {
243 let grayscale_matrix = StyleColorMatrix {
249 m0: FloatValue::new(0.2126),
250 m1: FloatValue::new(0.7152),
251 m2: FloatValue::new(0.0722),
252 m3: FloatValue::new(0.0),
253 m4: FloatValue::new(0.0),
254 m5: FloatValue::new(0.2126),
255 m6: FloatValue::new(0.7152),
256 m7: FloatValue::new(0.0722),
257 m8: FloatValue::new(0.0),
258 m9: FloatValue::new(0.0),
259 m10: FloatValue::new(0.2126),
260 m11: FloatValue::new(0.7152),
261 m12: FloatValue::new(0.0722),
262 m13: FloatValue::new(0.0),
263 m14: FloatValue::new(0.0),
264 m15: FloatValue::new(0.0),
265 m16: FloatValue::new(0.0),
266 m17: FloatValue::new(0.0),
267 m18: FloatValue::new(1.0),
268 m19: FloatValue::new(0.0),
269 };
270 filters.push(StyleFilter::ColorMatrix(grayscale_matrix));
271 }
272
273 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
275 filters.push(StyleFilter::Flood(*tint));
276 }
277
278 if !filters.is_empty() {
280 props_vec.push(CssPropertyWithConditions::simple(
281 CssProperty::Filter(CssPropertyValue::Exact(StyleFilterVec::from_vec(filters)))
282 ));
283 }
284}
285
286fn apply_font_icon_color(
292 props_vec: &mut Vec<CssPropertyWithConditions>,
293 system_style: &SystemStyle,
294) {
295 let icon_style = &system_style.icon_style;
296
297 if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
299 props_vec.push(CssPropertyWithConditions::simple(
300 CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor { inner: *tint }))
301 ));
302 }
303 }
306
307pub fn register_image_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, image: ImageRef) {
311 let size = image.get_size();
313 let data = ImageIconData {
314 image,
315 width: size.width,
316 height: size.height,
317 };
318 provider.register_icon(pack_name, icon_name, RefAny::new(data));
319}
320
321#[cfg(feature = "zip_support")]
323pub fn register_icons_from_zip(provider: &mut IconProviderHandle, pack_name: &str, zip_bytes: &[u8]) {
324 for (icon_name, image, width, height) in load_images_from_zip(zip_bytes) {
325 let data = ImageIconData { image, width, height };
326 provider.register_icon(pack_name, &icon_name, RefAny::new(data));
327 }
328}
329
330#[cfg(not(feature = "zip_support"))]
331pub fn register_icons_from_zip(_provider: &mut IconProviderHandle, _pack_name: &str, _zip_bytes: &[u8]) {
332 }
334
335pub fn register_font_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, font: FontRef, icon_char: &str) {
337 let data = FontIconData {
338 font,
339 icon_char: icon_char.to_string()
340 };
341 provider.register_icon(pack_name, icon_name, RefAny::new(data));
342}
343
344#[cfg(all(feature = "zip_support", feature = "image_decoding"))]
350fn load_images_from_zip(zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
351 use crate::zip::{ZipFile, ZipReadConfig};
352 use crate::image::decode::{decode_raw_image_from_any_bytes, ResultRawImageDecodeImageError};
353 use std::path::Path;
354
355 let mut result = Vec::new();
356 let config = ZipReadConfig::default();
357 let entries = match ZipFile::list(zip_bytes, &config) {
358 Ok(e) => e,
359 Err(_) => return result,
360 };
361
362 for entry in entries.iter() {
363 if entry.path.ends_with('/') { continue; } let file_bytes = match ZipFile::get_single_file(zip_bytes, entry, &config) {
366 Ok(Some(b)) => b,
367 _ => continue,
368 };
369
370 if let ResultRawImageDecodeImageError::Ok(raw_image) = decode_raw_image_from_any_bytes(&file_bytes) {
372 let path = Path::new(&entry.path);
374 let icon_name = path.file_stem()
375 .and_then(|s| s.to_str())
376 .unwrap_or("")
377 .to_string();
378
379 let width = raw_image.width as f32;
380 let height = raw_image.height as f32;
381
382 if let Some(image) = ImageRef::new_rawimage(raw_image) {
383 result.push((icon_name, image, width, height));
384 }
385 }
386 }
387
388 result
389}
390
391#[cfg(not(all(feature = "zip_support", feature = "image_decoding")))]
392fn load_images_from_zip(_zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
393 Vec::new()
394}
395
396#[cfg(feature = "icons")]
408pub fn register_material_icons(provider: &mut IconProviderHandle, font: FontRef) {
409 use material_icons::{ALL_ICONS, icon_to_char, icon_to_html_name};
410
411 for icon in ALL_ICONS.iter() {
413 let icon_char = icon_to_char(*icon);
414 let name = icon_to_html_name(icon);
415
416 let data = FontIconData {
417 font: font.clone(),
418 icon_char: icon_char.to_string(),
419 };
420 provider.register_icon("material-icons", name, RefAny::new(data));
421 }
422}
423
424#[cfg(not(feature = "icons"))]
425pub fn register_material_icons(_provider: &mut IconProviderHandle, _font: FontRef) {
426 }
428
429#[cfg(all(feature = "icons", feature = "text_layout"))]
436pub fn register_embedded_material_icons(provider: &mut IconProviderHandle) -> bool {
437 use crate::font::parsed::ParsedFont;
438 use crate::parsed_font_to_font_ref;
439
440 let font_bytes: &'static [u8] = material_icons::FONT;
442
443 let mut warnings = Vec::new();
445 let parsed_font = match ParsedFont::from_bytes(font_bytes, 0, &mut warnings) {
446 Some(f) => f,
447 None => {
448 return false;
449 }
450 };
451
452 let font_ref = parsed_font_to_font_ref(parsed_font);
454
455 register_material_icons(provider, font_ref);
457
458 true
459}
460
461#[cfg(not(all(feature = "icons", feature = "text_layout")))]
462pub fn register_embedded_material_icons(_provider: &mut IconProviderHandle) -> bool {
463 false
465}
466
467pub fn create_default_icon_provider() -> IconProviderHandle {
473 IconProviderHandle::with_resolver(default_icon_resolver)
474}
475
476#[cfg(test)]
481mod tests {
482 use super::*;
483
484 #[test]
485 fn test_default_resolver_no_data() {
486 let style = SystemStyle::default();
487 let original = StyledDom::default();
488
489 let result = default_icon_resolver(OptionRefAny::None, &original, &style);
490
491 assert_eq!(result.node_data.as_ref().len(), 1);
493 }
494
495 #[test]
496 fn test_create_default_provider() {
497 let provider = create_default_icon_provider();
498 assert!(provider.list_packs().is_empty());
499 }
500}