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