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 `icon` 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_ref(|i| &i.features));
48                crate::font_color(node, ICON_COLOR_VAR)
49            });
50        });
51    }
52}
53
54/// The glyph icon.
55#[property(CONTEXT, capture, widget_impl(Icon))]
56pub fn ico(ico: impl IntoVar<GlyphIcon>) {}
57
58/// Identifies an icon glyph in the font set.
59#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
60pub enum GlyphSource {
61    /// Code "char" that is mapped to the glyph.
62    Code(char),
63    /// String that resolves to the glyph due to the default ligature config of the font.
64    Ligature(Txt),
65}
66impl_from_and_into_var! {
67    fn from(code: char) -> GlyphSource {
68        GlyphSource::Code(code)
69    }
70    fn from(ligature: &'static str) -> GlyphSource {
71        Txt::from_static(ligature).into()
72    }
73    fn from(ligature: Txt) -> GlyphSource {
74        GlyphSource::Ligature(ligature)
75    }
76    fn from(source: GlyphSource) -> Txt {
77        match source {
78            GlyphSource::Code(c) => Txt::from_char(c),
79            GlyphSource::Ligature(l) => l,
80        }
81    }
82}
83impl fmt::Debug for GlyphSource {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        if f.alternate() {
86            write!(f, "GlyphSource::")?;
87        }
88        match self {
89            GlyphSource::Code(c) => write!(f, "Code({c:?})"),
90            GlyphSource::Ligature(l) => write!(f, "Ligature({l:?})"),
91        }
92    }
93}
94impl fmt::Display for GlyphSource {
95    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96        match self {
97            GlyphSource::Code(c) => write!(f, "{c}"),
98            GlyphSource::Ligature(l) => write!(f, "{l}"),
99        }
100    }
101}
102
103/// Represents an icon glyph and font.
104#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
105pub struct GlyphIcon {
106    /// Icon set font name.
107    pub font: FontName,
108    /// Font features, like ligatures.
109    pub features: FontFeatures,
110    /// Icon glyph.
111    pub glyph: GlyphSource,
112}
113impl GlyphIcon {
114    /// New icon.
115    pub fn new(font: impl Into<FontName>, glyph: impl Into<GlyphSource>) -> Self {
116        GlyphIcon {
117            font: font.into(),
118            features: FontFeatures::new(),
119            glyph: glyph.into(),
120        }
121    }
122
123    /// Enable all ligatures.
124    pub fn with_ligatures(mut self) -> Self {
125        self.features.common_lig().enable();
126        self.features.historical_lig().enable();
127        self.features.discretionary_lig().enable();
128        self
129    }
130}
131impl_from_and_into_var! {
132    fn from<F: Into<FontName>, G: Into<GlyphSource>>((name, glyph): (F, G)) -> GlyphIcon {
133        GlyphIcon::new(name, glyph)
134    }
135}
136
137context_var! {
138    /// Defines the size of an icon.
139    ///
140    /// Default is auto sized or the font size if cannot auto size.
141    pub static ICON_SIZE_VAR: FontSize = FontSize::Default;
142
143    /// Defines the color of an icon.
144    ///
145    /// Inherits from [`FONT_COLOR_VAR`].
146    ///
147    /// [`FONT_COLOR_VAR`]: crate::FONT_COLOR_VAR
148    pub static ICON_COLOR_VAR: Rgba = crate::FONT_COLOR_VAR;
149}
150
151/// Sets the icon font size.
152///
153/// The [`FontSize::Default`] value enables auto size to fill, or is the `font_size` if cannot auto size.
154///
155/// Sets the [`ICON_SIZE_VAR`] that affects all icons inside the widget.
156#[property(CONTEXT, default(ICON_SIZE_VAR), widget_impl(Icon))]
157pub fn ico_size(child: impl UiNode, size: impl IntoVar<FontSize>) -> impl UiNode {
158    with_context_var(child, ICON_SIZE_VAR, size)
159}
160
161/// Sets the icon font color.
162///
163/// Sets the [`ICON_COLOR_VAR`] that affects all icons inside the widget.
164#[property(CONTEXT, default(ICON_COLOR_VAR), widget_impl(Icon))]
165pub fn ico_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
166    with_context_var(child, ICON_COLOR_VAR, color)
167}
168
169/// Set the font-size from the parent size.
170fn icon_size(child: impl UiNode) -> impl UiNode {
171    match_node(child, |child, op| match op {
172        UiNodeOp::Init => {
173            WIDGET.sub_var_layout(&ICON_SIZE_VAR);
174        }
175        UiNodeOp::Measure { wm, desired_size } => {
176            let font_size = ICON_SIZE_VAR.get();
177            let s = LAYOUT.constraints().fill_size();
178            let mut default_size = s.width.min(s.height);
179            if default_size == 0 {
180                default_size = FONT_SIZE_VAR.layout_x();
181            }
182            let font_size_px = font_size.layout_dft_x(default_size);
183            *desired_size = if font_size_px >= 0 {
184                LAYOUT.with_font_size(font_size_px, || child.measure(wm))
185            } else {
186                tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
187                child.measure(wm)
188            };
189        }
190        UiNodeOp::Layout { wl, final_size } => {
191            let font_size = ICON_SIZE_VAR.get();
192            let s = LAYOUT.constraints().fill_size();
193            let mut default_size = s.width.min(s.height);
194            if default_size == 0 {
195                default_size = FONT_SIZE_VAR.layout_x();
196            }
197            let font_size_px = font_size.layout_dft_x(default_size);
198            *final_size = if font_size_px >= 0 {
199                LAYOUT.with_font_size(font_size_px, || child.layout(wl))
200            } else {
201                tracing::error!("invalid icon font size {font_size:?} => {font_size_px:?}");
202                child.layout(wl)
203            };
204        }
205        _ => {}
206    })
207}