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, ..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 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}