use alloc::{
string::{String, ToString},
vec::Vec,
};
use azul_css::{
AzString, OptionString,
system::SystemStyle,
props::basic::{FontRef, StyleFontFamily, StyleFontFamilyVec},
props::basic::font::StyleFontSize,
props::basic::color::ColorU,
props::basic::length::FloatValue,
props::layout::{LayoutWidth, LayoutHeight},
props::property::CssProperty,
props::style::filter::{StyleFilter, StyleFilterVec, StyleColorMatrix},
props::style::text::StyleTextColor,
dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
css::{Css, CssPropertyValue},
};
use azul_core::{
dom::{Dom, NodeData, NodeType, AccessibilityInfo, AccessibilityRole, OptionDomNodeId, AccessibilityStateVec},
icon::{IconProviderHandle, IconResolverCallbackType},
refany::{OptionRefAny, RefAny},
resources::ImageRef,
styled_dom::StyledDom,
window::OptionVirtualKeyCodeCombo,
};
pub struct ImageIconData {
pub image: ImageRef,
pub width: f32,
pub height: f32,
}
pub struct FontIconData {
pub font: FontRef,
pub icon_char: String,
}
pub extern "C" fn default_icon_resolver(
icon_data: OptionRefAny,
original_icon_dom: &StyledDom,
system_style: &SystemStyle,
) -> StyledDom {
let Some(mut data) = icon_data.into_option() else {
let mut dom = Dom::create_div();
return StyledDom::create(&mut dom, Css::empty());
};
if let Some(img) = data.downcast_ref::<ImageIconData>() {
return create_image_icon_from_original(&*img, original_icon_dom, system_style);
}
if let Some(font_icon) = data.downcast_ref::<FontIconData>() {
return create_font_icon_from_original(&*font_icon, original_icon_dom, system_style);
}
let mut dom = Dom::create_div();
StyledDom::create(&mut dom, Css::empty())
}
fn create_image_icon_from_original(
img: &ImageIconData,
original: &StyledDom,
system_style: &SystemStyle,
) -> StyledDom {
let mut dom = Dom::create_image(img.image.clone());
if let Some(original_node) = original.node_data.as_ref().first() {
let mut props_vec = copy_appropriate_styles_vec(original_node);
let has_width = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Width(_)));
let has_height = props_vec.iter().any(|p| matches!(&p.property, CssProperty::Height(_)));
if !has_width {
props_vec.push(CssPropertyWithConditions::simple(
CssProperty::width(LayoutWidth::px(img.width))
));
}
if !has_height {
props_vec.push(CssPropertyWithConditions::simple(
CssProperty::height(LayoutHeight::px(img.height))
));
}
apply_icon_style_filters(&mut props_vec, system_style);
dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
if let Some(a11y) = original_node.get_accessibility_info() {
dom = dom.with_accessibility_info(*a11y.clone());
}
} else {
let mut props_vec = vec![
CssPropertyWithConditions::simple(CssProperty::width(LayoutWidth::px(img.width))),
CssPropertyWithConditions::simple(CssProperty::height(LayoutHeight::px(img.height))),
];
apply_icon_style_filters(&mut props_vec, system_style);
dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
}
StyledDom::create(&mut dom, Css::empty())
}
fn create_font_icon_from_original(
font_icon: &FontIconData,
original: &StyledDom,
system_style: &SystemStyle,
) -> StyledDom {
let mut dom = Dom::create_text(font_icon.icon_char.clone());
let font_prop = CssPropertyWithConditions::simple(
CssProperty::font_family(StyleFontFamilyVec::from_vec(vec![
StyleFontFamily::Ref(font_icon.font.clone())
]))
);
if let Some(original_node) = original.node_data.as_ref().first() {
let mut props_vec = copy_appropriate_styles_vec(original_node);
props_vec.push(font_prop);
apply_font_icon_color(&mut props_vec, system_style);
dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
if let Some(a11y) = original_node.get_accessibility_info() {
dom = dom.with_accessibility_info(*a11y.clone());
}
} else {
let mut props_vec = vec![font_prop];
apply_font_icon_color(&mut props_vec, system_style);
dom.root.set_css_props(CssPropertyWithConditionsVec::from_vec(props_vec));
}
StyledDom::create(&mut dom, Css::empty())
}
fn copy_appropriate_styles_vec(
original_node: &NodeData,
) -> Vec<CssPropertyWithConditions> {
let original_props = original_node.get_css_props();
original_props.as_ref().iter().cloned().collect()
}
fn apply_icon_style_filters(
props_vec: &mut Vec<CssPropertyWithConditions>,
system_style: &SystemStyle,
) {
let icon_style = &system_style.icon_style;
let mut filters = Vec::new();
if icon_style.prefer_grayscale {
let grayscale_matrix = StyleColorMatrix {
m0: FloatValue::new(0.2126),
m1: FloatValue::new(0.7152),
m2: FloatValue::new(0.0722),
m3: FloatValue::new(0.0),
m4: FloatValue::new(0.0),
m5: FloatValue::new(0.2126),
m6: FloatValue::new(0.7152),
m7: FloatValue::new(0.0722),
m8: FloatValue::new(0.0),
m9: FloatValue::new(0.0),
m10: FloatValue::new(0.2126),
m11: FloatValue::new(0.7152),
m12: FloatValue::new(0.0722),
m13: FloatValue::new(0.0),
m14: FloatValue::new(0.0),
m15: FloatValue::new(0.0),
m16: FloatValue::new(0.0),
m17: FloatValue::new(0.0),
m18: FloatValue::new(1.0),
m19: FloatValue::new(0.0),
};
filters.push(StyleFilter::ColorMatrix(grayscale_matrix));
}
if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
filters.push(StyleFilter::Flood(*tint));
}
if !filters.is_empty() {
props_vec.push(CssPropertyWithConditions::simple(
CssProperty::Filter(CssPropertyValue::Exact(StyleFilterVec::from_vec(filters)))
));
}
}
fn apply_font_icon_color(
props_vec: &mut Vec<CssPropertyWithConditions>,
system_style: &SystemStyle,
) {
let icon_style = &system_style.icon_style;
if let azul_css::props::basic::color::OptionColorU::Some(tint) = &icon_style.tint_color {
props_vec.push(CssPropertyWithConditions::simple(
CssProperty::TextColor(CssPropertyValue::Exact(StyleTextColor { inner: *tint }))
));
}
}
pub fn register_image_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, image: ImageRef) {
let size = image.get_size();
let data = ImageIconData {
image,
width: size.width,
height: size.height,
};
provider.register_icon(pack_name, icon_name, RefAny::new(data));
}
#[cfg(feature = "zip_support")]
pub fn register_icons_from_zip(provider: &mut IconProviderHandle, pack_name: &str, zip_bytes: &[u8]) {
for (icon_name, image, width, height) in load_images_from_zip(zip_bytes) {
let data = ImageIconData { image, width, height };
provider.register_icon(pack_name, &icon_name, RefAny::new(data));
}
}
#[cfg(not(feature = "zip_support"))]
pub fn register_icons_from_zip(_provider: &mut IconProviderHandle, _pack_name: &str, _zip_bytes: &[u8]) {
}
pub fn register_font_icon(provider: &mut IconProviderHandle, pack_name: &str, icon_name: &str, font: FontRef, icon_char: &str) {
let data = FontIconData {
font,
icon_char: icon_char.to_string()
};
provider.register_icon(pack_name, icon_name, RefAny::new(data));
}
#[cfg(all(feature = "zip_support", feature = "image_decoding"))]
fn load_images_from_zip(zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
use crate::zip::{ZipFile, ZipReadConfig};
use crate::image::decode::{decode_raw_image_from_any_bytes, ResultRawImageDecodeImageError};
use std::path::Path;
let mut result = Vec::new();
let config = ZipReadConfig::default();
let entries = match ZipFile::list(zip_bytes, &config) {
Ok(e) => e,
Err(_) => return result,
};
for entry in entries.iter() {
if entry.path.ends_with('/') { continue; }
let file_bytes = match ZipFile::get_single_file(zip_bytes, entry, &config) {
Ok(Some(b)) => b,
_ => continue,
};
if let ResultRawImageDecodeImageError::Ok(raw_image) = decode_raw_image_from_any_bytes(&file_bytes) {
let path = Path::new(&entry.path);
let icon_name = path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string();
let width = raw_image.width as f32;
let height = raw_image.height as f32;
if let Some(image) = ImageRef::new_rawimage(raw_image) {
result.push((icon_name, image, width, height));
}
}
}
result
}
#[cfg(not(all(feature = "zip_support", feature = "image_decoding")))]
fn load_images_from_zip(_zip_bytes: &[u8]) -> Vec<(String, ImageRef, f32, f32)> {
Vec::new()
}
#[cfg(feature = "icons")]
pub fn register_material_icons(provider: &mut IconProviderHandle, font: FontRef) {
use material_icons::{ALL_ICONS, icon_to_char, icon_to_html_name};
for icon in ALL_ICONS.iter() {
let icon_char = icon_to_char(*icon);
let name = icon_to_html_name(icon);
let data = FontIconData {
font: font.clone(),
icon_char: icon_char.to_string(),
};
provider.register_icon("material-icons", name, RefAny::new(data));
}
}
#[cfg(not(feature = "icons"))]
pub fn register_material_icons(_provider: &mut IconProviderHandle, _font: FontRef) {
}
#[cfg(all(feature = "icons", feature = "text_layout"))]
pub fn register_embedded_material_icons(provider: &mut IconProviderHandle) -> bool {
use crate::font::parsed::ParsedFont;
use crate::parsed_font_to_font_ref;
let font_bytes: &'static [u8] = material_icons::FONT;
let mut warnings = Vec::new();
let parsed_font = match ParsedFont::from_bytes(font_bytes, 0, &mut warnings) {
Some(f) => f,
None => {
return false;
}
};
let font_ref = parsed_font_to_font_ref(parsed_font);
register_material_icons(provider, font_ref);
true
}
#[cfg(not(all(feature = "icons", feature = "text_layout")))]
pub fn register_embedded_material_icons(_provider: &mut IconProviderHandle) -> bool {
false
}
pub fn create_default_icon_provider() -> IconProviderHandle {
IconProviderHandle::with_resolver(default_icon_resolver)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_resolver_no_data() {
let style = SystemStyle::default();
let original = StyledDom::default();
let result = default_icon_resolver(OptionRefAny::None, &original, &style);
assert_eq!(result.node_data.as_ref().len(), 1);
}
#[test]
fn test_create_default_provider() {
let provider = create_default_icon_provider();
assert!(provider.list_packs().is_empty());
}
}