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};
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 EventHandlers,
35 LayoutContext,
36 RenderContext,
37 },
38 elements::paragraph::ParagraphPaintExt,
39 layers::Layer,
40 prelude::{
41 AccessibilityExt,
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 diff.insert(DiffModifies::ACCESSIBILITY);
138 }
139
140 if self.accessibility != label.accessibility {
141 diff.insert(DiffModifies::ACCESSIBILITY);
142 }
143
144 if self.relative_layer != label.relative_layer {
145 diff.insert(DiffModifies::LAYER);
146 }
147
148 if self.text_style_data != label.text_style_data
149 || self.line_height != label.line_height
150 || self.max_lines != label.max_lines
151 {
152 diff.insert(DiffModifies::TEXT_STYLE);
153 diff.insert(DiffModifies::LAYOUT);
154 }
155 if self.layout != label.layout {
156 diff.insert(DiffModifies::LAYOUT);
157 }
158
159 if self.event_handlers != label.event_handlers {
160 diff.insert(DiffModifies::EVENT_HANDLERS);
161 }
162
163 diff
164 }
165
166 fn layout(&'_ self) -> Cow<'_, LayoutData> {
167 Cow::Borrowed(&self.layout)
168 }
169
170 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
171 None
172 }
173
174 fn style(&'_ self) -> Cow<'_, StyleState> {
175 Cow::Owned(StyleState::default())
176 }
177
178 fn is_transparent(&self) -> bool {
179 false
180 }
181
182 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
183 Cow::Borrowed(&self.text_style_data)
184 }
185
186 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
187 Cow::Borrowed(&self.accessibility)
188 }
189
190 fn finish_accessibility(&self, builder: &mut accesskit::Node) {
191 builder.set_value(self.text.clone());
192 }
193
194 fn layer(&self) -> Layer {
195 self.relative_layer
196 }
197
198 fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
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 build = |fill_area: Area| {
215 let mut paragraph_style = ParagraphStyle::default();
216
217 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
218 paragraph_style.set_ellipsis(ellipsis);
219 }
220
221 paragraph_style.set_text_style(&context.text_style_state.to_text_style(
222 context.fallback_fonts,
223 context.scale_factor,
224 self.line_height,
225 fill_area,
226 ));
227 paragraph_style.set_max_lines(self.max_lines);
228 paragraph_style.set_text_align(context.text_style_state.text_align.into());
229
230 let mut paragraph_builder =
231 ParagraphBuilder::new(¶graph_style, &*context.font_collection);
232
233 paragraph_builder.add_text(&self.text);
234
235 let mut paragraph = paragraph_builder.build();
236 paragraph.layout(
237 if self.max_lines == Some(1)
238 && context.text_style_state.text_align == TextAlign::default()
239 && !paragraph_style.ellipsized()
240 {
241 f32::MAX
242 } else {
243 context.area_size.width + 1.0
244 },
245 );
246 paragraph
247 };
248
249 let mut paragraph = build(Area::default());
250
251 if context.text_style_state.color.as_color().is_none() {
252 paragraph = build(paragraph.fill_area());
253 }
254
255 context
256 .text_cache
257 .insert(context.node_id, &cached_paragraph, paragraph)
258 });
259
260 let size = Size2D::new(paragraph.longest_line(), paragraph.height()).max(Size2D::zero());
261
262 Some((size, paragraph))
263 }
264
265 fn should_hook_measurement(&self) -> bool {
266 true
267 }
268
269 fn should_measure_inner_children(&self) -> bool {
270 false
271 }
272
273 fn clip(&self, context: ClipContext) {
274 let area = context.visible_area;
275 context.canvas.clip_rect(
276 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
277 ClipOp::Intersect,
278 true,
279 );
280 }
281
282 fn render(&self, context: RenderContext) {
283 let layout_data = context.layout_node.data.as_ref().unwrap();
284 let paragraph = layout_data.downcast_ref::<SkParagraph>().unwrap();
285
286 paragraph.paint_at(context.canvas, context.layout_node.visible_area().origin);
287 }
288}
289
290impl From<Label> for Element {
291 fn from(value: Label) -> Self {
292 Element::Element {
293 key: value.key,
294 element: Rc::new(value.element),
295 elements: vec![],
296 }
297 }
298}
299
300impl KeyExt for Label {
301 fn write_key(&mut self) -> &mut DiffKey {
302 &mut self.key
303 }
304}
305
306impl EventHandlersExt for Label {
307 fn get_event_handlers(&mut self) -> &mut EventHandlers {
308 &mut self.element.event_handlers
309 }
310}
311
312impl AccessibilityExt for Label {
313 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
314 &mut self.element.accessibility
315 }
316}
317
318impl TextStyleExt for Label {
319 fn get_text_style_data(&mut self) -> &mut TextStyleData {
320 &mut self.element.text_style_data
321 }
322}
323
324impl LayerExt for Label {
325 fn get_layer(&mut self) -> &mut Layer {
326 &mut self.element.relative_layer
327 }
328}
329
330impl MaybeExt for Label {}
331
332#[derive(Default, Clone)]
333pub struct Label {
334 key: DiffKey,
335 element: LabelElement,
336}
337
338impl Label {
339 pub fn try_downcast(element: &dyn ElementExt) -> Option<LabelElement> {
340 (element as &dyn Any)
341 .downcast_ref::<LabelElement>()
342 .cloned()
343 }
344
345 pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
347 let text = text.into();
348 self.element.text = text;
349 self
350 }
351
352 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
354 self.element.max_lines = max_lines.into();
355 self
356 }
357
358 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
360 self.element.line_height = line_height.into();
361 self
362 }
363}
364
365impl LayoutExt for Label {
366 fn get_layout(&mut self) -> &mut LayoutData {
367 &mut self.element.layout
368 }
369}
370
371impl ContainerExt for Label {}