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