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