1use std::{
2 any::Any,
3 borrow::Cow,
4 rc::Rc,
5};
6
7use freya_engine::prelude::{
8 ClipOp,
9 FontStyle,
10 ParagraphBuilder,
11 ParagraphStyle,
12 SkParagraph,
13 SkRect,
14 TextStyle,
15};
16use rustc_hash::FxHashMap;
17use torin::prelude::Size2D;
18
19use crate::{
20 data::{
21 AccessibilityData,
22 EffectData,
23 LayoutData,
24 StyleState,
25 TextStyleData,
26 },
27 diff_key::DiffKey,
28 element::{
29 ClipContext,
30 Element,
31 ElementExt,
32 EventHandlerType,
33 LayoutContext,
34 RenderContext,
35 },
36 events::name::EventName,
37 layers::Layer,
38 prelude::{
39 AccessibilityExt,
40 ContainerExt,
41 EventHandlersExt,
42 KeyExt,
43 LayerExt,
44 LayoutExt,
45 MaybeExt,
46 Span,
47 TextAlign,
48 TextStyleExt,
49 },
50 text_cache::CachedParagraph,
51 tree::DiffModifies,
52};
53
54impl From<&str> for Element {
55 fn from(value: &str) -> Self {
56 label().text(value.to_string()).into()
57 }
58}
59
60impl From<String> for Element {
61 fn from(value: String) -> Self {
62 label().text(value).into()
63 }
64}
65
66pub enum TextWidth {
67 Fit,
68 Max,
69}
70
71#[derive(PartialEq, Clone)]
72pub struct LabelElement {
73 pub text: Cow<'static, str>,
74 pub accessibility: AccessibilityData,
75 pub text_style_data: TextStyleData,
76 pub layout: LayoutData,
77 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
78 pub max_lines: Option<usize>,
79 pub line_height: Option<f32>,
80 pub relative_layer: Layer,
81}
82
83impl Default for LabelElement {
84 fn default() -> Self {
85 let mut accessibility = AccessibilityData::default();
86 accessibility.builder.set_role(accesskit::Role::Label);
87 Self {
88 text: Default::default(),
89 accessibility,
90 text_style_data: Default::default(),
91 layout: Default::default(),
92 event_handlers: Default::default(),
93 max_lines: None,
94 line_height: None,
95 relative_layer: Layer::default(),
96 }
97 }
98}
99
100impl ElementExt for LabelElement {
101 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
102 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
103 return false;
104 };
105 self != label
106 }
107
108 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
109 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
110 return DiffModifies::all();
111 };
112
113 let mut diff = DiffModifies::empty();
114
115 if self.text != label.text {
116 diff.insert(DiffModifies::STYLE);
117 diff.insert(DiffModifies::LAYOUT);
118 }
119
120 if self.accessibility != label.accessibility {
121 diff.insert(DiffModifies::ACCESSIBILITY);
122 }
123
124 if self.relative_layer != label.relative_layer {
125 diff.insert(DiffModifies::LAYER);
126 }
127
128 if self.text_style_data != label.text_style_data
129 || self.line_height != label.line_height
130 || self.max_lines != label.max_lines
131 {
132 diff.insert(DiffModifies::TEXT_STYLE);
133 diff.insert(DiffModifies::LAYOUT);
134 }
135 if self.layout != label.layout {
136 diff.insert(DiffModifies::LAYOUT);
137 }
138
139 if self.event_handlers != label.event_handlers {
140 diff.insert(DiffModifies::EVENT_HANDLERS);
141 }
142
143 diff
144 }
145
146 fn layout(&'_ self) -> Cow<'_, LayoutData> {
147 Cow::Borrowed(&self.layout)
148 }
149
150 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
151 None
152 }
153
154 fn style(&'_ self) -> Cow<'_, StyleState> {
155 Cow::Owned(StyleState::default())
156 }
157
158 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
159 Cow::Borrowed(&self.text_style_data)
160 }
161
162 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
163 Cow::Borrowed(&self.accessibility)
164 }
165
166 fn layer(&self) -> Layer {
167 self.relative_layer
168 }
169
170 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
171 Some(Cow::Borrowed(&self.event_handlers))
172 }
173
174 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
175 let cached_paragraph = CachedParagraph {
176 text_style_state: context.text_style_state,
177 spans: &[Span::new(&*self.text)],
178 max_lines: None,
179 line_height: None,
180 width: context.area_size.width,
181 };
182 let paragraph = context
183 .text_cache
184 .utilize(context.node_id, &cached_paragraph)
185 .unwrap_or_else(|| {
186 let mut paragraph_style = ParagraphStyle::default();
187 let mut text_style = TextStyle::default();
188
189 let mut font_families = context.text_style_state.font_families.clone();
190 font_families.extend_from_slice(context.fallback_fonts);
191
192 text_style.set_color(context.text_style_state.color);
193 text_style.set_font_size(
194 f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
195 );
196 text_style.set_font_families(&font_families);
197 text_style.set_font_style(FontStyle::new(
198 context.text_style_state.font_weight.into(),
199 context.text_style_state.font_width.into(),
200 context.text_style_state.font_slant.into(),
201 ));
202
203 if context.text_style_state.text_height.needs_custom_height() {
204 text_style.set_height_override(true);
205 text_style.set_half_leading(true);
206 }
207
208 if let Some(line_height) = self.line_height {
209 text_style.set_height_override(true).set_height(line_height);
210 }
211
212 for text_shadow in context.text_style_state.text_shadows.iter() {
213 text_style.add_shadow((*text_shadow).into());
214 }
215
216 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
217 paragraph_style.set_ellipsis(ellipsis);
218 }
219
220 paragraph_style.set_text_style(&text_style);
221 paragraph_style.set_max_lines(self.max_lines);
222 paragraph_style.set_text_align(context.text_style_state.text_align.into());
223
224 let mut paragraph_builder =
225 ParagraphBuilder::new(¶graph_style, context.font_collection);
226
227 paragraph_builder.add_text(&self.text);
228
229 let mut paragraph = paragraph_builder.build();
230 paragraph.layout(
231 if self.max_lines == Some(1)
232 && context.text_style_state.text_align == TextAlign::default()
233 && !paragraph_style.ellipsized()
234 {
235 f32::MAX
236 } else {
237 context.area_size.width + 1.0
238 },
239 );
240
241 context
242 .text_cache
243 .insert(context.node_id, &cached_paragraph, paragraph)
244 });
245
246 let size = Size2D::new(paragraph.longest_line(), paragraph.height());
247
248 Some((size, paragraph))
249 }
250
251 fn should_hook_measurement(&self) -> bool {
252 true
253 }
254
255 fn should_measure_inner_children(&self) -> bool {
256 false
257 }
258
259 fn clip(&self, context: ClipContext) {
260 let area = context.visible_area;
261 context.canvas.clip_rect(
262 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
263 ClipOp::Intersect,
264 true,
265 );
266 }
267
268 fn render(&self, context: RenderContext) {
269 let layout_data = context.layout_node.data.as_ref().unwrap();
270 let paragraph = layout_data.downcast_ref::<SkParagraph>().unwrap();
271
272 paragraph.paint(context.canvas, context.layout_node.area.origin.to_tuple());
273 }
274}
275
276impl From<Label> for Element {
277 fn from(value: Label) -> Self {
278 Element::Element {
279 key: value.key,
280 element: Rc::new(value.element),
281 elements: vec![],
282 }
283 }
284}
285
286impl KeyExt for Label {
287 fn write_key(&mut self) -> &mut DiffKey {
288 &mut self.key
289 }
290}
291
292impl EventHandlersExt for Label {
293 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
294 &mut self.element.event_handlers
295 }
296}
297
298impl AccessibilityExt for Label {
299 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
300 &mut self.element.accessibility
301 }
302}
303
304impl TextStyleExt for Label {
305 fn get_text_style_data(&mut self) -> &mut TextStyleData {
306 &mut self.element.text_style_data
307 }
308}
309
310impl LayerExt for Label {
311 fn get_layer(&mut self) -> &mut Layer {
312 &mut self.element.relative_layer
313 }
314}
315
316impl MaybeExt for Label {}
317
318pub struct Label {
319 key: DiffKey,
320 element: LabelElement,
321}
322
323pub fn label() -> Label {
334 Label {
335 key: DiffKey::None,
336 element: LabelElement::default(),
337 }
338}
339
340impl Label {
341 pub fn try_downcast(element: &dyn ElementExt) -> Option<LabelElement> {
342 (element as &dyn Any)
343 .downcast_ref::<LabelElement>()
344 .cloned()
345 }
346
347 pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
348 let text = text.into();
349 self.element.accessibility.builder.set_value(text.clone());
350 self.element.text = text;
351 self
352 }
353
354 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
355 self.element.max_lines = max_lines.into();
356 self
357 }
358
359 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
360 self.element.line_height = line_height.into();
361 self
362 }
363}
364
365impl LayoutExt for Label {
366 fn get_layout(&mut self) -> &mut LayoutData {
367 &mut self.element.layout
368 }
369}
370
371impl ContainerExt for Label {}