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
54pub fn label() -> Label {
65 Label {
66 key: DiffKey::None,
67 element: LabelElement::default(),
68 }
69}
70
71impl From<&str> for Element {
72 fn from(value: &str) -> Self {
73 label().text(value.to_string()).into()
74 }
75}
76
77impl From<String> for Element {
78 fn from(value: String) -> Self {
79 label().text(value).into()
80 }
81}
82
83pub enum TextWidth {
84 Fit,
85 Max,
86}
87
88#[derive(PartialEq, Clone)]
89pub struct LabelElement {
90 pub text: Cow<'static, str>,
91 pub accessibility: AccessibilityData,
92 pub text_style_data: TextStyleData,
93 pub layout: LayoutData,
94 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
95 pub max_lines: Option<usize>,
96 pub line_height: Option<f32>,
97 pub relative_layer: Layer,
98}
99
100impl Default for LabelElement {
101 fn default() -> Self {
102 let mut accessibility = AccessibilityData::default();
103 accessibility.builder.set_role(accesskit::Role::Label);
104 Self {
105 text: Default::default(),
106 accessibility,
107 text_style_data: Default::default(),
108 layout: Default::default(),
109 event_handlers: Default::default(),
110 max_lines: None,
111 line_height: None,
112 relative_layer: Layer::default(),
113 }
114 }
115}
116
117impl ElementExt for LabelElement {
118 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
119 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
120 return false;
121 };
122 self != label
123 }
124
125 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
126 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
127 return DiffModifies::all();
128 };
129
130 let mut diff = DiffModifies::empty();
131
132 if self.text != label.text {
133 diff.insert(DiffModifies::STYLE);
134 diff.insert(DiffModifies::LAYOUT);
135 }
136
137 if self.accessibility != label.accessibility {
138 diff.insert(DiffModifies::ACCESSIBILITY);
139 }
140
141 if self.relative_layer != label.relative_layer {
142 diff.insert(DiffModifies::LAYER);
143 }
144
145 if self.text_style_data != label.text_style_data
146 || self.line_height != label.line_height
147 || self.max_lines != label.max_lines
148 {
149 diff.insert(DiffModifies::TEXT_STYLE);
150 diff.insert(DiffModifies::LAYOUT);
151 }
152 if self.layout != label.layout {
153 diff.insert(DiffModifies::LAYOUT);
154 }
155
156 if self.event_handlers != label.event_handlers {
157 diff.insert(DiffModifies::EVENT_HANDLERS);
158 }
159
160 diff
161 }
162
163 fn layout(&'_ self) -> Cow<'_, LayoutData> {
164 Cow::Borrowed(&self.layout)
165 }
166
167 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
168 None
169 }
170
171 fn style(&'_ self) -> Cow<'_, StyleState> {
172 Cow::Owned(StyleState::default())
173 }
174
175 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
176 Cow::Borrowed(&self.text_style_data)
177 }
178
179 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
180 Cow::Borrowed(&self.accessibility)
181 }
182
183 fn layer(&self) -> Layer {
184 self.relative_layer
185 }
186
187 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
188 Some(Cow::Borrowed(&self.event_handlers))
189 }
190
191 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
192 let cached_paragraph = CachedParagraph {
193 text_style_state: context.text_style_state,
194 spans: &[Span::new(&*self.text)],
195 max_lines: None,
196 line_height: None,
197 width: context.area_size.width,
198 };
199 let paragraph = context
200 .text_cache
201 .utilize(context.node_id, &cached_paragraph)
202 .unwrap_or_else(|| {
203 let mut paragraph_style = ParagraphStyle::default();
204 let mut text_style = TextStyle::default();
205
206 let mut font_families = context.text_style_state.font_families.clone();
207 font_families.extend_from_slice(context.fallback_fonts);
208
209 text_style.set_color(context.text_style_state.color);
210 text_style.set_font_size(
211 f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
212 );
213 text_style.set_font_families(&font_families);
214 text_style.set_font_style(FontStyle::new(
215 context.text_style_state.font_weight.into(),
216 context.text_style_state.font_width.into(),
217 context.text_style_state.font_slant.into(),
218 ));
219
220 if context.text_style_state.text_height.needs_custom_height() {
221 text_style.set_height_override(true);
222 text_style.set_half_leading(true);
223 }
224
225 if let Some(line_height) = self.line_height {
226 text_style.set_height_override(true).set_height(line_height);
227 }
228
229 for text_shadow in context.text_style_state.text_shadows.iter() {
230 text_style.add_shadow((*text_shadow).into());
231 }
232
233 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
234 paragraph_style.set_ellipsis(ellipsis);
235 }
236
237 paragraph_style.set_text_style(&text_style);
238 paragraph_style.set_max_lines(self.max_lines);
239 paragraph_style.set_text_align(context.text_style_state.text_align.into());
240
241 let mut paragraph_builder =
242 ParagraphBuilder::new(¶graph_style, context.font_collection);
243
244 paragraph_builder.add_text(&self.text);
245
246 let mut paragraph = paragraph_builder.build();
247 paragraph.layout(
248 if self.max_lines == Some(1)
249 && context.text_style_state.text_align == TextAlign::default()
250 && !paragraph_style.ellipsized()
251 {
252 f32::MAX
253 } else {
254 context.area_size.width + 1.0
255 },
256 );
257
258 context
259 .text_cache
260 .insert(context.node_id, &cached_paragraph, paragraph)
261 });
262
263 let size = Size2D::new(paragraph.longest_line(), paragraph.height());
264
265 Some((size, paragraph))
266 }
267
268 fn should_hook_measurement(&self) -> bool {
269 true
270 }
271
272 fn should_measure_inner_children(&self) -> bool {
273 false
274 }
275
276 fn clip(&self, context: ClipContext) {
277 let area = context.visible_area;
278 context.canvas.clip_rect(
279 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
280 ClipOp::Intersect,
281 true,
282 );
283 }
284
285 fn render(&self, context: RenderContext) {
286 let layout_data = context.layout_node.data.as_ref().unwrap();
287 let paragraph = layout_data.downcast_ref::<SkParagraph>().unwrap();
288
289 paragraph.paint(context.canvas, context.layout_node.area.origin.to_tuple());
290 }
291}
292
293impl From<Label> for Element {
294 fn from(value: Label) -> Self {
295 Element::Element {
296 key: value.key,
297 element: Rc::new(value.element),
298 elements: vec![],
299 }
300 }
301}
302
303impl KeyExt for Label {
304 fn write_key(&mut self) -> &mut DiffKey {
305 &mut self.key
306 }
307}
308
309impl EventHandlersExt for Label {
310 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
311 &mut self.element.event_handlers
312 }
313}
314
315impl AccessibilityExt for Label {
316 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
317 &mut self.element.accessibility
318 }
319}
320
321impl TextStyleExt for Label {
322 fn get_text_style_data(&mut self) -> &mut TextStyleData {
323 &mut self.element.text_style_data
324 }
325}
326
327impl LayerExt for Label {
328 fn get_layer(&mut self) -> &mut Layer {
329 &mut self.element.relative_layer
330 }
331}
332
333impl MaybeExt for Label {}
334
335pub struct Label {
336 key: DiffKey,
337 element: LabelElement,
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 {}