Skip to main content

freya_core/elements/
label.rs

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