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 `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#[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#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
63pub enum GlyphSource {
64 Code(char),
66 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#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
108pub struct GlyphIcon {
109 pub font: FontName,
111 pub features: FontFeatures,
113 pub glyph: GlyphSource,
115}
116impl GlyphIcon {
117 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 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 pub static ICON_SIZE_VAR: FontSize = FontSize::Default;
145
146 pub static ICON_COLOR_VAR: Rgba = crate::FONT_COLOR_VAR;
152}
153
154#[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#[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
172fn 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}