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