zng_wgt_text/
icon.rs

1//! Glyph icon widget, properties and nodes.
2//!
3//! Note that no icons are embedded in this crate directly, you can manually create a [`GlyphIcon`]
4//! or use an icon set crate. See the `zng::icon::material` module for an example.
5
6use zng_ext_font::{FontName, FontSize, font_features::FontFeatures};
7use zng_wgt::prelude::*;
8
9use std::fmt;
10
11use crate::FONT_SIZE_VAR;
12
13/// Render icons defined as glyphs in an icon font.
14#[widget($crate::icon::Icon {
15    ($ico:expr) => {
16        ico = $ico;
17    }
18})]
19pub struct Icon(WidgetBase);
20impl Icon {
21    fn widget_intrinsic(&mut self) {
22        widget_set! {
23            self;
24            crate::txt_align = Align::CENTER;
25
26            // in case the icon is tested on a TextInput
27            crate::txt_editable = false;
28            crate::txt_selectable = false;
29        }
30        self.widget_builder().push_build_action(|wgt| {
31            let icon = if let Some(icon) = wgt.capture_var::<GlyphIcon>(property_id!(ico)) {
32                icon
33            } else {
34                tracing::error!("missing `ico` property");
35                return;
36            };
37
38            wgt.set_child(crate::node::render_text());
39
40            wgt.push_intrinsic(NestGroup::CHILD_LAYOUT + 100, "layout_text", move |child| {
41                let node = crate::node::layout_text(child);
42                icon_size(node)
43            });
44            wgt.push_intrinsic(NestGroup::EVENT, "resolve_text", move |child| {
45                let node = crate::node::resolve_text(child, icon.map(|i| i.glyph.clone().into()));
46                let node = crate::font_family(node, icon.map(|i| i.font.clone().into()));
47                let node = crate::font_features(node, icon.map(|i| i.features.clone()));
48                crate::font_color(node, ICON_COLOR_VAR)
49            });
50        });
51    }
52}
53
54/// The glyph icon.
55#[property(CONTEXT, widget_impl(Icon))]
56pub fn ico(wgt: &mut WidgetBuilding, ico: impl IntoVar<GlyphIcon>) {
57    let _ = ico;
58    wgt.expect_property_capture();
59}
60
61/// Identifies an icon glyph in the font set.
62#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
63pub enum GlyphSource {
64    /// Code "char" that is mapped to the glyph.
65    Code(char),
66    /// String that resolves to the glyph due to the default ligature config of the font.
67    Ligature(Txt),
68}
69impl_from_and_into_var! {
70    fn from(code: char) -> GlyphSource {
71        GlyphSource::Code(code)
72    }
73    fn from(ligature: &'static str) -> GlyphSource {
74        Txt::from_static(ligature).into()
75    }
76    fn from(ligature: Txt) -> GlyphSource {
77        GlyphSource::Ligature(ligature)
78    }
79    fn from(source: GlyphSource) -> Txt {
80        match source {
81            GlyphSource::Code(c) => Txt::from_char(c),
82            GlyphSource::Ligature(l) => l,
83        }
84    }
85}
86impl fmt::Debug for GlyphSource {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        if f.alternate() {
89            write!(f, "GlyphSource::")?;
90        }
91        match self {
92            GlyphSource::Code(c) => write!(f, "Code({c:?})"),
93            GlyphSource::Ligature(l) => write!(f, "Ligature({l:?})"),
94        }
95    }
96}
97impl fmt::Display for GlyphSource {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        match self {
100            GlyphSource::Code(c) => write!(f, "{c}"),
101            GlyphSource::Ligature(l) => write!(f, "{l}"),
102        }
103    }
104}
105
106/// Represents an icon glyph and font.
107#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
108pub struct GlyphIcon {
109    /// Icon set font name.
110    pub font: FontName,
111    /// Font features, like ligatures.
112    pub features: FontFeatures,
113    /// Icon glyph.
114    pub glyph: GlyphSource,
115}
116impl GlyphIcon {
117    /// New icon.
118    pub fn new(font: impl Into<FontName>, glyph: impl Into<GlyphSource>) -> Self {
119        GlyphIcon {
120            font: font.into(),
121            features: FontFeatures::new(),
122            glyph: glyph.into(),
123        }
124    }
125
126    /// Enable all ligatures.
127    pub fn with_ligatures(mut self) -> Self {
128        self.features.common_lig().enable();
129        self.features.historical_lig().enable();
130        self.features.discretionary_lig().enable();
131        self
132    }
133}
134impl_from_and_into_var! {
135    fn from<F: Into<FontName>, G: Into<GlyphSource>>((name, glyph): (F, G)) -> GlyphIcon {
136        GlyphIcon::new(name, glyph)
137    }
138}
139
140context_var! {
141    /// Defines the size of an icon.
142    ///
143    /// Default is auto sized or the font size if cannot auto size.
144    pub static ICON_SIZE_VAR: FontSize = FontSize::Default;
145
146    /// Defines the color of an icon.
147    ///
148    /// Inherits from [`FONT_COLOR_VAR`].
149    ///
150    /// [`FONT_COLOR_VAR`]: crate::FONT_COLOR_VAR
151    pub static ICON_COLOR_VAR: Rgba = crate::FONT_COLOR_VAR;
152}
153
154/// Sets the icon font size.
155///
156/// The [`FontSize::Default`] value enables auto size to fill, or is the `font_size` if cannot auto size.
157///
158/// Sets the [`ICON_SIZE_VAR`] that affects all icons inside the widget.
159#[property(CONTEXT, default(ICON_SIZE_VAR), widget_impl(Icon))]
160pub fn ico_size(child: impl IntoUiNode, size: impl IntoVar<FontSize>) -> UiNode {
161    with_context_var(child, ICON_SIZE_VAR, size)
162}
163
164/// Sets the icon font color.
165///
166/// Sets the [`ICON_COLOR_VAR`] that affects all icons inside the widget.
167#[property(CONTEXT, default(ICON_COLOR_VAR), widget_impl(Icon))]
168pub fn ico_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
169    with_context_var(child, ICON_COLOR_VAR, color)
170}
171
172/// Set the font-size from the parent size.
173fn icon_size(child: impl IntoUiNode) -> UiNode {
174    match_node(child, |child, op| match op {
175        UiNodeOp::Init => {
176            WIDGET.sub_var_layout(&ICON_SIZE_VAR);
177        }
178        UiNodeOp::Measure { wm, desired_size } => {
179            let font_size = ICON_SIZE_VAR.get();
180            let s = LAYOUT.constraints().fill_size();
181            let mut default_size = s.width.min(s.height);
182            if default_size == 0 {
183                default_size = FONT_SIZE_VAR.layout_x();
184            }
185            let font_size_px = font_size.layout_dft_x(default_size);
186            *desired_size = if font_size_px >= 0 {
187                LAYOUT.with_font_size(font_size_px, || child.measure(wm))
188            } else {
189                tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
190                child.measure(wm)
191            };
192        }
193        UiNodeOp::Layout { wl, final_size } => {
194            let font_size = ICON_SIZE_VAR.get();
195            let s = LAYOUT.constraints().fill_size();
196            let mut default_size = s.width.min(s.height);
197            if default_size == 0 {
198                default_size = FONT_SIZE_VAR.layout_x();
199            }
200            let font_size_px = font_size.layout_dft_x(default_size);
201            *final_size = if font_size_px >= 0 {
202                LAYOUT.with_font_size(font_size_px, || child.layout(wl))
203            } else {
204                tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
205                child.layout(wl)
206            };
207        }
208        _ => {}
209    })
210}