1use super::fonts::{FontFamily, FontFeature, FontVariation, FontWeight};
2use super::text::{
3 FontStyle, LineHeight, TextBaseline, TextDecoration, TextDecorationStyle,
4 TextLeadingDistribution, TextOverflow, TextScaler, TextShadow,
5};
6use incular_config::WidgetDefaults;
7use incular_core::{ChangeImpact, Color, Lerp};
8use std::sync::Arc;
9
10#[derive(Clone, Debug, PartialEq)]
12pub struct TextStyle {
13 pub inherit: bool,
14 pub has_explicit_color: bool,
15 pub has_explicit_size: bool,
16 pub has_explicit_weight: bool,
17 pub has_explicit_style: bool,
18 pub has_explicit_family: bool,
19 pub family: FontFamily,
20 pub fallback_families: Arc<[FontFamily]>,
21 pub size: f32,
22 pub weight: FontWeight,
23 pub style: FontStyle,
24 pub color: Color,
25 pub background_color: Option<Color>,
26 pub line_height: Option<LineHeight>,
27 pub letter_spacing: f32,
28 pub word_spacing: Option<f32>,
29 pub text_baseline: Option<TextBaseline>,
30 pub leading_distribution: Option<TextLeadingDistribution>,
31 pub locale: Option<String>,
32 pub foreground: Option<Color>,
33 pub background: Option<Color>,
34 pub shadows: Arc<[TextShadow]>,
35 pub font_variations: Option<Arc<str>>,
36 pub font_features: Option<Arc<str>>,
37 pub decoration: Option<TextDecoration>,
38 pub decoration_color: Option<Color>,
39 pub decoration_style: Option<TextDecorationStyle>,
40 pub decoration_thickness: Option<f32>,
41 pub debug_label: Option<String>,
42 pub overflow: Option<TextOverflow>,
43}
44
45impl TextStyle {
46 #[must_use]
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 #[must_use]
52 pub fn inherit(mut self, inherit: bool) -> Self {
53 self.inherit = inherit;
54 self
55 }
56
57 #[must_use]
58 pub fn font_family(mut self, family: impl Into<String>) -> Self {
59 self.family = FontFamily::named(family);
60 self.has_explicit_family = true;
61 self
62 }
63
64 #[must_use]
65 pub fn family(mut self, family: FontFamily) -> Self {
66 self.family = family;
67 self.has_explicit_family = true;
68 self
69 }
70
71 #[must_use]
72 pub fn fallback_families<I, S>(mut self, families: I) -> Self
73 where
74 I: IntoIterator<Item = S>,
75 S: Into<String>,
76 {
77 self.fallback_families = families.into_iter().map(FontFamily::named).collect();
78 self
79 }
80
81 #[must_use]
82 pub fn fallback_family(mut self, family: FontFamily) -> Self {
83 let mut families = self.fallback_families.to_vec();
84 families.push(family);
85 self.fallback_families = families.into();
86 self
87 }
88
89 #[must_use]
90 pub fn font_size(mut self, size: f32) -> Self {
91 self.size = size.max(0.0);
92 self.has_explicit_size = true;
93 self
94 }
95
96 #[must_use]
97 pub fn size(self, size: f32) -> Self {
98 self.font_size(size)
99 }
100
101 #[must_use]
102 pub fn font_weight(mut self, weight: FontWeight) -> Self {
103 self.weight = weight;
104 self.has_explicit_weight = true;
105 self
106 }
107
108 #[must_use]
109 pub fn bold(self) -> Self {
110 self.font_weight(FontWeight::BOLD)
111 }
112
113 #[must_use]
114 pub fn font_style(mut self, style: FontStyle) -> Self {
115 self.style = style;
116 self.has_explicit_style = true;
117 self
118 }
119
120 #[must_use]
121 pub fn italic(self) -> Self {
122 self.font_style(FontStyle::Italic)
123 }
124
125 #[must_use]
126 pub fn color(mut self, color: Color) -> Self {
127 self.color = color;
128 self.foreground = None;
129 self.has_explicit_color = true;
130 self
131 }
132
133 #[must_use]
134 pub fn background_color(mut self, color: Color) -> Self {
135 self.background_color = Some(color);
136 self.background = None;
137 self
138 }
139
140 #[must_use]
141 pub fn line_height(mut self, line_height: Option<f32>) -> Self {
142 self.line_height = line_height.map(|value| {
143 if value <= 4.0 {
144 LineHeight::Multiplier(value.max(0.0))
145 } else {
146 LineHeight::Absolute(value.max(0.0))
147 }
148 });
149 self
150 }
151
152 #[must_use]
153 pub fn height(self, height: Option<f32>) -> Self {
154 self.line_height(height)
155 }
156
157 #[must_use]
158 pub fn line_height_multiplier(mut self, factor: f32) -> Self {
159 self.line_height = Some(LineHeight::Multiplier(factor.max(0.0)));
160 self
161 }
162
163 #[must_use]
164 pub fn line_height_absolute(mut self, pixels: f32) -> Self {
165 self.line_height = Some(LineHeight::Absolute(pixels.max(0.0)));
166 self
167 }
168
169 #[must_use]
170 pub fn line_height_mode(mut self, mode: LineHeight) -> Self {
171 self.line_height = Some(mode);
172 self
173 }
174
175 #[must_use]
176 pub fn letter_spacing(mut self, letter_spacing: f32) -> Self {
177 self.letter_spacing = letter_spacing;
178 self
179 }
180
181 #[must_use]
182 pub fn word_spacing(mut self, word_spacing: Option<f32>) -> Self {
183 self.word_spacing = word_spacing;
184 self
185 }
186
187 #[must_use]
188 pub fn text_baseline(mut self, baseline: TextBaseline) -> Self {
189 self.text_baseline = Some(baseline);
190 self
191 }
192
193 #[must_use]
194 pub fn leading_distribution(mut self, distribution: TextLeadingDistribution) -> Self {
195 self.leading_distribution = Some(distribution);
196 self
197 }
198
199 #[must_use]
200 pub fn locale(mut self, locale: impl Into<String>) -> Self {
201 self.locale = Some(locale.into());
202 self
203 }
204
205 #[must_use]
206 pub fn foreground(mut self, color: Color) -> Self {
207 self.foreground = Some(color);
208 self
209 }
210
211 #[must_use]
212 pub fn background(mut self, color: Color) -> Self {
213 self.background = Some(color);
214 self.background_color = None;
215 self
216 }
217
218 #[must_use]
219 pub fn shadows<I>(mut self, shadows: I) -> Self
220 where
221 I: IntoIterator<Item = TextShadow>,
222 {
223 self.shadows = shadows.into_iter().collect::<Vec<_>>().into();
224 self
225 }
226
227 #[must_use]
228 pub fn shadow(mut self, shadow: TextShadow) -> Self {
229 let mut shadows = self.shadows.to_vec();
230 shadows.push(shadow);
231 self.shadows = shadows.into();
232 self
233 }
234
235 #[must_use]
236 pub fn font_variations(mut self, settings: impl Into<Arc<str>>) -> Self {
237 self.font_variations = Some(settings.into());
238 self
239 }
240
241 #[must_use]
242 pub fn font_variation_settings(mut self, variations: &[FontVariation]) -> Self {
243 let formatted = variations
244 .iter()
245 .map(FontVariation::to_css_setting)
246 .collect::<Vec<_>>()
247 .join(", ");
248 self.font_variations = Some(formatted.into());
249 self
250 }
251
252 #[must_use]
253 pub fn font_features(mut self, settings: impl Into<Arc<str>>) -> Self {
254 self.font_features = Some(settings.into());
255 self
256 }
257
258 #[must_use]
259 pub fn font_feature_settings(mut self, features: &[FontFeature]) -> Self {
260 let formatted = features
261 .iter()
262 .map(FontFeature::to_css_setting)
263 .collect::<Vec<_>>()
264 .join(", ");
265 self.font_features = Some(formatted.into());
266 self
267 }
268
269 #[must_use]
270 pub fn decoration(mut self, decoration: TextDecoration) -> Self {
271 self.decoration = Some(decoration);
272 self
273 }
274
275 #[must_use]
276 pub fn decoration_color(mut self, color: Color) -> Self {
277 self.decoration_color = Some(color);
278 self
279 }
280
281 #[must_use]
282 pub fn decoration_style(mut self, style: TextDecorationStyle) -> Self {
283 self.decoration_style = Some(style);
284 self
285 }
286
287 #[must_use]
288 pub fn decoration_thickness(mut self, thickness: f32) -> Self {
289 self.decoration_thickness = Some(thickness.max(0.0));
290 self
291 }
292
293 #[must_use]
294 pub fn debug_label(mut self, label: impl Into<String>) -> Self {
295 self.debug_label = Some(label.into());
296 self
297 }
298
299 #[must_use]
300 pub fn overflow(mut self, overflow: TextOverflow) -> Self {
301 self.overflow = Some(overflow);
302 self
303 }
304
305 #[must_use]
307 pub fn with_font_size(&self, size: f32) -> Self {
308 let mut s = self.clone();
309 s.size = size.max(0.0);
310 s
311 }
312
313 #[must_use]
314 pub fn with_font_weight(&self, weight: FontWeight) -> Self {
315 let mut s = self.clone();
316 s.weight = weight;
317 s
318 }
319
320 #[must_use]
321 pub fn with_color(&self, color: Color) -> Self {
322 let mut s = self.clone();
323 s.color = color;
324 s.has_explicit_color = true;
325 s
326 }
327
328 #[must_use]
329 pub fn with_letter_spacing(&self, letter_spacing: f32) -> Self {
330 let mut s = self.clone();
331 s.letter_spacing = letter_spacing;
332 s
333 }
334
335 #[must_use]
336 pub fn with_line_height(&self, line_height: Option<f32>) -> Self {
337 let mut s = self.clone();
338 s.line_height = line_height.map(|v| {
339 if v <= 4.0 {
340 LineHeight::Multiplier(v.max(0.0))
341 } else {
342 LineHeight::Absolute(v.max(0.0))
343 }
344 });
345 s
346 }
347
348 #[must_use]
349 pub fn with_decoration(&self, decoration: Option<TextDecoration>) -> Self {
350 let mut s = self.clone();
351 s.decoration = decoration;
352 s
353 }
354
355 #[must_use]
356 pub fn merge(&self, other: &Self) -> Self {
357 if !other.inherit {
358 return other.clone();
359 }
360 let mut merged = self.clone();
361 if other.has_explicit_family {
362 merged.family = other.family.clone();
363 merged.has_explicit_family = true;
364 }
365 if !other.fallback_families.is_empty() {
366 merged.fallback_families = other.fallback_families.clone();
367 }
368 if other.has_explicit_size {
369 merged.size = other.size;
370 merged.has_explicit_size = true;
371 }
372 if other.has_explicit_weight {
373 merged.weight = other.weight;
374 merged.has_explicit_weight = true;
375 }
376 if other.has_explicit_style {
377 merged.style = other.style;
378 merged.has_explicit_style = true;
379 }
380 if other.has_explicit_color || other.foreground.is_some() {
381 merged.color = other.color;
382 merged.has_explicit_color = true;
383 }
384 if other.background_color.is_some() {
385 merged.background_color = other.background_color;
386 }
387 if other.line_height.is_some() {
388 merged.line_height = other.line_height;
389 }
390 if other.letter_spacing != 0.0 {
391 merged.letter_spacing = other.letter_spacing;
392 }
393 if other.word_spacing.is_some() {
394 merged.word_spacing = other.word_spacing;
395 }
396 if other.text_baseline.is_some() {
397 merged.text_baseline = other.text_baseline;
398 }
399 if other.leading_distribution.is_some() {
400 merged.leading_distribution = other.leading_distribution;
401 }
402 if other.locale.is_some() {
403 merged.locale = other.locale.clone();
404 }
405 if other.foreground.is_some() {
406 merged.foreground = other.foreground;
407 }
408 if other.background.is_some() {
409 merged.background = other.background;
410 }
411 if !other.shadows.is_empty() {
412 merged.shadows = other.shadows.clone();
413 }
414 if other.font_variations.is_some() {
415 merged.font_variations = other.font_variations.clone();
416 }
417 if other.font_features.is_some() {
418 merged.font_features = other.font_features.clone();
419 }
420 if other.decoration.is_some() {
421 merged.decoration = other.decoration;
422 }
423 if other.decoration_color.is_some() {
424 merged.decoration_color = other.decoration_color;
425 }
426 if other.decoration_style.is_some() {
427 merged.decoration_style = other.decoration_style;
428 }
429 if other.decoration_thickness.is_some() {
430 merged.decoration_thickness = other.decoration_thickness;
431 }
432 if other.overflow.is_some() {
433 merged.overflow = other.overflow;
434 }
435 merged
436 }
437
438 #[must_use]
439 pub fn apply(
440 &self,
441 color: Option<Color>,
442 background_color: Option<Color>,
443 font_size_factor: Option<f32>,
444 font_size_delta: Option<f32>,
445 letter_spacing_factor: Option<f32>,
446 letter_spacing_delta: Option<f32>,
447 ) -> Self {
448 let mut res = self.clone();
449 if let Some(c) = color {
450 res.color = c;
451 }
452 if let Some(bg) = background_color {
453 res.background_color = Some(bg);
454 }
455 if let Some(factor) = font_size_factor {
456 res.size = (res.size * factor).max(0.0);
457 }
458 if let Some(delta) = font_size_delta {
459 res.size = (res.size + delta).max(0.0);
460 }
461 if let Some(factor) = letter_spacing_factor {
462 res.letter_spacing *= factor;
463 }
464 if let Some(delta) = letter_spacing_delta {
465 res.letter_spacing += delta;
466 }
467 res
468 }
469
470 #[must_use]
471 pub fn scaled(&self, scaler: TextScaler) -> Self {
472 let mut scaled = self.clone();
473 scaled.size = scaler.scale(self.size);
474 scaled
475 }
476
477 #[must_use]
478 pub fn change_impact(&self, other: &Self) -> ChangeImpact {
479 if self == other {
480 return ChangeImpact::None;
481 }
482 if self.family != other.family
483 || self.fallback_families != other.fallback_families
484 || self.size != other.size
485 || self.weight != other.weight
486 || self.style != other.style
487 || self.line_height != other.line_height
488 || self.letter_spacing != other.letter_spacing
489 || self.word_spacing != other.word_spacing
490 || self.text_baseline != other.text_baseline
491 || self.leading_distribution != other.leading_distribution
492 || self.font_variations != other.font_variations
493 || self.font_features != other.font_features
494 || self.overflow != other.overflow
495 {
496 ChangeImpact::Layout
497 } else {
498 ChangeImpact::Paint
499 }
500 }
501
502 #[must_use]
503 pub fn invalidation(&self, other: &Self) -> incular_core::Invalidation {
504 if self == other {
505 return incular_core::Invalidation::NONE;
506 }
507 if self.family != other.family
508 || self.fallback_families != other.fallback_families
509 || self.size != other.size
510 || self.weight != other.weight
511 || self.style != other.style
512 || self.line_height != other.line_height
513 || self.letter_spacing != other.letter_spacing
514 || self.word_spacing != other.word_spacing
515 || self.text_baseline != other.text_baseline
516 || self.leading_distribution != other.leading_distribution
517 || self.font_variations != other.font_variations
518 || self.font_features != other.font_features
519 || self.overflow != other.overflow
520 {
521 incular_core::Invalidation::LAYOUT | incular_core::Invalidation::PAINT
522 } else {
523 incular_core::Invalidation::PAINT
524 }
525 }
526}
527
528impl Lerp for TextStyle {
529 fn lerp(&self, other: &Self, t: f32) -> Self {
530 let t = t.clamp(0.0, 1.0);
531 let mix = |left: f32, right: f32| left + (right - left) * t;
532 Self {
533 inherit: if t < 0.5 { self.inherit } else { other.inherit },
534 has_explicit_color: self.has_explicit_color || other.has_explicit_color,
535 has_explicit_size: self.has_explicit_size || other.has_explicit_size,
536 has_explicit_weight: self.has_explicit_weight || other.has_explicit_weight,
537 has_explicit_style: self.has_explicit_style || other.has_explicit_style,
538 has_explicit_family: self.has_explicit_family || other.has_explicit_family,
539 family: if t < 0.5 {
540 self.family.clone()
541 } else {
542 other.family.clone()
543 },
544 fallback_families: if t < 0.5 {
545 self.fallback_families.clone()
546 } else {
547 other.fallback_families.clone()
548 },
549 size: mix(self.size, other.size),
550 weight: self.weight.lerp(&other.weight, t),
551 style: if t < 0.5 { self.style } else { other.style },
552 color: self.color.lerp(&other.color, t),
553 background_color: match (self.background_color, other.background_color) {
554 (Some(a), Some(b)) => Some(a.lerp(&b, t)),
555 (Some(a), None) => {
556 if t < 0.5 {
557 Some(a)
558 } else {
559 None
560 }
561 }
562 (None, Some(b)) => {
563 if t >= 0.5 {
564 Some(b)
565 } else {
566 None
567 }
568 }
569 (None, None) => None,
570 },
571 line_height: match (self.line_height, other.line_height) {
572 (Some(LineHeight::Multiplier(a)), Some(LineHeight::Multiplier(b))) => {
573 Some(LineHeight::Multiplier(mix(a, b)))
574 }
575 (Some(LineHeight::Absolute(a)), Some(LineHeight::Absolute(b))) => {
576 Some(LineHeight::Absolute(mix(a, b)))
577 }
578 (Some(a), Some(b)) => {
579 if t < 0.5 {
580 Some(a)
581 } else {
582 Some(b)
583 }
584 }
585 (Some(a), None) => {
586 if t < 0.5 {
587 Some(a)
588 } else {
589 None
590 }
591 }
592 (None, Some(b)) => {
593 if t >= 0.5 {
594 Some(b)
595 } else {
596 None
597 }
598 }
599 (None, None) => None,
600 },
601 letter_spacing: mix(self.letter_spacing, other.letter_spacing),
602 word_spacing: match (self.word_spacing, other.word_spacing) {
603 (Some(left), Some(right)) => Some(mix(left, right)),
604 (Some(left), None) => Some(mix(left, 0.0)),
605 (None, Some(right)) => Some(mix(0.0, right)),
606 (None, None) => None,
607 },
608 text_baseline: if t < 0.5 {
609 self.text_baseline
610 } else {
611 other.text_baseline
612 },
613 leading_distribution: if t < 0.5 {
614 self.leading_distribution
615 } else {
616 other.leading_distribution
617 },
618 locale: if t < 0.5 {
619 self.locale.clone()
620 } else {
621 other.locale.clone()
622 },
623 foreground: match (self.foreground, other.foreground) {
624 (Some(a), Some(b)) => Some(a.lerp(&b, t)),
625 (Some(a), None) => {
626 if t < 0.5 {
627 Some(a)
628 } else {
629 None
630 }
631 }
632 (None, Some(b)) => {
633 if t >= 0.5 {
634 Some(b)
635 } else {
636 None
637 }
638 }
639 (None, None) => None,
640 },
641 background: match (self.background, other.background) {
642 (Some(a), Some(b)) => Some(a.lerp(&b, t)),
643 (Some(a), None) => {
644 if t < 0.5 {
645 Some(a)
646 } else {
647 None
648 }
649 }
650 (None, Some(b)) => {
651 if t >= 0.5 {
652 Some(b)
653 } else {
654 None
655 }
656 }
657 (None, None) => None,
658 },
659 shadows: if t < 0.5 {
660 self.shadows.clone()
661 } else {
662 other.shadows.clone()
663 },
664 font_variations: if t < 0.5 {
665 self.font_variations.clone()
666 } else {
667 other.font_variations.clone()
668 },
669 font_features: if t < 0.5 {
670 self.font_features.clone()
671 } else {
672 other.font_features.clone()
673 },
674 decoration: if t < 0.5 {
675 self.decoration
676 } else {
677 other.decoration
678 },
679 decoration_color: match (self.decoration_color, other.decoration_color) {
680 (Some(a), Some(b)) => Some(a.lerp(&b, t)),
681 (Some(a), None) => {
682 if t < 0.5 {
683 Some(a)
684 } else {
685 None
686 }
687 }
688 (None, Some(b)) => {
689 if t >= 0.5 {
690 Some(b)
691 } else {
692 None
693 }
694 }
695 (None, None) => None,
696 },
697 decoration_style: if t < 0.5 {
698 self.decoration_style
699 } else {
700 other.decoration_style
701 },
702 decoration_thickness: match (self.decoration_thickness, other.decoration_thickness) {
703 (Some(a), Some(b)) => Some(mix(a, b)),
704 (Some(a), None) => Some(mix(a, 0.0)),
705 (None, Some(b)) => Some(mix(0.0, b)),
706 (None, None) => None,
707 },
708 debug_label: if t < 0.5 {
709 self.debug_label.clone()
710 } else {
711 other.debug_label.clone()
712 },
713 overflow: if t < 0.5 {
714 self.overflow
715 } else {
716 other.overflow
717 },
718 }
719 }
720}
721
722impl Default for TextStyle {
723 fn default() -> Self {
724 Self {
725 inherit: true,
726 has_explicit_color: false,
727 has_explicit_size: false,
728 has_explicit_weight: false,
729 has_explicit_style: false,
730 has_explicit_family: false,
731 family: FontFamily::SystemUi,
732 fallback_families: Arc::new([]),
733 size: WidgetDefaults::DEFAULT.text_size,
734 weight: FontWeight::NORMAL,
735 style: FontStyle::Normal,
736 color: Color::WHITE,
737 background_color: None,
738 line_height: None,
739 letter_spacing: 0.0,
740 word_spacing: None,
741 text_baseline: None,
742 leading_distribution: None,
743 locale: None,
744 foreground: None,
745 background: None,
746 shadows: Arc::new([]),
747 font_variations: None,
748 font_features: None,
749 decoration: None,
750 decoration_color: None,
751 decoration_style: None,
752 decoration_thickness: None,
753 debug_label: None,
754 overflow: None,
755 }
756 }
757}