1use std::{
2 hash::{Hash, Hasher},
3 iter, mem,
4 ops::Range,
5};
6
7use crate::{
8 AbsoluteLength, App, Background, BackgroundTag, BorderStyle, Bounds, ContentMask, Corners,
9 CornersRefinement, CursorStyle, DefiniteLength, DevicePixels, Edges, EdgesRefinement, Font,
10 FontFallbacks, FontFeatures, FontStyle, FontVariations, FontWeight, GridLocation, Hsla, Length,
11 Pixels, Point, PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun,
12 Window, black, phi, point, px, quad, rems, size,
13};
14use collections::HashSet;
15use refineable::Refineable;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18
19#[cfg(debug_assertions)]
23pub struct DebugBelow;
24
25#[cfg(debug_assertions)]
26impl crate::Global for DebugBelow {}
27
28pub enum ObjectFit {
30 Fill,
32 Contain,
34 Cover,
36 ScaleDown,
38 None,
40}
41
42impl ObjectFit {
43 pub fn get_bounds(
45 &self,
46 bounds: Bounds<Pixels>,
47 image_size: Size<DevicePixels>,
48 ) -> Bounds<Pixels> {
49 let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
50 let image_ratio = image_size.width / image_size.height;
51 let bounds_ratio = bounds.size.width / bounds.size.height;
52
53 match self {
54 ObjectFit::Fill => bounds,
55 ObjectFit::Contain => {
56 let new_size = if bounds_ratio > image_ratio {
57 size(
58 image_size.width * (bounds.size.height / image_size.height),
59 bounds.size.height,
60 )
61 } else {
62 size(
63 bounds.size.width,
64 image_size.height * (bounds.size.width / image_size.width),
65 )
66 };
67
68 Bounds {
69 origin: point(
70 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
71 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
72 ),
73 size: new_size,
74 }
75 }
76 ObjectFit::ScaleDown => {
77 if image_size.width > bounds.size.width || image_size.height > bounds.size.height {
79 let new_size = if bounds_ratio > image_ratio {
81 size(
82 image_size.width * (bounds.size.height / image_size.height),
83 bounds.size.height,
84 )
85 } else {
86 size(
87 bounds.size.width,
88 image_size.height * (bounds.size.width / image_size.width),
89 )
90 };
91
92 Bounds {
93 origin: point(
94 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
95 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
96 ),
97 size: new_size,
98 }
99 } else {
100 let original_size = size(image_size.width, image_size.height);
103 Bounds {
104 origin: point(
105 bounds.origin.x + (bounds.size.width - original_size.width) / 2.0,
106 bounds.origin.y + (bounds.size.height - original_size.height) / 2.0,
107 ),
108 size: original_size,
109 }
110 }
111 }
112 ObjectFit::Cover => {
113 let new_size = if bounds_ratio > image_ratio {
114 size(
115 bounds.size.width,
116 image_size.height * (bounds.size.width / image_size.width),
117 )
118 } else {
119 size(
120 image_size.width * (bounds.size.height / image_size.height),
121 bounds.size.height,
122 )
123 };
124
125 Bounds {
126 origin: point(
127 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
128 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
129 ),
130 size: new_size,
131 }
132 }
133 ObjectFit::None => Bounds {
134 origin: bounds.origin,
135 size: image_size,
136 },
137 }
138 }
139}
140
141#[derive(
143 Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, JsonSchema, Serialize, Deserialize,
144)]
145pub enum GridTemplateMinSize {
146 #[default]
148 Zero,
149 MinContent,
151 MaxContent,
153}
154
155#[derive(
157 Copy,
158 Clone,
159 Refineable,
160 PartialEq,
161 Eq,
162 PartialOrd,
163 Ord,
164 Debug,
165 Default,
166 JsonSchema,
167 Serialize,
168 Deserialize,
169)]
170pub struct GridTemplate {
171 pub repeat: u16,
173 pub min_size: GridTemplateMinSize,
175}
176
177#[derive(Clone, Refineable, Debug)]
179#[refineable(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
180pub struct Style {
181 pub display: Display,
183
184 pub visibility: Visibility,
186
187 #[refineable]
190 pub overflow: Point<Overflow>,
191 pub scrollbar_width: AbsoluteLength,
193 pub allow_concurrent_scroll: bool,
195 pub restrict_scroll_to_axis: bool,
221
222 pub position: Position,
225 #[refineable]
227 pub inset: Edges<Length>,
228
229 #[refineable]
232 pub size: Size<Length>,
233 #[refineable]
235 pub min_size: Size<Length>,
236 #[refineable]
238 pub max_size: Size<Length>,
239 pub aspect_ratio: Option<f32>,
241
242 #[refineable]
245 pub margin: Edges<Length>,
246 #[refineable]
248 pub padding: Edges<DefiniteLength>,
249 #[refineable]
251 pub border_widths: Edges<AbsoluteLength>,
252
253 pub align_items: Option<AlignItems>,
256 pub align_self: Option<AlignSelf>,
258 pub align_content: Option<AlignContent>,
260 pub justify_content: Option<JustifyContent>,
262 #[refineable]
264 pub gap: Size<DefiniteLength>,
265
266 pub flex_direction: FlexDirection,
269 pub flex_wrap: FlexWrap,
271 pub flex_basis: Length,
273 pub flex_grow: f32,
275 pub flex_shrink: f32,
277
278 pub background: Option<Fill>,
280
281 pub border_color: Option<Hsla>,
283
284 pub border_style: BorderStyle,
286
287 #[refineable]
289 pub corner_radii: Corners<AbsoluteLength>,
290
291 pub box_shadow: Vec<BoxShadow>,
293
294 #[refineable]
296 pub text: TextStyleRefinement,
297
298 pub mouse_cursor: Option<CursorStyle>,
300
301 pub opacity: Option<f32>,
303
304 pub grid_cols: Option<GridTemplate>,
307
308 pub grid_rows: Option<GridTemplate>,
311
312 pub grid_location: Option<GridLocation>,
314
315 #[cfg(debug_assertions)]
317 pub debug: bool,
318
319 #[cfg(debug_assertions)]
321 pub debug_below: bool,
322}
323
324impl Styled for StyleRefinement {
325 fn style(&mut self) -> &mut StyleRefinement {
326 self
327 }
328}
329
330impl StyleRefinement {
331 pub fn grid_location_mut(&mut self) -> &mut GridLocation {
333 self.grid_location.get_or_insert_default()
334 }
335}
336
337#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
339pub enum Visibility {
340 #[default]
342 Visible,
343 Hidden,
345}
346
347#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
349pub struct BoxShadow {
350 pub color: Hsla,
352 pub offset: Point<Pixels>,
354 pub blur_radius: Pixels,
356 pub spread_radius: Pixels,
358 pub inset: bool,
360}
361
362impl BoxShadow {
363 pub fn new(offset_x: Pixels, offset_y: Pixels, color: Hsla) -> Self {
367 Self {
368 color,
369 offset: point(offset_x, offset_y),
370 blur_radius: px(0.),
371 spread_radius: px(0.),
372 inset: false,
373 }
374 }
375
376 pub fn blur_radius(mut self, blur_radius: Pixels) -> Self {
378 self.blur_radius = blur_radius;
379 self
380 }
381
382 pub fn spread_radius(mut self, spread_radius: Pixels) -> Self {
384 self.spread_radius = spread_radius;
385 self
386 }
387
388 pub fn inset(mut self) -> Self {
390 self.inset = true;
391 self
392 }
393}
394
395#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
397pub enum WhiteSpace {
398 #[default]
400 Normal,
401 Nowrap,
403}
404
405#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
407pub enum TextOverflow {
408 Truncate(SharedString),
411 TruncateStart(SharedString),
415 TruncateMiddle(SharedString),
419}
420
421#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
423pub enum TextAlign {
424 #[default]
426 Left,
427
428 Center,
430
431 Right,
433}
434
435#[derive(Refineable, Clone, Debug, PartialEq)]
437#[refineable(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
438pub struct TextStyle {
439 pub color: Hsla,
441
442 pub font_family: SharedString,
444
445 pub font_features: FontFeatures,
447
448 pub font_variations: FontVariations,
450
451 pub font_fallbacks: Option<FontFallbacks>,
453
454 pub font_size: AbsoluteLength,
456
457 pub line_height: DefiniteLength,
459
460 pub font_weight: FontWeight,
462
463 pub font_style: FontStyle,
465
466 pub background_color: Option<Hsla>,
468
469 pub underline: Option<UnderlineStyle>,
471
472 pub strikethrough: Option<StrikethroughStyle>,
474
475 pub white_space: WhiteSpace,
477
478 pub text_overflow: Option<TextOverflow>,
480
481 pub text_align: TextAlign,
483
484 pub line_clamp: Option<usize>,
486}
487
488impl Default for TextStyle {
489 fn default() -> Self {
490 TextStyle {
491 color: black(),
492 font_family: ".SystemUIFont".into(),
494 font_features: FontFeatures::default(),
495 font_variations: FontVariations::default(),
496 font_fallbacks: None,
497 font_size: rems(1.).into(),
498 line_height: phi(),
499 font_weight: FontWeight::default(),
500 font_style: FontStyle::default(),
501 background_color: None,
502 underline: None,
503 strikethrough: None,
504 white_space: WhiteSpace::Normal,
505 text_overflow: None,
506 text_align: TextAlign::default(),
507 line_clamp: None,
508 }
509 }
510}
511
512impl TextStyle {
513 pub fn highlight(mut self, style: impl Into<HighlightStyle>) -> Self {
515 let style = style.into();
516 if let Some(weight) = style.font_weight {
517 self.font_weight = weight;
518 }
519 if let Some(style) = style.font_style {
520 self.font_style = style;
521 }
522
523 if let Some(color) = style.color {
524 self.color = self.color.blend(color);
525 }
526
527 if let Some(factor) = style.fade_out {
528 self.color.fade_out(factor);
529 }
530
531 if let Some(background_color) = style.background_color {
532 self.background_color = Some(background_color);
533 }
534
535 if let Some(underline) = style.underline {
536 self.underline = Some(underline);
537 }
538
539 if let Some(strikethrough) = style.strikethrough {
540 self.strikethrough = Some(strikethrough);
541 }
542
543 self
544 }
545
546 pub fn font(&self) -> Font {
548 Font {
549 family: self.font_family.clone(),
550 features: self.font_features.clone(),
551 variations: self.font_variations.clone(),
552 fallbacks: self.font_fallbacks.clone(),
553 weight: self.font_weight,
554 style: self.font_style,
555 }
556 }
557
558 pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels {
560 self.line_height.to_pixels(self.font_size, rem_size).round()
561 }
562
563 pub fn to_run(&self, len: usize) -> TextRun {
565 TextRun {
566 len,
567 font: Font {
568 family: self.font_family.clone(),
569 features: self.font_features.clone(),
570 variations: self.font_variations.clone(),
571 fallbacks: self.font_fallbacks.clone(),
572 weight: self.font_weight,
573 style: self.font_style,
574 },
575 color: self.color,
576 background_color: self.background_color,
577 underline: self.underline,
578 strikethrough: self.strikethrough,
579 }
580 }
581}
582
583#[derive(Copy, Clone, Debug, Default, PartialEq)]
586pub struct HighlightStyle {
587 pub color: Option<Hsla>,
589
590 pub font_weight: Option<FontWeight>,
592
593 pub font_style: Option<FontStyle>,
595
596 pub background_color: Option<Hsla>,
598
599 pub underline: Option<UnderlineStyle>,
601
602 pub strikethrough: Option<StrikethroughStyle>,
604
605 pub fade_out: Option<f32>,
607}
608
609impl Eq for HighlightStyle {}
610
611impl Hash for HighlightStyle {
612 fn hash<H: Hasher>(&self, state: &mut H) {
613 self.color.hash(state);
614 self.font_weight.hash(state);
615 self.font_style.hash(state);
616 self.background_color.hash(state);
617 self.underline.hash(state);
618 self.strikethrough.hash(state);
619 state.write_u32(u32::from_be_bytes(
620 self.fade_out.map(|f| f.to_be_bytes()).unwrap_or_default(),
621 ));
622 }
623}
624
625impl Style {
626 pub fn has_opaque_background(&self) -> bool {
628 self.background
629 .as_ref()
630 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
631 }
632
633 pub fn text_style(&self) -> Option<&TextStyleRefinement> {
635 if self.text.is_some() {
636 Some(&self.text)
637 } else {
638 None
639 }
640 }
641
642 pub fn overflow_mask(
645 &self,
646 bounds: Bounds<Pixels>,
647 rem_size: Pixels,
648 ) -> Option<ContentMask<Pixels>> {
649 match self.overflow {
650 Point {
651 x: Overflow::Visible,
652 y: Overflow::Visible,
653 } => None,
654 _ => {
655 let mut min = bounds.origin;
656 let mut max = bounds.bottom_right();
657
658 if self
659 .border_color
660 .is_some_and(|color| !color.is_transparent())
661 {
662 min.x += self.border_widths.left.to_pixels(rem_size);
663 max.x -= self.border_widths.right.to_pixels(rem_size);
664 min.y += self.border_widths.top.to_pixels(rem_size);
665 max.y -= self.border_widths.bottom.to_pixels(rem_size);
666 }
667
668 let bounds = match (
669 self.overflow.x == Overflow::Visible,
670 self.overflow.y == Overflow::Visible,
671 ) {
672 (true, true) => return None,
674 (true, false) => Bounds::from_corners(
676 point(bounds.origin.x, min.y),
677 point(bounds.bottom_right().x, max.y),
678 ),
679 (false, true) => Bounds::from_corners(
681 point(min.x, bounds.origin.y),
682 point(max.x, bounds.bottom_right().y),
683 ),
684 (false, false) => Bounds::from_corners(min, max),
686 };
687
688 Some(ContentMask { bounds })
689 }
690 }
691 }
692
693 pub fn paint(
695 &self,
696 bounds: Bounds<Pixels>,
697 window: &mut Window,
698 cx: &mut App,
699 continuation: impl FnOnce(&mut Window, &mut App),
700 ) {
701 #[cfg(debug_assertions)]
702 if self.debug_below {
703 cx.set_global(DebugBelow)
704 }
705
706 #[cfg(debug_assertions)]
707 if self.debug || cx.has_global::<DebugBelow>() {
708 window.paint_quad(crate::outline(bounds, crate::red(), BorderStyle::default()));
709 }
710
711 let rem_size = window.rem_size();
712 let corner_radii = self
713 .corner_radii
714 .to_pixels(rem_size)
715 .clamp_radii_for_quad_size(bounds.size);
716
717 window.paint_drop_shadows(bounds, corner_radii, &self.box_shadow);
718
719 let background_color = self.background.as_ref().and_then(Fill::color);
720 if background_color.is_some_and(|color| !color.is_transparent()) {
721 let mut border_color = match background_color {
722 Some(color) => match color.tag {
723 BackgroundTag::Solid
724 | BackgroundTag::PatternSlash
725 | BackgroundTag::Checkerboard => color.solid,
726
727 BackgroundTag::LinearGradient => color
728 .colors
729 .first()
730 .map(|stop| stop.color)
731 .unwrap_or_default(),
732 },
733 None => Hsla::default(),
734 };
735 border_color.a = 0.;
736 window.paint_quad(quad(
737 bounds,
738 corner_radii,
739 background_color.unwrap_or_default(),
740 Edges::default(),
741 border_color,
742 self.border_style,
743 ));
744 }
745
746 window.paint_inset_shadows(bounds, corner_radii, &self.box_shadow);
747
748 continuation(window, cx);
749
750 if self.is_border_visible() {
751 let border_widths = self.border_widths.to_pixels(rem_size);
752 let mut background = self.border_color.unwrap_or_default();
753 background.a = 0.;
754 window.paint_quad(quad(
755 bounds,
756 corner_radii,
757 background,
758 border_widths,
759 self.border_color.unwrap_or_default(),
760 self.border_style,
761 ));
762 }
763
764 #[cfg(debug_assertions)]
765 if self.debug_below {
766 cx.remove_global::<DebugBelow>();
767 }
768 }
769
770 fn is_border_visible(&self) -> bool {
771 self.border_color
772 .is_some_and(|color| !color.is_transparent())
773 && self.border_widths.any(|length| !length.is_zero())
774 }
775}
776
777impl Default for Style {
778 fn default() -> Self {
779 Style {
780 display: Display::Block,
781 visibility: Visibility::Visible,
782 overflow: Point {
783 x: Overflow::Visible,
784 y: Overflow::Visible,
785 },
786 allow_concurrent_scroll: false,
787 restrict_scroll_to_axis: false,
788 scrollbar_width: AbsoluteLength::default(),
789 position: Position::Relative,
790 inset: Edges::auto(),
791 margin: Edges::<Length>::zero(),
792 padding: Edges::<DefiniteLength>::zero(),
793 border_widths: Edges::<AbsoluteLength>::zero(),
794 size: Size::auto(),
795 min_size: Size::auto(),
796 max_size: Size::auto(),
797 aspect_ratio: None,
798 gap: Size::default(),
799 align_items: None,
801 align_self: None,
802 align_content: None,
803 justify_content: None,
804 flex_direction: FlexDirection::Row,
806 flex_wrap: FlexWrap::NoWrap,
807 flex_grow: 0.0,
808 flex_shrink: 1.0,
809 flex_basis: Length::Auto,
810 background: None,
811 border_color: None,
812 border_style: BorderStyle::default(),
813 corner_radii: Corners::default(),
814 box_shadow: Default::default(),
815 text: TextStyleRefinement::default(),
816 mouse_cursor: None,
817 opacity: None,
818 grid_rows: None,
819 grid_cols: None,
820 grid_location: None,
821
822 #[cfg(debug_assertions)]
823 debug: false,
824 #[cfg(debug_assertions)]
825 debug_below: false,
826 }
827 }
828}
829
830#[derive(
832 Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema,
833)]
834pub struct UnderlineStyle {
835 pub thickness: Pixels,
837
838 pub color: Option<Hsla>,
840
841 pub wavy: bool,
843}
844
845#[derive(
847 Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema,
848)]
849pub struct StrikethroughStyle {
850 pub thickness: Pixels,
852
853 pub color: Option<Hsla>,
855}
856
857#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
859pub enum Fill {
860 Color(Background),
862}
863
864impl Fill {
865 pub fn color(&self) -> Option<Background> {
869 match self {
870 Fill::Color(color) => Some(*color),
871 }
872 }
873}
874
875impl Default for Fill {
876 fn default() -> Self {
877 Self::Color(Background::default())
878 }
879}
880
881impl From<Hsla> for Fill {
882 fn from(color: Hsla) -> Self {
883 Self::Color(color.into())
884 }
885}
886
887impl From<Rgba> for Fill {
888 fn from(color: Rgba) -> Self {
889 Self::Color(color.into())
890 }
891}
892
893impl From<Background> for Fill {
894 fn from(background: Background) -> Self {
895 Self::Color(background)
896 }
897}
898
899impl From<TextStyle> for HighlightStyle {
900 fn from(other: TextStyle) -> Self {
901 Self::from(&other)
902 }
903}
904
905impl From<&TextStyle> for HighlightStyle {
906 fn from(other: &TextStyle) -> Self {
907 Self {
908 color: Some(other.color),
909 font_weight: Some(other.font_weight),
910 font_style: Some(other.font_style),
911 background_color: other.background_color,
912 underline: other.underline,
913 strikethrough: other.strikethrough,
914 fade_out: None,
915 }
916 }
917}
918
919impl HighlightStyle {
920 pub fn color(color: Hsla) -> Self {
922 Self {
923 color: Some(color),
924 ..Default::default()
925 }
926 }
927 #[must_use]
930 pub fn highlight(self, other: HighlightStyle) -> Self {
931 Self {
932 color: other
933 .color
934 .map(|other_color| {
935 if let Some(color) = self.color {
936 color.blend(other_color)
937 } else {
938 other_color
939 }
940 })
941 .or(self.color),
942 font_weight: other.font_weight.or(self.font_weight),
943 font_style: other.font_style.or(self.font_style),
944 background_color: other.background_color.or(self.background_color),
945 underline: other.underline.or(self.underline),
946 strikethrough: other.strikethrough.or(self.strikethrough),
947 fade_out: other
948 .fade_out
949 .map(|source_fade| {
950 self.fade_out
951 .map(|dest_fade| (dest_fade * (1. + source_fade)).clamp(0., 1.))
952 .unwrap_or(source_fade)
953 })
954 .or(self.fade_out),
955 }
956 }
957}
958
959impl From<Hsla> for HighlightStyle {
960 fn from(color: Hsla) -> Self {
961 Self {
962 color: Some(color),
963 ..Default::default()
964 }
965 }
966}
967
968impl From<FontWeight> for HighlightStyle {
969 fn from(font_weight: FontWeight) -> Self {
970 Self {
971 font_weight: Some(font_weight),
972 ..Default::default()
973 }
974 }
975}
976
977impl From<FontStyle> for HighlightStyle {
978 fn from(font_style: FontStyle) -> Self {
979 Self {
980 font_style: Some(font_style),
981 ..Default::default()
982 }
983 }
984}
985
986impl From<Rgba> for HighlightStyle {
987 fn from(color: Rgba) -> Self {
988 Self {
989 color: Some(color.into()),
990 ..Default::default()
991 }
992 }
993}
994
995pub fn combine_highlights(
997 a: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
998 b: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
999) -> impl Iterator<Item = (Range<usize>, HighlightStyle)> {
1000 let mut endpoints = Vec::new();
1001 let mut highlights = Vec::new();
1002 for (range, highlight) in a.into_iter().chain(b) {
1003 if !range.is_empty() {
1004 let highlight_id = highlights.len();
1005 endpoints.push((range.start, highlight_id, true));
1006 endpoints.push((range.end, highlight_id, false));
1007 highlights.push(highlight);
1008 }
1009 }
1010 endpoints.sort_unstable_by_key(|(position, _, _)| *position);
1011 let mut endpoints = endpoints.into_iter().peekable();
1012
1013 let mut active_styles = HashSet::default();
1014 let mut ix = 0;
1015 iter::from_fn(move || {
1016 while let Some((endpoint_ix, highlight_id, is_start)) = endpoints.peek() {
1017 let prev_index = mem::replace(&mut ix, *endpoint_ix);
1018 if ix > prev_index && !active_styles.is_empty() {
1019 let current_style = active_styles
1020 .iter()
1021 .fold(HighlightStyle::default(), |acc, highlight_id| {
1022 acc.highlight(highlights[*highlight_id])
1023 });
1024 return Some((prev_index..ix, current_style));
1025 }
1026
1027 if *is_start {
1028 active_styles.insert(*highlight_id);
1029 } else {
1030 active_styles.remove(highlight_id);
1031 }
1032 endpoints.next();
1033 }
1034 None
1035 })
1036}
1037
1038#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
1044pub enum AlignItems {
1046 Start,
1048 End,
1050 FlexStart,
1055 FlexEnd,
1060 Center,
1062 Baseline,
1064 Stretch,
1066}
1067pub type JustifyItems = AlignItems;
1073pub type AlignSelf = AlignItems;
1080pub type JustifySelf = AlignItems;
1087
1088#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
1094pub enum AlignContent {
1096 Start,
1098 End,
1100 FlexStart,
1105 FlexEnd,
1110 Center,
1112 Stretch,
1114 SpaceBetween,
1117 SpaceEvenly,
1120 SpaceAround,
1123}
1124
1125pub type JustifyContent = AlignContent;
1131
1132#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1136pub enum Display {
1138 Block,
1140 #[default]
1142 Flex,
1143 Grid,
1145 None,
1147}
1148
1149#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1155pub enum FlexWrap {
1157 #[default]
1159 NoWrap,
1160 Wrap,
1162 WrapReverse,
1164}
1165
1166#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1178pub enum FlexDirection {
1180 #[default]
1184 Row,
1185 Column,
1189 RowReverse,
1193 ColumnReverse,
1197}
1198
1199#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1213pub enum Overflow {
1215 #[default]
1218 Visible,
1219 Clip,
1222 Hidden,
1225 Scroll,
1229}
1230
1231#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1241pub enum Position {
1243 #[default]
1246 Relative,
1247 Absolute,
1253}
1254
1255impl From<AlignItems> for taffy::style::AlignItems {
1256 fn from(value: AlignItems) -> Self {
1257 match value {
1258 AlignItems::Start => Self::START,
1259 AlignItems::End => Self::END,
1260 AlignItems::FlexStart => Self::FLEX_START,
1261 AlignItems::FlexEnd => Self::FLEX_END,
1262 AlignItems::Center => Self::CENTER,
1263 AlignItems::Baseline => Self::BASELINE,
1264 AlignItems::Stretch => Self::STRETCH,
1265 }
1266 }
1267}
1268
1269impl From<AlignContent> for taffy::style::AlignContent {
1270 fn from(value: AlignContent) -> Self {
1271 match value {
1272 AlignContent::Start => Self::START,
1273 AlignContent::End => Self::END,
1274 AlignContent::FlexStart => Self::FLEX_START,
1275 AlignContent::FlexEnd => Self::FLEX_END,
1276 AlignContent::Center => Self::CENTER,
1277 AlignContent::Stretch => Self::STRETCH,
1278 AlignContent::SpaceBetween => Self::SPACE_BETWEEN,
1279 AlignContent::SpaceEvenly => Self::SPACE_EVENLY,
1280 AlignContent::SpaceAround => Self::SPACE_AROUND,
1281 }
1282 }
1283}
1284
1285impl From<Display> for taffy::style::Display {
1286 fn from(value: Display) -> Self {
1287 match value {
1288 Display::Block => Self::Block,
1289 Display::Flex => Self::Flex,
1290 Display::Grid => Self::Grid,
1291 Display::None => Self::None,
1292 }
1293 }
1294}
1295
1296impl From<FlexWrap> for taffy::style::FlexWrap {
1297 fn from(value: FlexWrap) -> Self {
1298 match value {
1299 FlexWrap::NoWrap => Self::NoWrap,
1300 FlexWrap::Wrap => Self::Wrap,
1301 FlexWrap::WrapReverse => Self::WrapReverse,
1302 }
1303 }
1304}
1305
1306impl From<FlexDirection> for taffy::style::FlexDirection {
1307 fn from(value: FlexDirection) -> Self {
1308 match value {
1309 FlexDirection::Row => Self::Row,
1310 FlexDirection::Column => Self::Column,
1311 FlexDirection::RowReverse => Self::RowReverse,
1312 FlexDirection::ColumnReverse => Self::ColumnReverse,
1313 }
1314 }
1315}
1316
1317impl From<Overflow> for taffy::style::Overflow {
1318 fn from(value: Overflow) -> Self {
1319 match value {
1320 Overflow::Visible => Self::Visible,
1321 Overflow::Clip => Self::Clip,
1322 Overflow::Hidden => Self::Hidden,
1323 Overflow::Scroll => Self::Scroll,
1324 }
1325 }
1326}
1327
1328impl From<Position> for taffy::style::Position {
1329 fn from(value: Position) -> Self {
1330 match value {
1331 Position::Relative => Self::Relative,
1332 Position::Absolute => Self::Absolute,
1333 }
1334 }
1335}
1336
1337#[cfg(test)]
1338mod tests {
1339 use crate::{blue, green, px, red, yellow};
1340
1341 use super::*;
1342
1343 use util_macros::perf;
1344
1345 #[test]
1346 fn explicit_axes_survive_text_style_runs_and_refinement_serialization() {
1347 let variations = crate::FontVariations::new([("slnt".parse().unwrap(), -9.125)]).unwrap();
1348 let style = TextStyle {
1349 font_variations: variations.clone(),
1350 ..Default::default()
1351 };
1352 assert_eq!(style.font().variations, variations);
1353 assert_eq!(style.to_run(3).font.variations, variations);
1354 let refinement = TextStyleRefinement {
1355 font_variations: Some(variations.clone()),
1356 ..Default::default()
1357 };
1358 let json = serde_json::to_string(&refinement).unwrap();
1359 let restored: TextStyleRefinement = serde_json::from_str(&json).unwrap();
1360 assert_eq!(restored.font_variations, Some(variations));
1361 assert!(TextStyle::default().font_variations.settings().is_empty());
1362 }
1363
1364 #[test]
1367 fn overflow_mask_insets_only_hidden_axes_for_native_borders() {
1368 let bounds = Bounds::new(point(px(10.), px(20.)), size(px(100.), px(80.)));
1369 let mut style = Style {
1370 border_color: Some(red()),
1371 border_widths: Edges {
1372 left: px(2.).into(),
1373 right: px(4.).into(),
1374 top: px(6.).into(),
1375 bottom: px(8.).into(),
1376 },
1377 ..Default::default()
1378 };
1379 style.overflow = point(Overflow::Visible, Overflow::Hidden);
1380 assert_eq!(
1381 style.overflow_mask(bounds, px(16.)).unwrap().bounds,
1382 Bounds::new(point(px(10.), px(26.)), size(px(100.), px(66.)))
1383 );
1384 style.overflow = point(Overflow::Hidden, Overflow::Visible);
1385 assert_eq!(
1386 style.overflow_mask(bounds, px(16.)).unwrap().bounds,
1387 Bounds::new(point(px(12.), px(20.)), size(px(94.), px(80.)))
1388 );
1389 style.border_color = Some(crate::transparent_black());
1390 assert_eq!(style.overflow_mask(bounds, px(16.)).unwrap().bounds, bounds);
1391 }
1392
1393 #[perf]
1394 fn test_basic_highlight_style_combination() {
1395 let style_a = HighlightStyle::default();
1396 let style_b = HighlightStyle::default();
1397 let style_a = style_a.highlight(style_b);
1398 assert_eq!(
1399 style_a,
1400 HighlightStyle::default(),
1401 "Combining empty styles should not produce a non-empty style."
1402 );
1403
1404 let style_b = HighlightStyle {
1405 color: Some(red()),
1406 strikethrough: Some(StrikethroughStyle {
1407 thickness: px(2.),
1408 color: Some(blue()),
1409 }),
1410 fade_out: Some(0.),
1411 font_style: Some(FontStyle::Italic),
1412 font_weight: Some(FontWeight(300.)),
1413 background_color: Some(yellow()),
1414 underline: Some(UnderlineStyle {
1415 thickness: px(2.),
1416 color: Some(red()),
1417 wavy: true,
1418 }),
1419 };
1420 let expected_style = style_b;
1421
1422 let style_a = style_a.highlight(style_b);
1423 assert_eq!(
1424 style_a, expected_style,
1425 "Blending an empty style with another style should return the other style"
1426 );
1427
1428 let style_b = style_b.highlight(Default::default());
1429 assert_eq!(
1430 style_b, expected_style,
1431 "Blending a style with an empty style should not change the style."
1432 );
1433
1434 let style_c = expected_style;
1435
1436 let style_d = HighlightStyle {
1437 color: Some(blue().alpha(0.7)),
1438 strikethrough: Some(StrikethroughStyle {
1439 thickness: px(4.),
1440 color: Some(crate::red()),
1441 }),
1442 fade_out: Some(0.),
1443 font_style: Some(FontStyle::Oblique),
1444 font_weight: Some(FontWeight(800.)),
1445 background_color: Some(green()),
1446 underline: Some(UnderlineStyle {
1447 thickness: px(4.),
1448 color: None,
1449 wavy: false,
1450 }),
1451 };
1452
1453 let expected_style = HighlightStyle {
1454 color: Some(red().blend(blue().alpha(0.7))),
1455 strikethrough: Some(StrikethroughStyle {
1456 thickness: px(4.),
1457 color: Some(red()),
1458 }),
1459 fade_out: Some(0.),
1461 font_style: Some(FontStyle::Oblique),
1462 font_weight: Some(FontWeight(800.)),
1463 background_color: Some(green()),
1464 underline: Some(UnderlineStyle {
1465 thickness: px(4.),
1466 color: None,
1467 wavy: false,
1468 }),
1469 };
1470
1471 let style_c = style_c.highlight(style_d);
1472 assert_eq!(
1473 style_c, expected_style,
1474 "Blending styles should blend properties where possible and override all others"
1475 );
1476 }
1477
1478 #[perf]
1479 fn test_combine_highlights() {
1480 assert_eq!(
1481 combine_highlights(
1482 [
1483 (0..5, green().into()),
1484 (4..10, FontWeight::BOLD.into()),
1485 (15..20, yellow().into()),
1486 ],
1487 [
1488 (2..6, FontStyle::Italic.into()),
1489 (1..3, blue().into()),
1490 (21..23, red().into()),
1491 ]
1492 )
1493 .collect::<Vec<_>>(),
1494 [
1495 (
1496 0..1,
1497 HighlightStyle {
1498 color: Some(green()),
1499 ..Default::default()
1500 }
1501 ),
1502 (
1503 1..2,
1504 HighlightStyle {
1505 color: Some(blue()),
1506 ..Default::default()
1507 }
1508 ),
1509 (
1510 2..3,
1511 HighlightStyle {
1512 color: Some(blue()),
1513 font_style: Some(FontStyle::Italic),
1514 ..Default::default()
1515 }
1516 ),
1517 (
1518 3..4,
1519 HighlightStyle {
1520 color: Some(green()),
1521 font_style: Some(FontStyle::Italic),
1522 ..Default::default()
1523 }
1524 ),
1525 (
1526 4..5,
1527 HighlightStyle {
1528 color: Some(green()),
1529 font_weight: Some(FontWeight::BOLD),
1530 font_style: Some(FontStyle::Italic),
1531 ..Default::default()
1532 }
1533 ),
1534 (
1535 5..6,
1536 HighlightStyle {
1537 font_weight: Some(FontWeight::BOLD),
1538 font_style: Some(FontStyle::Italic),
1539 ..Default::default()
1540 }
1541 ),
1542 (
1543 6..10,
1544 HighlightStyle {
1545 font_weight: Some(FontWeight::BOLD),
1546 ..Default::default()
1547 }
1548 ),
1549 (
1550 15..20,
1551 HighlightStyle {
1552 color: Some(yellow()),
1553 ..Default::default()
1554 }
1555 ),
1556 (
1557 21..23,
1558 HighlightStyle {
1559 color: Some(red()),
1560 ..Default::default()
1561 }
1562 )
1563 ]
1564 );
1565 }
1566
1567 #[perf]
1568 fn test_text_style_refinement() {
1569 let mut style = Style::default();
1570 style.refine(&StyleRefinement::default().text_size(px(20.0)));
1571 style.refine(&StyleRefinement::default().font_weight(FontWeight::SEMIBOLD));
1572
1573 assert_eq!(
1574 Some(AbsoluteLength::from(px(20.0))),
1575 style.text_style().unwrap().font_size
1576 );
1577
1578 assert_eq!(
1579 Some(FontWeight::SEMIBOLD),
1580 style.text_style().unwrap().font_weight
1581 );
1582 }
1583}