freya_core/states/
font_style.rs

1use std::sync::{
2    Arc,
3    Mutex,
4};
5
6use freya_engine::prelude::*;
7use freya_native_core::{
8    attributes::AttributeName,
9    exports::shipyard::Component,
10    node_ref::NodeView,
11    prelude::{
12        AttributeMaskBuilder,
13        Dependancy,
14        NodeMaskBuilder,
15        State,
16    },
17    NodeId,
18    SendAnyMap,
19};
20use freya_native_core_macro::partial_derive_state;
21use torin::torin::Torin;
22
23use crate::{
24    custom_attributes::CustomAttributeValues,
25    dom::CompositorDirtyNodes,
26    parsing::{
27        ExtSplit,
28        Parse,
29        ParseAttribute,
30        ParseError,
31    },
32    values::{
33        TextHeight,
34        TextOverflow,
35    },
36};
37
38#[derive(Debug, Clone, PartialEq, Component)]
39pub struct FontStyleState {
40    pub color: Color,
41    pub text_shadows: Arc<[TextShadow]>,
42    pub font_family: Arc<[String]>,
43    pub font_size: f32,
44    pub font_slant: Slant,
45    pub font_weight: Weight,
46    pub font_width: Width,
47    pub line_height: Option<f32>,
48    pub decoration: Decoration,
49    pub word_spacing: f32,
50    pub letter_spacing: f32,
51    pub text_align: TextAlign,
52    pub max_lines: Option<usize>,
53    pub text_overflow: TextOverflow,
54    pub text_height: TextHeightBehavior,
55}
56
57impl FontStyleState {
58    pub fn text_style(
59        &self,
60        default_font_family: &[String],
61        scale_factor: f32,
62        paragraph_text_height: TextHeightBehavior,
63    ) -> TextStyle {
64        let mut text_style = TextStyle::new();
65
66        let mut font_family = self.font_family.to_vec();
67
68        font_family.extend_from_slice(default_font_family);
69
70        text_style
71            .set_color(self.color)
72            .set_font_style(FontStyle::new(
73                self.font_weight,
74                self.font_width,
75                self.font_slant,
76            ))
77            .set_font_size(self.font_size * scale_factor)
78            .set_font_families(&font_family)
79            .set_word_spacing(self.word_spacing)
80            .set_letter_spacing(self.letter_spacing);
81
82        if paragraph_text_height.needs_custom_height() {
83            text_style.set_height_override(true);
84            text_style.set_half_leading(true);
85        }
86
87        if let Some(line_height) = self.line_height {
88            text_style.set_height_override(true).set_height(line_height);
89        }
90
91        for text_shadow in self.text_shadows.iter() {
92            text_style.add_shadow(*text_shadow);
93        }
94
95        text_style.set_decoration_style(self.decoration.style);
96        text_style.set_decoration_type(self.decoration.ty);
97        text_style.set_decoration_color(self.decoration.color);
98
99        text_style
100    }
101}
102
103impl Default for FontStyleState {
104    fn default() -> Self {
105        Self {
106            color: Color::BLACK,
107            text_shadows: Arc::default(),
108            font_family: Arc::default(),
109            font_size: 16.0,
110            font_weight: Weight::NORMAL,
111            font_slant: Slant::Upright,
112            font_width: Width::NORMAL,
113            line_height: None,
114            word_spacing: 0.0,
115            letter_spacing: 0.0,
116            decoration: Decoration {
117                thickness_multiplier: 1.0, // Defaults to 0.0, even though 0.0 won't render anything
118                ..Decoration::default()
119            },
120            text_align: TextAlign::default(),
121            max_lines: None,
122            text_overflow: TextOverflow::default(),
123            text_height: TextHeightBehavior::DisableAll,
124        }
125    }
126}
127
128impl ParseAttribute for FontStyleState {
129    fn parse_attribute(
130        &mut self,
131        attr: freya_native_core::prelude::OwnedAttributeView<CustomAttributeValues>,
132    ) -> Result<(), ParseError> {
133        match attr.attribute {
134            AttributeName::Color => {
135                if let Some(value) = attr.value.as_text() {
136                    // Make an exception for the "inherit" as in this case we don't want to pass
137                    //  a color at all but use the inherited one.
138                    if value != "inherit" {
139                        self.color = Color::parse(value)?;
140                    }
141                }
142            }
143            AttributeName::TextShadow => {
144                if let Some(value) = attr.value.as_text() {
145                    self.text_shadows = value
146                        .split_excluding_group(',', '(', ')')
147                        .map(|chunk| TextShadow::parse(chunk).unwrap_or_default())
148                        .collect();
149                }
150            }
151            AttributeName::FontFamily => {
152                if let Some(value) = attr.value.as_text() {
153                    let families = value.split(',');
154                    self.font_family = families.into_iter().map(|f| f.trim().to_string()).collect();
155                }
156            }
157            AttributeName::FontSize => {
158                if let Some(value) = attr.value.as_text() {
159                    if let Ok(font_size) = value.parse::<f32>() {
160                        self.font_size = font_size;
161                    }
162                }
163            }
164            AttributeName::LineHeight => {
165                if let Some(value) = attr.value.as_text() {
166                    if let Ok(line_height) = value.parse::<f32>() {
167                        self.line_height = Some(line_height);
168                    }
169                }
170            }
171            AttributeName::TextAlign => {
172                if let Some(value) = attr.value.as_text() {
173                    if let Ok(text_align) = TextAlign::parse(value) {
174                        self.text_align = text_align;
175                    }
176                }
177            }
178            AttributeName::MaxLines => {
179                if let Some(value) = attr.value.as_text() {
180                    if let Ok(max_lines) = value.parse() {
181                        self.max_lines = Some(max_lines);
182                    }
183                }
184            }
185            AttributeName::TextOverflow => {
186                let value = attr.value.as_text();
187                if let Some(value) = value {
188                    if let Ok(text_overflow) = TextOverflow::parse(value) {
189                        self.text_overflow = text_overflow;
190                    }
191                }
192            }
193            AttributeName::FontStyle => {
194                if let Some(value) = attr.value.as_text() {
195                    if let Ok(font_slant) = Slant::parse(value) {
196                        self.font_slant = font_slant;
197                    }
198                }
199            }
200            AttributeName::FontWeight => {
201                if let Some(value) = attr.value.as_text() {
202                    if let Ok(font_weight) = Weight::parse(value) {
203                        self.font_weight = font_weight;
204                    }
205                }
206            }
207            AttributeName::FontWidth => {
208                if let Some(value) = attr.value.as_text() {
209                    if let Ok(font_width) = Width::parse(value) {
210                        self.font_width = font_width;
211                    }
212                }
213            }
214            AttributeName::Decoration => {
215                if let Some(value) = attr.value.as_text() {
216                    if let Ok(decoration) = TextDecoration::parse(value) {
217                        self.decoration.ty = decoration;
218                    }
219                }
220            }
221            AttributeName::DecorationStyle => {
222                if let Some(value) = attr.value.as_text() {
223                    if let Ok(style) = TextDecorationStyle::parse(value) {
224                        self.decoration.style = style;
225                    }
226                }
227            }
228            AttributeName::DecorationColor => {
229                if let Some(value) = attr.value.as_text() {
230                    if let Ok(new_decoration_color) = Color::parse(value) {
231                        self.decoration.color = new_decoration_color;
232                    }
233                } else {
234                    self.decoration.color = self.color;
235                }
236            }
237            AttributeName::WordSpacing => {
238                let value = attr.value.as_text();
239                if let Some(value) = value {
240                    if let Ok(word_spacing) = value.parse() {
241                        self.word_spacing = word_spacing;
242                    }
243                }
244            }
245            AttributeName::LetterSpacing => {
246                let value = attr.value.as_text();
247                if let Some(value) = value {
248                    if let Ok(letter_spacing) = value.parse() {
249                        self.letter_spacing = letter_spacing;
250                    }
251                }
252            }
253            AttributeName::TextHeight => {
254                let value = attr.value.as_text();
255                if let Some(value) = value {
256                    if let Ok(text_height) = TextHeightBehavior::parse(value) {
257                        self.text_height = text_height;
258                    }
259                }
260            }
261            _ => {}
262        }
263
264        Ok(())
265    }
266}
267
268#[partial_derive_state]
269impl State<CustomAttributeValues> for FontStyleState {
270    type ParentDependencies = (Self,);
271
272    type ChildDependencies = ();
273
274    type NodeDependencies = ();
275
276    const NODE_MASK: NodeMaskBuilder<'static> =
277        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&[
278            AttributeName::Color,
279            AttributeName::TextAlign,
280            AttributeName::TextShadow,
281            AttributeName::FontSize,
282            AttributeName::FontFamily,
283            AttributeName::LineHeight,
284            AttributeName::MaxLines,
285            AttributeName::FontStyle,
286            AttributeName::FontWeight,
287            AttributeName::FontWidth,
288            AttributeName::WordSpacing,
289            AttributeName::LetterSpacing,
290            AttributeName::Decoration,
291            AttributeName::DecorationColor,
292            AttributeName::DecorationStyle,
293            AttributeName::TextOverflow,
294            AttributeName::TextHeight,
295        ]));
296
297    fn update<'a>(
298        &mut self,
299        node_view: NodeView<CustomAttributeValues>,
300        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
301        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
302        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
303        context: &SendAnyMap,
304    ) -> bool {
305        let root_id = context.get::<NodeId>().unwrap();
306        let torin_layout = context.get::<Arc<Mutex<Torin<NodeId>>>>().unwrap();
307        let compositor_dirty_nodes = context.get::<Arc<Mutex<CompositorDirtyNodes>>>().unwrap();
308
309        let mut font_style = parent.map(|(v,)| v.clone()).unwrap_or_default();
310
311        if let Some(attributes) = node_view.attributes() {
312            for attr in attributes {
313                font_style.parse_safe(attr);
314            }
315        }
316
317        let changed = &font_style != self;
318
319        let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id;
320
321        if changed && !is_orphan {
322            torin_layout.lock().unwrap().invalidate(node_view.node_id());
323            compositor_dirty_nodes
324                .lock()
325                .unwrap()
326                .invalidate(node_view.node_id());
327        }
328
329        *self = font_style;
330        changed
331    }
332}