1use std::{
4 any::Any,
5 borrow::Cow,
6 rc::Rc,
7};
8
9use freya_engine::prelude::{
10 ClipOp,
11 ParagraphBuilder,
12 ParagraphStyle,
13 SkParagraph,
14 SkRect,
15};
16use torin::prelude::{
17 Area,
18 Size2D,
19 SizeModel,
20};
21
22use crate::{
23 data::{
24 AccessibilityData,
25 EffectData,
26 LayoutData,
27 StyleState,
28 TextStyleData,
29 },
30 diff_key::DiffKey,
31 element::{
32 ClipContext,
33 Element,
34 ElementExt,
35 EventHandlers,
36 LayoutContext,
37 RenderContext,
38 },
39 elements::paragraph::ParagraphPaintExt,
40 layers::Layer,
41 prelude::{
42 AccessibilityExt,
43 ContainerExt,
44 EffectExt,
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::default()
70}
71
72impl From<&str> for Element {
73 fn from(value: &str) -> Self {
74 label().text(value.to_string()).into()
75 }
76}
77
78impl From<String> for Element {
79 fn from(value: String) -> Self {
80 label().text(value).into()
81 }
82}
83
84pub enum TextWidth {
86 Fit,
88 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: EventHandlers,
99 pub max_lines: Option<usize>,
100 pub line_height: Option<f32>,
101 pub relative_layer: Layer,
102 pub effect: Option<EffectData>,
103}
104
105impl Default for LabelElement {
106 fn default() -> Self {
107 let mut accessibility = AccessibilityData::default();
108 accessibility.builder.set_role(accesskit::Role::Label);
109 Self {
110 text: Default::default(),
111 accessibility,
112 text_style_data: Default::default(),
113 layout: Default::default(),
114 event_handlers: Default::default(),
115 max_lines: None,
116 line_height: None,
117 relative_layer: Layer::default(),
118 effect: None,
119 }
120 }
121}
122
123impl ElementExt for LabelElement {
124 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
125 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
126 return false;
127 };
128 self != label
129 }
130
131 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
132 let Some(label) = (other.as_ref() as &dyn Any).downcast_ref::<LabelElement>() else {
133 return DiffModifies::all();
134 };
135
136 let mut diff = DiffModifies::empty();
137
138 if self.text != label.text {
139 diff.insert(DiffModifies::STYLE);
140 diff.insert(DiffModifies::LAYOUT);
141 diff.insert(DiffModifies::ACCESSIBILITY);
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.effect != label.effect {
153 diff.insert(DiffModifies::EFFECT);
154 }
155
156 if self.text_style_data != label.text_style_data
157 || self.line_height != label.line_height
158 || self.max_lines != label.max_lines
159 {
160 diff.insert(DiffModifies::TEXT_STYLE);
161 diff.insert(DiffModifies::LAYOUT);
162 }
163 if self.layout != label.layout {
164 diff.insert(DiffModifies::LAYOUT);
165 }
166
167 if self.event_handlers != label.event_handlers {
168 diff.insert(DiffModifies::EVENT_HANDLERS);
169 }
170
171 diff
172 }
173
174 fn layout(&'_ self) -> Cow<'_, LayoutData> {
175 Cow::Borrowed(&self.layout)
176 }
177
178 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
179 self.effect.as_ref().map(Cow::Borrowed)
180 }
181
182 fn style(&'_ self) -> Cow<'_, StyleState> {
183 Cow::Owned(StyleState::default())
184 }
185
186 fn is_transparent(&self) -> bool {
187 false
188 }
189
190 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
191 Cow::Borrowed(&self.text_style_data)
192 }
193
194 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
195 Cow::Borrowed(&self.accessibility)
196 }
197
198 fn finish_accessibility(&self, builder: &mut accesskit::Node) {
199 builder.set_value(self.text.clone());
200 }
201
202 fn layer(&self) -> Layer {
203 self.relative_layer
204 }
205
206 fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
207 Some(Cow::Borrowed(&self.event_handlers))
208 }
209
210 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
211 let content_area_size =
212 (*context.area_size - context.torin_node.padding.into()).max(Size2D::zero());
213 let cached_paragraph = CachedParagraph {
214 text_style_state: context.text_style_state,
215 spans: &[Span::new(&*self.text)],
216 max_lines: self.max_lines,
217 line_height: self.line_height,
218 width: content_area_size.width,
219 };
220 let paragraph = context
221 .text_cache
222 .utilize(context.node_id, &cached_paragraph)
223 .unwrap_or_else(|| {
224 let build = |fill_area: Area| {
225 let mut paragraph_style = ParagraphStyle::default();
226
227 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
228 paragraph_style.set_ellipsis(ellipsis);
229 }
230
231 let text_style = context.text_style_state.to_text_style(
232 context.fallback_fonts,
233 context.scale_factor,
234 self.line_height,
235 fill_area,
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.push_style(&text_style);
245 paragraph_builder.add_text(&self.text);
246
247 let mut paragraph = paragraph_builder.build();
248 paragraph.layout(
249 if self.max_lines == Some(1)
250 && context.text_style_state.text_align == TextAlign::default()
251 && !paragraph_style.ellipsized()
252 {
253 f32::MAX
254 } else {
255 content_area_size.width + 1.0
256 },
257 );
258 paragraph
259 };
260
261 let mut paragraph = build(Area::default());
262
263 if context.text_style_state.color.as_color().is_none() {
264 paragraph = build(paragraph.fill_area());
265 }
266
267 context
268 .text_cache
269 .insert(context.node_id, &cached_paragraph, paragraph)
270 });
271
272 let size = Size2D::new(paragraph.longest_line(), paragraph.height())
273 .max(Size2D::zero())
274 .with_gaps(&context.torin_node.padding);
275
276 Some((size, paragraph))
277 }
278
279 fn should_hook_measurement(&self) -> bool {
280 true
281 }
282
283 fn should_measure_inner_children(&self) -> bool {
284 false
285 }
286
287 fn clip(&self, context: ClipContext) {
288 let area = context.visible_area;
289 context.canvas.clip_rect(
290 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
291 ClipOp::Intersect,
292 true,
293 );
294 }
295
296 fn render(&self, context: RenderContext) {
297 let layout_data = context.layout_node.data.as_ref().unwrap();
298 let paragraph = layout_data.downcast_ref::<SkParagraph>().unwrap();
299
300 let origin = context.layout_node.inner_area.origin;
301 paragraph.paint_at(context.canvas, origin.cast_unit());
302 }
303}
304
305impl From<Label> for Element {
306 fn from(value: Label) -> Self {
307 Element::Element {
308 key: value.key,
309 element: Rc::new(value.element),
310 elements: vec![],
311 }
312 }
313}
314
315impl KeyExt for Label {
316 fn key_mut(&mut self) -> &mut DiffKey {
317 &mut self.key
318 }
319}
320
321impl EventHandlersExt for Label {
322 fn event_handlers_mut(&mut self) -> &mut EventHandlers {
323 &mut self.element.event_handlers
324 }
325}
326
327impl AccessibilityExt for Label {
328 fn accessibility_data_mut(&mut self) -> &mut AccessibilityData {
329 &mut self.element.accessibility
330 }
331}
332
333impl TextStyleExt for Label {
334 fn text_style_data_mut(&mut self) -> &mut TextStyleData {
335 &mut self.element.text_style_data
336 }
337}
338
339impl EffectExt for Label {
340 fn effect_mut(&mut self) -> &mut EffectData {
341 self.element.effect.get_or_insert_with(EffectData::default)
342 }
343}
344
345impl LayerExt for Label {
346 fn layer_mut(&mut self) -> &mut Layer {
347 &mut self.element.relative_layer
348 }
349}
350
351impl MaybeExt for Label {}
352
353#[derive(Default, Clone)]
354pub struct Label {
355 key: DiffKey,
356 element: LabelElement,
357}
358
359impl Label {
360 pub fn try_downcast(element: &dyn ElementExt) -> Option<LabelElement> {
361 (element as &dyn Any)
362 .downcast_ref::<LabelElement>()
363 .cloned()
364 }
365
366 pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
368 let text = text.into();
369 self.element.text = text;
370 self
371 }
372
373 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
375 self.element.max_lines = max_lines.into();
376 self
377 }
378
379 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
381 self.element.line_height = line_height.into();
382 self
383 }
384}
385
386impl LayoutExt for Label {
387 fn layout_mut(&mut self) -> &mut LayoutData {
388 &mut self.element.layout
389 }
390}
391
392impl ContainerExt for Label {}