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