freya_core/elements/
label.rs

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