use zng_ext_font::{font_features::FontFeatures, FontName, FontSize};
use zng_wgt::prelude::*;
use std::fmt;
use crate::FONT_SIZE_VAR;
#[widget($crate::icon::Icon {
($ico:expr) => {
ico = $ico;
}
})]
pub struct Icon(WidgetBase);
impl Icon {
fn widget_intrinsic(&mut self) {
widget_set! {
self;
crate::txt_align = Align::CENTER;
crate::txt_editable = false;
crate::txt_selectable = false;
}
self.widget_builder().push_build_action(|wgt| {
let icon = if let Some(icon) = wgt.capture_var::<GlyphIcon>(property_id!(ico)) {
icon
} else {
tracing::error!("missing `icon` property");
return;
};
wgt.set_child(crate::node::render_text());
wgt.push_intrinsic(NestGroup::CHILD_LAYOUT + 100, "layout_text", move |child| {
let node = crate::node::layout_text(child);
icon_size(node)
});
wgt.push_intrinsic(NestGroup::EVENT, "resolve_text", move |child| {
let node = crate::node::resolve_text(child, icon.map(|i| i.glyph.clone().into()));
let node = crate::font_family(node, icon.map(|i| i.font.clone().into()));
let node = crate::font_features(node, icon.map_ref(|i| &i.features));
crate::font_color(node, ICON_COLOR_VAR)
});
});
}
}
#[property(CONTEXT, capture, widget_impl(Icon))]
pub fn ico(ico: impl IntoVar<GlyphIcon>) {}
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum GlyphSource {
Code(char),
Ligature(Txt),
}
impl_from_and_into_var! {
fn from(code: char) -> GlyphSource {
GlyphSource::Code(code)
}
fn from(ligature: &'static str) -> GlyphSource {
Txt::from_static(ligature).into()
}
fn from(ligature: Txt) -> GlyphSource {
GlyphSource::Ligature(ligature)
}
fn from(source: GlyphSource) -> Txt {
match source {
GlyphSource::Code(c) => Txt::from_char(c),
GlyphSource::Ligature(l) => l,
}
}
}
impl fmt::Debug for GlyphSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
write!(f, "GlyphSource::")?;
}
match self {
GlyphSource::Code(c) => write!(f, "Code({c:?})"),
GlyphSource::Ligature(l) => write!(f, "Ligature({l:?})"),
}
}
}
impl fmt::Display for GlyphSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GlyphSource::Code(c) => write!(f, "{c}"),
GlyphSource::Ligature(l) => write!(f, "{l}"),
}
}
}
#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
pub struct GlyphIcon {
pub font: FontName,
pub features: FontFeatures,
pub glyph: GlyphSource,
}
impl GlyphIcon {
pub fn new(font: impl Into<FontName>, glyph: impl Into<GlyphSource>) -> Self {
GlyphIcon {
font: font.into(),
features: FontFeatures::new(),
glyph: glyph.into(),
}
}
pub fn with_ligatures(mut self) -> Self {
self.features.common_lig().enable();
self.features.historical_lig().enable();
self.features.discretionary_lig().enable();
self
}
}
impl_from_and_into_var! {
fn from<F: Into<FontName>, G: Into<GlyphSource>>((name, glyph): (F, G)) -> GlyphIcon {
GlyphIcon::new(name, glyph)
}
}
context_var! {
pub static ICON_SIZE_VAR: FontSize = FontSize::Default;
pub static ICON_COLOR_VAR: Rgba = crate::FONT_COLOR_VAR;
}
#[property(CONTEXT, default(ICON_SIZE_VAR), widget_impl(Icon))]
pub fn ico_size(child: impl UiNode, size: impl IntoVar<FontSize>) -> impl UiNode {
with_context_var(child, ICON_SIZE_VAR, size)
}
#[property(CONTEXT, default(ICON_COLOR_VAR), widget_impl(Icon))]
pub fn ico_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
with_context_var(child, ICON_COLOR_VAR, color)
}
fn icon_size(child: impl UiNode) -> impl UiNode {
match_node(child, |child, op| match op {
UiNodeOp::Init => {
WIDGET.sub_var_layout(&ICON_SIZE_VAR);
}
UiNodeOp::Measure { wm, desired_size } => {
let font_size = ICON_SIZE_VAR.get();
let s = LAYOUT.constraints().fill_size();
let mut default_size = s.width.min(s.height);
if default_size == 0 {
default_size = FONT_SIZE_VAR.layout_x();
}
let font_size_px = font_size.layout_dft_x(default_size);
*desired_size = if font_size_px >= 0 {
LAYOUT.with_font_size(font_size_px, || child.measure(wm))
} else {
tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
child.measure(wm)
};
}
UiNodeOp::Layout { wl, final_size } => {
let font_size = ICON_SIZE_VAR.get();
let s = LAYOUT.constraints().fill_size();
let mut default_size = s.width.min(s.height);
if default_size == 0 {
default_size = FONT_SIZE_VAR.layout_x();
}
let font_size_px = font_size.layout_dft_x(default_size);
*final_size = if font_size_px >= 0 {
LAYOUT.with_font_size(font_size_px, || child.layout(wl))
} else {
tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
child.layout(wl)
};
}
_ => {}
})
}