zng_wgt_text/
icon.rs
1use zng_ext_font::{FontName, FontSize, font_features::FontFeatures};
7use zng_wgt::prelude::*;
8
9use std::fmt;
10
11use crate::FONT_SIZE_VAR;
12
13#[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 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#[property(CONTEXT, capture, widget_impl(Icon))]
56pub fn ico(ico: impl IntoVar<GlyphIcon>) {}
57
58#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
60pub enum GlyphSource {
61 Code(char),
63 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#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
105pub struct GlyphIcon {
106 pub font: FontName,
108 pub features: FontFeatures,
110 pub glyph: GlyphSource,
112}
113impl GlyphIcon {
114 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 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 pub static ICON_SIZE_VAR: FontSize = FontSize::Default;
142
143 pub static ICON_COLOR_VAR: Rgba = crate::FONT_COLOR_VAR;
149}
150
151#[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#[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
169fn 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}