1use crate::{
2 AbsoluteLength, App, Background, BorderStyle, Bounds, ColorExt, ContentMask, Corners,
3 CornersRefinement, CursorStyle, DefiniteLength, DevicePixels, Edges, EdgesRefinement, Font,
4 FontFallbacks, FontFeatures, FontStyle, FontWeight, FontWidth, GridLocation, Length, PaintQuad,
5 Pixels, Point, PointRefinement, ScaledPixels, SharedString, Size, SizeRefinement, Styled,
6 TextRun, Window, black, phi, point, px, rems, size,
7};
8use collections::HashSet;
9use palette::{Hsla, IntoColor, rgb::Rgba};
10use refineable::Refineable;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use std::{iter, mem, ops::Range};
14
15#[cfg(debug_assertions)]
19pub struct DebugBelow;
20
21#[cfg(debug_assertions)]
22impl crate::Global for DebugBelow {}
23
24pub enum ObjectFit {
26 Fill,
28 Contain,
30 Cover,
32 ScaleDown,
34 None,
36}
37
38impl ObjectFit {
39 pub fn get_bounds(
41 &self,
42 bounds: Bounds<Pixels>,
43 image_size: Size<DevicePixels>,
44 ) -> Bounds<Pixels> {
45 let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension)));
46 let image_ratio = image_size.width / image_size.height;
47 let bounds_ratio = bounds.size.width / bounds.size.height;
48
49 match self {
50 ObjectFit::Fill => bounds,
51 ObjectFit::Contain => {
52 let new_size = if bounds_ratio > image_ratio {
53 size(
54 image_size.width * (bounds.size.height / image_size.height),
55 bounds.size.height,
56 )
57 } else {
58 size(
59 bounds.size.width,
60 image_size.height * (bounds.size.width / image_size.width),
61 )
62 };
63
64 Bounds {
65 origin: point(
66 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
67 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
68 ),
69 size: new_size,
70 }
71 }
72 ObjectFit::ScaleDown => {
73 if image_size.width > bounds.size.width || image_size.height > bounds.size.height {
75 let new_size = if bounds_ratio > image_ratio {
77 size(
78 image_size.width * (bounds.size.height / image_size.height),
79 bounds.size.height,
80 )
81 } else {
82 size(
83 bounds.size.width,
84 image_size.height * (bounds.size.width / image_size.width),
85 )
86 };
87
88 Bounds {
89 origin: point(
90 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
91 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
92 ),
93 size: new_size,
94 }
95 } else {
96 let original_size = size(image_size.width, image_size.height);
99 Bounds {
100 origin: point(
101 bounds.origin.x + (bounds.size.width - original_size.width) / 2.0,
102 bounds.origin.y + (bounds.size.height - original_size.height) / 2.0,
103 ),
104 size: original_size,
105 }
106 }
107 }
108 ObjectFit::Cover => {
109 let new_size = if bounds_ratio > image_ratio {
110 size(
111 bounds.size.width,
112 image_size.height * (bounds.size.width / image_size.width),
113 )
114 } else {
115 size(
116 image_size.width * (bounds.size.height / image_size.height),
117 bounds.size.height,
118 )
119 };
120
121 Bounds {
122 origin: point(
123 bounds.origin.x + (bounds.size.width - new_size.width) / 2.0,
124 bounds.origin.y + (bounds.size.height - new_size.height) / 2.0,
125 ),
126 size: new_size,
127 }
128 }
129 ObjectFit::None => Bounds {
130 origin: bounds.origin,
131 size: image_size,
132 },
133 }
134 }
135}
136
137#[derive(
139 Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, JsonSchema, Serialize, Deserialize,
140)]
141pub enum GridTemplateMinSize {
142 #[default]
144 Zero,
145 MinContent,
147 MaxContent,
149}
150
151#[derive(
153 Copy,
154 Clone,
155 Refineable,
156 PartialEq,
157 Eq,
158 PartialOrd,
159 Ord,
160 Debug,
161 Default,
162 JsonSchema,
163 Serialize,
164 Deserialize,
165)]
166pub struct GridTemplate {
167 pub repeat: u16,
169 pub min_size: GridTemplateMinSize,
171}
172
173#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
175pub enum RingColor {
176 #[default]
178 CurrentColor,
179 Color(Background),
181}
182
183impl RingColor {
184 fn resolve(self, current_color: Hsla) -> Background {
185 match self {
186 Self::CurrentColor => current_color.into(),
187 Self::Color(color) => color,
188 }
189 }
190}
191
192#[derive(Refineable, Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
194#[refineable(Debug, PartialEq, Serialize, Deserialize)]
195pub struct RingStyle {
196 pub width: Pixels,
198 pub color: RingColor,
200}
201
202impl RingStyle {
203 fn outer_quad(
204 self,
205 bounds: Bounds<Pixels>,
206 corner_radii: Corners<Pixels>,
207 current_color: Hsla,
208 ) -> Option<PaintQuad> {
209 (self.width > Pixels::ZERO).then(|| {
210 PaintQuad::new(bounds.dilate(self.width))
213 .corner_radii(corner_radii.map(|radius| *radius + self.width))
214 .border_widths(Edges::all(self.width))
215 .border_color(self.color.resolve(current_color))
216 })
217 }
218
219 fn inset_shadow(self, current_color: Hsla) -> Option<BoxShadow> {
220 (self.width > Pixels::ZERO).then(|| {
221 BoxShadow::new(px(0.), px(0.), self.color.resolve(current_color))
222 .spread_radius(self.width)
223 .inset()
224 })
225 }
226}
227
228#[derive(Clone, Refineable, Debug)]
248#[refineable(Debug, PartialEq, Serialize, Deserialize)]
249pub struct Style {
250 pub display: Display,
252
253 pub visibility: Visibility,
255
256 pub direction: LayoutDirection,
258
259 pub unicode_bidi: UnicodeBidi,
261
262 #[doc(hidden)]
264 pub unicode_bidi_explicit: bool,
265
266 #[refineable]
269 pub overflow: Point<Overflow>,
270 #[refineable]
272 pub overflow_fade: Edges<AbsoluteLength>,
273 pub scrollbar_width: AbsoluteLength,
275 pub allow_concurrent_scroll: bool,
277 pub restrict_scroll_to_axis: bool,
300
301 pub position: Position,
304 #[refineable]
306 pub inset: Edges<Length>,
307
308 #[refineable]
311 pub size: Size<Length>,
312 #[refineable]
314 pub min_size: Size<Length>,
315 #[refineable]
317 pub max_size: Size<Length>,
318 pub aspect_ratio: Option<f32>,
320
321 #[refineable]
324 pub margin: Edges<Length>,
325 #[refineable]
327 pub padding: Edges<DefiniteLength>,
328 #[refineable]
330 pub border_widths: Edges<AbsoluteLength>,
331
332 pub align_items: Option<AlignItems>,
335 pub align_self: Option<AlignSelf>,
337 pub vertical_align: VerticalAlign,
339 pub align_content: Option<AlignContent>,
341 pub justify_content: Option<JustifyContent>,
343 #[refineable]
345 pub gap: Size<DefiniteLength>,
346
347 pub flex_direction: FlexDirection,
350 pub flex_wrap: FlexWrap,
352 pub flex_basis: Length,
354 pub flex_grow: f32,
356 pub flex_shrink: f32,
358
359 pub background: Option<Fill>,
361
362 pub border_color: Option<Background>,
364
365 pub border_style: BorderStyle,
367
368 pub border_dashed_length: f32,
370
371 pub border_dashed_gap: f32,
373
374 #[refineable]
376 pub corner_radii: Corners<AbsoluteLength>,
377
378 pub corner_smoothing: Option<f32>,
381
382 pub box_shadow: Vec<BoxShadow>,
384
385 #[refineable]
387 pub ring: RingStyle,
388
389 #[refineable]
391 pub inset_ring: RingStyle,
392
393 pub filter: Vec<Filter>,
395
396 pub backdrop_filter: Vec<Filter>,
398
399 #[refineable]
401 pub text: TextStyleRefinement,
402
403 pub mouse_cursor: Option<CursorStyle>,
405
406 pub opacity: Option<f32>,
408
409 pub grid_cols: Option<GridTemplate>,
412
413 pub grid_rows: Option<GridTemplate>,
416
417 pub grid_location: Option<GridLocation>,
419
420 #[cfg(debug_assertions)]
422 pub debug: bool,
423
424 #[cfg(debug_assertions)]
426 pub debug_below: bool,
427}
428
429impl Styled for StyleRefinement {
430 fn style(&mut self) -> &mut StyleRefinement {
431 self
432 }
433}
434
435impl StyleRefinement {
436 pub fn grid_location_mut(&mut self) -> &mut GridLocation {
438 self.grid_location.get_or_insert_default()
439 }
440}
441
442#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
444pub enum Visibility {
445 #[default]
447 Visible,
448 Hidden,
450}
451
452#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema)]
454pub enum LayoutDirection {
455 #[default]
457 Inherit,
458 LeftToRight,
460 RightToLeft,
462 Auto,
464}
465
466#[doc(hidden)]
468#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
469pub enum ResolvedDirection {
470 #[default]
472 LeftToRight,
473 RightToLeft,
475}
476
477impl ResolvedDirection {
478 pub fn is_rtl(self) -> bool {
480 self == Self::RightToLeft
481 }
482
483 pub(crate) fn from_first_strong(text: &str) -> Option<Self> {
484 use unicode_bidi::BidiClass;
485
486 text.chars()
487 .find_map(|character| match unicode_bidi::bidi_class(character) {
488 BidiClass::L => Some(Self::LeftToRight),
489 BidiClass::R | BidiClass::AL => Some(Self::RightToLeft),
490 _ => None,
491 })
492 }
493}
494
495#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, JsonSchema)]
497pub enum UnicodeBidi {
498 #[default]
500 Normal,
501 Embed,
503 Isolate,
505 BidiOverride,
507 IsolateOverride,
509 Plaintext,
511}
512
513#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, derive_setters::Setters)]
515#[setters(generate = false)]
516pub struct BoxShadow {
517 pub color: Background,
519 pub offset: Point<Pixels>,
521 #[setters(generate)]
523 pub blur_radius: Pixels,
524 #[setters(generate)]
526 pub spread_radius: Pixels,
527 #[setters(generate, bool)]
529 pub inset: bool,
530}
531
532impl BoxShadow {
533 pub fn new(offset_x: Pixels, offset_y: Pixels, color: impl Into<Background>) -> Self {
537 Self {
538 color: color.into(),
539 offset: point(offset_x, offset_y),
540 blur_radius: px(0.),
541 spread_radius: px(0.),
542 inset: false,
543 }
544 }
545}
546
547#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
551pub enum Filter {
552 Blur(Pixels),
554}
555
556impl Filter {
557 pub fn is_identity(&self) -> bool {
563 match self {
564 Filter::Blur(radius) => *radius <= Pixels::ZERO,
565 }
566 }
567
568 pub fn scale(&self, factor: f32) -> ScaledFilter {
571 match self {
572 Filter::Blur(radius) => ScaledFilter::Blur(radius.scale(factor)),
573 }
574 }
575}
576
577#[derive(Clone, Copy, Debug, PartialEq)]
584pub enum ScaledFilter {
585 Blur(ScaledPixels),
587}
588
589#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
591pub enum WhiteSpace {
592 #[default]
594 Normal,
595 Nowrap,
597}
598
599#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
601pub enum TextOverflow {
602 Truncate(SharedString),
605 TruncateStart(SharedString),
609 TruncateMiddle(SharedString),
613}
614
615#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
617pub enum TextAlign {
618 #[default]
620 Start,
621
622 End,
624
625 Left,
627
628 Center,
630
631 Right,
633}
634
635#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
649pub enum TextTransform {
650 #[default]
652 None,
653 Uppercase,
655 Lowercase,
657 Capitalize,
660}
661
662#[derive(Refineable, Clone, Debug, PartialEq)]
687#[refineable(Debug, PartialEq, Serialize, Deserialize)]
688pub struct TextStyle {
689 pub color: Hsla,
691
692 pub font_family: SharedString,
694
695 pub font_features: FontFeatures,
697
698 pub font_fallbacks: Option<FontFallbacks>,
700
701 pub font_size: AbsoluteLength,
703
704 pub line_height: DefiniteLength,
706
707 pub font_weight: FontWeight,
709
710 pub font_width: FontWidth,
712
713 pub font_style: FontStyle,
715
716 pub background_color: Option<Hsla>,
718
719 pub underline: Option<UnderlineStyle>,
721
722 pub strikethrough: Option<StrikethroughStyle>,
724
725 pub white_space: WhiteSpace,
727
728 pub text_overflow: Option<TextOverflow>,
730
731 pub text_align: TextAlign,
733
734 pub line_clamp: Option<usize>,
736
737 pub letter_spacing: Option<Pixels>,
741
742 pub text_transform: Option<TextTransform>,
744}
745
746impl Default for TextStyle {
747 fn default() -> Self {
748 TextStyle {
749 color: black(),
750 font_family: ".SystemUIFont".into(),
752 font_features: FontFeatures::default(),
753 font_fallbacks: None,
754 font_size: rems(1.).into(),
755 line_height: phi().into(),
756 font_weight: FontWeight::default(),
757 font_width: FontWidth::default(),
758 font_style: FontStyle::default(),
759 background_color: None,
760 underline: None,
761 strikethrough: None,
762 white_space: WhiteSpace::Normal,
763 text_overflow: None,
764 text_align: TextAlign::default(),
765 line_clamp: None,
766 letter_spacing: None,
767 text_transform: None,
768 }
769 }
770}
771
772impl TextStyle {
773 pub fn highlight(mut self, style: impl Into<HighlightStyle>) -> Self {
775 let style = style.into();
776 if let Some(weight) = style.font_weight {
777 self.font_weight = weight;
778 }
779
780 if let Some(width) = style.font_width {
781 self.font_width = width;
782 }
783
784 if let Some(style) = style.font_style {
785 self.font_style = style;
786 }
787
788 if let Some(color) = style.color {
789 self.color = self.color.blend(&color);
790 }
791
792 if let Some(factor) = style.fade_out {
793 self.color.fade_out(factor);
794 }
795
796 if let Some(background_color) = style.background_color {
797 self.background_color = Some(background_color);
798 }
799
800 if let Some(underline) = style.underline {
801 self.underline = Some(underline);
802 }
803
804 if let Some(strikethrough) = style.strikethrough {
805 self.strikethrough = Some(strikethrough);
806 }
807
808 self
809 }
810
811 pub fn font(&self) -> Font {
813 Font {
814 family: self.font_family.clone(),
815 features: self.font_features.clone(),
816 fallbacks: self.font_fallbacks.clone(),
817 weight: self.font_weight,
818 width: self.font_width,
819 style: self.font_style,
820 }
821 }
822
823 pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels {
825 self.line_height.to_pixels(self.font_size, rem_size).round()
826 }
827
828 pub fn to_run(&self, len: usize) -> TextRun {
830 TextRun {
831 len,
832 font: Font {
833 family: self.font_family.clone(),
834 features: self.font_features.clone(),
835 fallbacks: self.font_fallbacks.clone(),
836 weight: self.font_weight,
837 width: self.font_width,
838 style: self.font_style,
839 },
840 color: self.color,
841 background_color: self.background_color,
842 underline: self.underline,
843 strikethrough: self.strikethrough,
844 letter_spacing: self.letter_spacing,
845 }
846 }
847}
848
849#[derive(Copy, Clone, Debug, Default, PartialEq)]
857pub struct HighlightStyle {
858 pub color: Option<Hsla>,
860
861 pub font_weight: Option<FontWeight>,
863
864 pub font_width: Option<FontWidth>,
866
867 pub font_style: Option<FontStyle>,
869
870 pub background_color: Option<Hsla>,
872
873 pub underline: Option<UnderlineStyle>,
875
876 pub strikethrough: Option<StrikethroughStyle>,
878
879 pub fade_out: Option<f32>,
881}
882
883impl Style {
884 #[doc(hidden)]
886 pub fn effective_unicode_bidi(&self) -> UnicodeBidi {
887 if self.unicode_bidi_explicit {
888 self.unicode_bidi
889 } else if self.direction == LayoutDirection::Inherit {
890 UnicodeBidi::Normal
891 } else {
892 UnicodeBidi::Isolate
893 }
894 }
895
896 pub fn has_opaque_background(&self) -> bool {
898 self.background
899 .as_ref()
900 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
901 }
902
903 pub fn text_style(&self) -> Option<&TextStyleRefinement> {
905 if self.text.is_some() {
906 Some(&self.text)
907 } else {
908 None
909 }
910 }
911
912 pub fn overflow_mask(
915 &self,
916 bounds: Bounds<Pixels>,
917 rem_size: Pixels,
918 ) -> Option<ContentMask<Pixels>> {
919 match self.overflow {
920 Point {
921 x: Overflow::Visible,
922 y: Overflow::Visible,
923 } => None,
924 _ => {
925 let mut min = bounds.origin;
926 let mut max = bounds.bottom_right();
927
928 if self
929 .border_color
930 .is_some_and(|background| !background.is_transparent())
931 {
932 min.x += self.border_widths.left.to_pixels(rem_size);
933 max.x -= self.border_widths.right.to_pixels(rem_size);
934 min.y += self.border_widths.top.to_pixels(rem_size);
935 max.y -= self.border_widths.bottom.to_pixels(rem_size);
936 }
937
938 let bounds = match (
939 self.overflow.x == Overflow::Visible,
940 self.overflow.y == Overflow::Visible,
941 ) {
942 (true, true) => return None,
944 (true, false) => Bounds::from_corners(
946 point(min.x, bounds.origin.y),
947 point(max.x, bounds.bottom_right().y),
948 ),
949 (false, true) => Bounds::from_corners(
951 point(bounds.origin.x, min.y),
952 point(bounds.bottom_right().x, max.y),
953 ),
954 (false, false) => Bounds::from_corners(min, max),
956 };
957
958 let mut fade_out = self.overflow_fade.to_pixels(rem_size);
959 if self.overflow.x == Overflow::Visible {
960 fade_out.left = Pixels::ZERO;
961 fade_out.right = Pixels::ZERO;
962 }
963 if self.overflow.y == Overflow::Visible {
964 fade_out.top = Pixels::ZERO;
965 fade_out.bottom = Pixels::ZERO;
966 }
967
968 Some(ContentMask { bounds, fade_out })
969 }
970 }
971 }
972
973 pub fn scroll_mask(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> ContentMask<Pixels> {
979 let scroll_style = Style {
980 overflow: Point {
981 x: self.overflow.x,
982 y: Overflow::Scroll,
983 },
984 border_color: self.border_color,
985 border_widths: self.border_widths,
986 overflow_fade: self.overflow_fade,
987 ..Style::default()
988 };
989 scroll_style
990 .overflow_mask(bounds, rem_size)
991 .expect("vertical scrolling always clips")
992 }
993
994 pub fn paint(
996 &self,
997 bounds: Bounds<Pixels>,
998 window: &mut Window,
999 cx: &mut App,
1000 continuation: impl FnOnce(&mut Window, &mut App),
1001 ) {
1002 window.with_element_opacity(self.opacity, |window| {
1003 if let Some(fragments) = window.current_inline_fragments.clone() {
1004 for fragment in fragments.iter() {
1005 self.paint_box(*fragment, window, cx, |_, _| {});
1006 }
1007
1008 continuation(window, cx);
1009
1010 return;
1011 }
1012
1013 self.paint_box(bounds, window, cx, continuation);
1014 });
1015 }
1016
1017 fn paint_box(
1018 &self,
1019 bounds: Bounds<Pixels>,
1020 window: &mut Window,
1021 cx: &mut App,
1022 continuation: impl FnOnce(&mut Window, &mut App),
1023 ) {
1024 #[cfg(debug_assertions)]
1025 if self.debug_below {
1026 cx.set_global(DebugBelow)
1027 }
1028
1029 #[cfg(debug_assertions)]
1030 if self.debug || cx.has_global::<DebugBelow>() {
1031 window.paint_quad(
1032 PaintQuad::new(bounds)
1033 .border_widths(px(1.))
1034 .border_color(crate::red()),
1035 );
1036 }
1037
1038 let rem_size = window.rem_size();
1039 let corner_radii = self
1040 .corner_radii
1041 .to_pixels(rem_size)
1042 .clamp_radii_for_quad_size(bounds.size);
1043 let corner_smoothing = self.corner_smoothing.unwrap_or_default();
1044
1045 let current_color = self.text.color.unwrap_or_else(|| window.text_style().color);
1046
1047 window.paint_drop_shadows(bounds, corner_radii, corner_smoothing, &self.box_shadow);
1048
1049 if let Some(ring) = self.ring.outer_quad(bounds, corner_radii, current_color) {
1050 window.paint_quad(ring.corner_smoothing(corner_smoothing));
1051 }
1052
1053 if !self.backdrop_filter.is_empty() {
1056 window.paint_backdrop_filter(
1057 bounds,
1058 corner_radii,
1059 corner_smoothing,
1060 &self.backdrop_filter,
1061 );
1062 }
1063
1064 let paint_box = |window: &mut Window, cx: &mut App| {
1068 let background_color = self.background.as_ref().and_then(Fill::color);
1069 if background_color.is_some_and(|color| !color.is_transparent()) {
1070 let background_color = background_color.unwrap_or_default();
1071 window.paint_quad(
1072 PaintQuad::new(bounds)
1073 .corner_radii(corner_radii)
1074 .background(background_color)
1075 .border_color(background_color.opacity(0.))
1076 .border_style(self.border_style)
1077 .corner_smoothing(corner_smoothing),
1078 );
1079 }
1080
1081 if let Some(ring) = self.inset_ring.inset_shadow(current_color) {
1082 window.paint_inset_shadows(
1083 bounds,
1084 corner_radii,
1085 corner_smoothing,
1086 std::slice::from_ref(&ring),
1087 );
1088 }
1089 window.paint_inset_shadows(bounds, corner_radii, corner_smoothing, &self.box_shadow);
1090
1091 continuation(window, cx);
1092
1093 if self.is_border_visible() {
1094 let border_widths = self.border_widths.to_pixels(rem_size);
1095 let border_color = self.border_color.unwrap_or_default();
1096 window.paint_quad(
1097 PaintQuad::new(bounds)
1098 .corner_radii(corner_radii)
1099 .background(border_color.opacity(0.))
1100 .border_widths(border_widths)
1101 .border_color(border_color)
1102 .border_style(self.border_style)
1103 .corner_smoothing(corner_smoothing)
1104 .border_dashed_length(self.border_dashed_length)
1105 .border_dashed_gap(self.border_dashed_gap),
1106 );
1107 }
1108 };
1109
1110 if self.filter.is_empty() {
1111 paint_box(window, cx);
1112 } else {
1113 window.with_filter_layer(
1114 bounds,
1115 corner_radii,
1116 corner_smoothing,
1117 &self.filter,
1118 |window| {
1119 paint_box(window, cx);
1120 },
1121 );
1122 }
1123
1124 #[cfg(debug_assertions)]
1125 if self.debug_below {
1126 cx.remove_global::<DebugBelow>();
1127 }
1128 }
1129
1130 fn is_border_visible(&self) -> bool {
1131 self.border_color
1132 .is_some_and(|background| !background.is_transparent())
1133 && self.border_widths.any(|length| !length.is_zero())
1134 }
1135}
1136
1137impl Default for Style {
1138 fn default() -> Self {
1139 Style {
1140 display: Display::Block,
1141 visibility: Visibility::Visible,
1142 direction: LayoutDirection::Inherit,
1143 unicode_bidi: UnicodeBidi::Normal,
1144 unicode_bidi_explicit: false,
1145 overflow: Point {
1146 x: Overflow::Visible,
1147 y: Overflow::Visible,
1148 },
1149 overflow_fade: Edges::<AbsoluteLength>::zero(),
1150 allow_concurrent_scroll: false,
1151 restrict_scroll_to_axis: false,
1152 scrollbar_width: AbsoluteLength::default(),
1153 position: Position::Relative,
1154 inset: Edges::auto(),
1155 margin: Edges::<Length>::zero(),
1156 padding: Edges::<DefiniteLength>::zero(),
1157 border_widths: Edges::<AbsoluteLength>::zero(),
1158 size: Size::auto(),
1159 min_size: Size::auto(),
1160 max_size: Size::auto(),
1161 aspect_ratio: None,
1162 gap: Size::default(),
1163 align_items: None,
1165 align_self: None,
1166 vertical_align: VerticalAlign::Baseline,
1167 align_content: None,
1168 justify_content: None,
1169 flex_direction: FlexDirection::Row,
1171 flex_wrap: FlexWrap::NoWrap,
1172 flex_grow: 0.0,
1173 flex_shrink: 1.0,
1174 flex_basis: Length::Auto,
1175 background: None,
1176 border_color: None,
1177 border_style: BorderStyle::default(),
1178 border_dashed_length: crate::scene::DEFAULT_BORDER_DASHED_LENGTH,
1179 border_dashed_gap: crate::scene::DEFAULT_BORDER_DASHED_GAP,
1180 corner_radii: Corners::default(),
1181 corner_smoothing: None,
1182 box_shadow: Default::default(),
1183 ring: Default::default(),
1184 inset_ring: Default::default(),
1185 filter: Default::default(),
1186 backdrop_filter: Default::default(),
1187 text: TextStyleRefinement::default(),
1188 mouse_cursor: None,
1189 opacity: None,
1190 grid_rows: None,
1191 grid_cols: None,
1192 grid_location: None,
1193
1194 #[cfg(debug_assertions)]
1195 debug: false,
1196 #[cfg(debug_assertions)]
1197 debug_below: false,
1198 }
1199 }
1200}
1201
1202#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1204pub struct UnderlineStyle {
1205 pub thickness: Pixels,
1207
1208 pub color: Option<Hsla>,
1210
1211 pub wavy: bool,
1213}
1214
1215#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1217pub struct StrikethroughStyle {
1218 pub thickness: Pixels,
1220
1221 pub color: Option<Hsla>,
1223}
1224
1225#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
1227pub enum Fill {
1228 Color(Background),
1230}
1231
1232impl Fill {
1233 pub fn color(&self) -> Option<Background> {
1237 match self {
1238 Fill::Color(color) => Some(*color),
1239 }
1240 }
1241}
1242
1243impl Default for Fill {
1244 fn default() -> Self {
1245 Self::Color(Background::default())
1246 }
1247}
1248
1249impl<T: Into<Background>> From<T> for Fill {
1250 fn from(color: T) -> Self {
1251 Self::Color(color.into())
1252 }
1253}
1254
1255impl From<TextStyle> for HighlightStyle {
1256 fn from(other: TextStyle) -> Self {
1257 Self::from(&other)
1258 }
1259}
1260
1261impl From<&TextStyle> for HighlightStyle {
1262 fn from(other: &TextStyle) -> Self {
1263 Self {
1264 color: Some(other.color),
1265 font_weight: Some(other.font_weight),
1266 font_width: Some(other.font_width),
1267 font_style: Some(other.font_style),
1268 background_color: other.background_color,
1269 underline: other.underline,
1270 strikethrough: other.strikethrough,
1271 fade_out: None,
1272 }
1273 }
1274}
1275
1276impl HighlightStyle {
1277 pub fn color(color: Hsla) -> Self {
1279 Self {
1280 color: Some(color),
1281 ..Default::default()
1282 }
1283 }
1284 #[must_use]
1287 pub fn highlight(self, other: HighlightStyle) -> Self {
1288 Self {
1289 color: other
1290 .color
1291 .map(|other_color| {
1292 if let Some(color) = self.color {
1293 color.blend(&other_color)
1294 } else {
1295 other_color
1296 }
1297 })
1298 .or(self.color),
1299 font_weight: other.font_weight.or(self.font_weight),
1300 font_width: other.font_width.or(self.font_width),
1301 font_style: other.font_style.or(self.font_style),
1302 background_color: other.background_color.or(self.background_color),
1303 underline: other.underline.or(self.underline),
1304 strikethrough: other.strikethrough.or(self.strikethrough),
1305 fade_out: other
1306 .fade_out
1307 .map(|source_fade| {
1308 self.fade_out
1309 .map(|dest_fade| (dest_fade * (1. + source_fade)).clamp(0., 1.))
1310 .unwrap_or(source_fade)
1311 })
1312 .or(self.fade_out),
1313 }
1314 }
1315}
1316
1317impl From<Hsla> for HighlightStyle {
1318 fn from(color: Hsla) -> Self {
1319 Self {
1320 color: Some(color),
1321 ..Default::default()
1322 }
1323 }
1324}
1325
1326impl From<FontWeight> for HighlightStyle {
1327 fn from(font_weight: FontWeight) -> Self {
1328 Self {
1329 font_weight: Some(font_weight),
1330 ..Default::default()
1331 }
1332 }
1333}
1334
1335impl From<FontStyle> for HighlightStyle {
1336 fn from(font_style: FontStyle) -> Self {
1337 Self {
1338 font_style: Some(font_style),
1339 ..Default::default()
1340 }
1341 }
1342}
1343
1344impl From<Rgba> for HighlightStyle {
1345 fn from(color: Rgba) -> Self {
1346 Self {
1347 color: Some(color.into_color()),
1348 ..Default::default()
1349 }
1350 }
1351}
1352
1353pub fn combine_highlights(
1355 a: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
1356 b: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
1357) -> impl Iterator<Item = (Range<usize>, HighlightStyle)> {
1358 let mut endpoints = Vec::new();
1359 let mut highlights = Vec::new();
1360 for (range, highlight) in a.into_iter().chain(b) {
1361 if !range.is_empty() {
1362 let highlight_id = highlights.len();
1363 endpoints.push((range.start, highlight_id, true));
1364 endpoints.push((range.end, highlight_id, false));
1365 highlights.push(highlight);
1366 }
1367 }
1368 endpoints.sort_unstable_by_key(|(position, _, _)| *position);
1369 let mut endpoints = endpoints.into_iter().peekable();
1370
1371 let mut active_styles = HashSet::default();
1372 let mut ix = 0;
1373 iter::from_fn(move || {
1374 while let Some((endpoint_ix, highlight_id, is_start)) = endpoints.peek() {
1375 let prev_index = mem::replace(&mut ix, *endpoint_ix);
1376 if ix > prev_index && !active_styles.is_empty() {
1377 let current_style = active_styles
1378 .iter()
1379 .fold(HighlightStyle::default(), |acc, highlight_id| {
1380 acc.highlight(highlights[*highlight_id])
1381 });
1382 return Some((prev_index..ix, current_style));
1383 }
1384
1385 if *is_start {
1386 active_styles.insert(*highlight_id);
1387 } else {
1388 active_styles.remove(highlight_id);
1389 }
1390 endpoints.next();
1391 }
1392 None
1393 })
1394}
1395
1396#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
1400pub enum AlignItems {
1402 Start,
1404 End,
1406 FlexStart,
1411 FlexEnd,
1416 Center,
1418 Baseline,
1420 Stretch,
1422}
1423pub type JustifyItems = AlignItems;
1429pub type AlignSelf = AlignItems;
1435pub type JustifySelf = AlignItems;
1441
1442#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default, Serialize, Deserialize, JsonSchema)]
1446pub enum VerticalAlign {
1447 #[default]
1449 Baseline,
1450 Middle,
1454 Top,
1456 Bottom,
1458}
1459
1460#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
1464pub enum AlignContent {
1466 Start,
1468 End,
1470 FlexStart,
1475 FlexEnd,
1480 Center,
1482 Stretch,
1484 SpaceBetween,
1487 SpaceEvenly,
1490 SpaceAround,
1493}
1494
1495pub type JustifyContent = AlignContent;
1499
1500#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1504pub enum Display {
1506 Block,
1508
1509 #[default]
1511 Flex,
1512
1513 Grid,
1515
1516 Inline,
1519
1520 InlineFlex,
1522
1523 None,
1525}
1526
1527#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1533pub enum FlexWrap {
1535 #[default]
1537 NoWrap,
1538 Wrap,
1540 WrapReverse,
1542}
1543
1544#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1554pub enum FlexDirection {
1556 #[default]
1560 Row,
1561 Column,
1565 RowReverse,
1569 ColumnReverse,
1573}
1574
1575#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1588pub enum Overflow {
1590 #[default]
1593 Visible,
1594 Clip,
1597 Hidden,
1600 Scroll,
1604}
1605
1606#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
1616pub enum Position {
1618 #[default]
1621 Relative,
1622 Absolute,
1628}
1629
1630impl From<AlignItems> for taffy::style::AlignItems {
1631 fn from(value: AlignItems) -> Self {
1632 match value {
1633 AlignItems::Start => Self::START,
1634 AlignItems::End => Self::END,
1635 AlignItems::FlexStart => Self::FLEX_START,
1636 AlignItems::FlexEnd => Self::FLEX_END,
1637 AlignItems::Center => Self::CENTER,
1638 AlignItems::Baseline => Self::BASELINE,
1639 AlignItems::Stretch => Self::STRETCH,
1640 }
1641 }
1642}
1643
1644impl From<AlignContent> for taffy::style::AlignContent {
1645 fn from(value: AlignContent) -> Self {
1646 match value {
1647 AlignContent::Start => Self::START,
1648 AlignContent::End => Self::END,
1649 AlignContent::FlexStart => Self::FLEX_START,
1650 AlignContent::FlexEnd => Self::FLEX_END,
1651 AlignContent::Center => Self::CENTER,
1652 AlignContent::Stretch => Self::STRETCH,
1653 AlignContent::SpaceBetween => Self::SPACE_BETWEEN,
1654 AlignContent::SpaceEvenly => Self::SPACE_EVENLY,
1655 AlignContent::SpaceAround => Self::SPACE_AROUND,
1656 }
1657 }
1658}
1659
1660impl From<Display> for taffy::style::Display {
1661 fn from(value: Display) -> Self {
1662 match value {
1663 Display::Block | Display::Inline => Self::Block,
1664 Display::Flex | Display::InlineFlex => Self::Flex,
1665 Display::Grid => Self::Grid,
1666 Display::None => Self::None,
1667 }
1668 }
1669}
1670
1671impl From<FlexWrap> for taffy::style::FlexWrap {
1672 fn from(value: FlexWrap) -> Self {
1673 match value {
1674 FlexWrap::NoWrap => Self::NoWrap,
1675 FlexWrap::Wrap => Self::Wrap,
1676 FlexWrap::WrapReverse => Self::WrapReverse,
1677 }
1678 }
1679}
1680
1681impl From<FlexDirection> for taffy::style::FlexDirection {
1682 fn from(value: FlexDirection) -> Self {
1683 match value {
1684 FlexDirection::Row => Self::Row,
1685 FlexDirection::Column => Self::Column,
1686 FlexDirection::RowReverse => Self::RowReverse,
1687 FlexDirection::ColumnReverse => Self::ColumnReverse,
1688 }
1689 }
1690}
1691
1692impl From<Overflow> for taffy::style::Overflow {
1693 fn from(value: Overflow) -> Self {
1694 match value {
1695 Overflow::Visible => Self::Visible,
1696 Overflow::Clip => Self::Clip,
1697 Overflow::Hidden => Self::Hidden,
1698 Overflow::Scroll => Self::Scroll,
1699 }
1700 }
1701}
1702
1703impl From<Position> for taffy::style::Position {
1704 fn from(value: Position) -> Self {
1705 match value {
1706 Position::Relative => Self::Relative,
1707 Position::Absolute => Self::Absolute,
1708 }
1709 }
1710}
1711
1712#[cfg(test)]
1713mod tests {
1714 use crate::{blue, green, hsla, linear_color_stop, linear_gradient, px, red, yellow};
1715 use palette::WithAlpha;
1716
1717 use super::*;
1718
1719 fn ring_gradient() -> Background {
1720 linear_gradient(
1721 90.,
1722 linear_color_stop(red(), 0.),
1723 linear_color_stop(blue(), 1.),
1724 )
1725 }
1726
1727 #[test]
1728 fn border_color_accepts_backgrounds_and_solid_colors() {
1729 let gradient = linear_gradient(
1730 90.0,
1731 linear_color_stop(red(), 0.0),
1732 linear_color_stop(blue(), 1.0),
1733 );
1734 let gradient_style = StyleRefinement::default().border_color(gradient);
1735 assert_eq!(gradient_style.border_color, Some(gradient));
1736
1737 let solid_style = StyleRefinement::default().border_color(red());
1738 assert_eq!(solid_style.border_color, Some(red().into()));
1739 }
1740
1741 #[test]
1742 fn test_basic_highlight_style_combination() {
1743 let style_a = HighlightStyle::default();
1744 let style_b = HighlightStyle::default();
1745 let style_a = style_a.highlight(style_b);
1746 assert_eq!(
1747 style_a,
1748 HighlightStyle::default(),
1749 "Combining empty styles should not produce a non-empty style."
1750 );
1751
1752 let mut style_b = HighlightStyle {
1753 color: Some(red()),
1754 strikethrough: Some(StrikethroughStyle {
1755 thickness: px(2.),
1756 color: Some(blue()),
1757 }),
1758 fade_out: Some(0.),
1759 font_style: Some(FontStyle::Italic),
1760 font_weight: Some(FontWeight(300.)),
1761 font_width: Some(FontWidth::CONDENSED),
1762 background_color: Some(yellow()),
1763 underline: Some(UnderlineStyle {
1764 thickness: px(2.),
1765 color: Some(red()),
1766 wavy: true,
1767 }),
1768 };
1769 let expected_style = style_b;
1770
1771 assert_eq!(
1772 TextStyle::default().highlight(style_b).font_width,
1773 FontWidth::CONDENSED
1774 );
1775
1776 let style_a = style_a.highlight(style_b);
1777 assert_eq!(
1778 style_a, expected_style,
1779 "Blending an empty style with another style should return the other style"
1780 );
1781
1782 let style_b = style_b.highlight(Default::default());
1783 assert_eq!(
1784 style_b, expected_style,
1785 "Blending a style with an empty style should not change the style."
1786 );
1787
1788 let mut style_c = expected_style;
1789
1790 let style_d = HighlightStyle {
1791 color: Some(blue().with_alpha(0.7)),
1792 strikethrough: Some(StrikethroughStyle {
1793 thickness: px(4.),
1794 color: Some(crate::red()),
1795 }),
1796 fade_out: Some(0.),
1797 font_style: Some(FontStyle::Oblique),
1798 font_weight: Some(FontWeight(800.)),
1799 font_width: Some(FontWidth::EXPANDED),
1800 background_color: Some(green()),
1801 underline: Some(UnderlineStyle {
1802 thickness: px(4.),
1803 color: None,
1804 wavy: false,
1805 }),
1806 };
1807
1808 let expected_style = HighlightStyle {
1809 color: Some(red().blend(&blue().with_alpha(0.7))),
1810 strikethrough: Some(StrikethroughStyle {
1811 thickness: px(4.),
1812 color: Some(red()),
1813 }),
1814 fade_out: Some(0.),
1816 font_style: Some(FontStyle::Oblique),
1817 font_weight: Some(FontWeight(800.)),
1818 font_width: Some(FontWidth::EXPANDED),
1819 background_color: Some(green()),
1820 underline: Some(UnderlineStyle {
1821 thickness: px(4.),
1822 color: None,
1823 wavy: false,
1824 }),
1825 };
1826
1827 let style_c = style_c.highlight(style_d);
1828 assert_eq!(
1829 style_c, expected_style,
1830 "Blending styles should blend properties where possible and override all others"
1831 );
1832 }
1833
1834 #[test]
1835 fn test_combine_highlights() {
1836 assert_eq!(
1837 combine_highlights(
1838 [
1839 (0..5, green().into()),
1840 (4..10, FontWeight::BOLD.into()),
1841 (15..20, yellow().into()),
1842 ],
1843 [
1844 (2..6, FontStyle::Italic.into()),
1845 (1..3, blue().into()),
1846 (21..23, red().into()),
1847 ]
1848 )
1849 .collect::<Vec<_>>(),
1850 [
1851 (
1852 0..1,
1853 HighlightStyle {
1854 color: Some(green()),
1855 ..Default::default()
1856 }
1857 ),
1858 (
1859 1..2,
1860 HighlightStyle {
1861 color: Some(blue()),
1862 ..Default::default()
1863 }
1864 ),
1865 (
1866 2..3,
1867 HighlightStyle {
1868 color: Some(blue()),
1869 font_style: Some(FontStyle::Italic),
1870 ..Default::default()
1871 }
1872 ),
1873 (
1874 3..4,
1875 HighlightStyle {
1876 color: Some(green()),
1877 font_style: Some(FontStyle::Italic),
1878 ..Default::default()
1879 }
1880 ),
1881 (
1882 4..5,
1883 HighlightStyle {
1884 color: Some(green()),
1885 font_weight: Some(FontWeight::BOLD),
1886 font_style: Some(FontStyle::Italic),
1887 ..Default::default()
1888 }
1889 ),
1890 (
1891 5..6,
1892 HighlightStyle {
1893 font_weight: Some(FontWeight::BOLD),
1894 font_style: Some(FontStyle::Italic),
1895 ..Default::default()
1896 }
1897 ),
1898 (
1899 6..10,
1900 HighlightStyle {
1901 font_weight: Some(FontWeight::BOLD),
1902 ..Default::default()
1903 }
1904 ),
1905 (
1906 15..20,
1907 HighlightStyle {
1908 color: Some(yellow()),
1909 ..Default::default()
1910 }
1911 ),
1912 (
1913 21..23,
1914 HighlightStyle {
1915 color: Some(red()),
1916 ..Default::default()
1917 }
1918 )
1919 ]
1920 );
1921 }
1922
1923 #[test]
1924 fn test_text_style_refinement() {
1925 let mut style = Style::default();
1926 style.refine(&StyleRefinement::default().text_size(px(20.0)));
1927 style.refine(&StyleRefinement::default().font_weight(FontWeight::SEMIBOLD));
1928 style.refine(&StyleRefinement::default().font_width(87.5));
1929
1930 assert_eq!(
1931 Some(AbsoluteLength::from(px(20.0))),
1932 style.text_style().unwrap().font_size
1933 );
1934
1935 assert_eq!(
1936 Some(FontWeight::SEMIBOLD),
1937 style.text_style().unwrap().font_weight
1938 );
1939
1940 let mut resolved = TextStyle::default();
1941 resolved.refine(style.text_style().unwrap());
1942 let inherited = resolved.highlight(HighlightStyle::color(red()));
1943
1944 assert_eq!(inherited.font().width, FontWidth::SEMI_CONDENSED);
1945 assert_eq!(inherited.to_run(4).font.width, FontWidth::SEMI_CONDENSED);
1946 assert_eq!(
1947 HighlightStyle::from(&inherited).font_width,
1948 Some(FontWidth::SEMI_CONDENSED)
1949 );
1950
1951 let mut element = crate::div()
1952 .font_width(FontWidth::CONDENSED)
1953 .font(crate::font("IBM Plex Sans"));
1954 assert_eq!(element.text_style().font_width, Some(FontWidth::NORMAL));
1955 }
1956
1957 #[test]
1958 fn dashed_border_pattern_has_legacy_defaults_and_refines_independently() {
1959 let mut style = Style::default();
1960
1961 assert_eq!(
1962 style.border_dashed_length,
1963 crate::scene::DEFAULT_BORDER_DASHED_LENGTH
1964 );
1965 assert_eq!(
1966 style.border_dashed_gap,
1967 crate::scene::DEFAULT_BORDER_DASHED_GAP
1968 );
1969
1970 style.refine(
1971 &StyleRefinement::default()
1972 .border_dashed_length(4.0)
1973 .border_dashed_gap(0.5),
1974 );
1975
1976 assert_eq!(style.border_dashed_length, 4.0);
1977 assert_eq!(style.border_dashed_gap, 0.5);
1978 assert_eq!(style.border_style, BorderStyle::Solid);
1979
1980 style.refine(&StyleRefinement::default().border_dashed());
1981 assert_eq!(style.border_style, BorderStyle::Dashed);
1982 }
1983
1984 #[test]
1985 fn outer_ring_is_hollow_and_composes_with_shadows() {
1986 let drop_shadow = BoxShadow::new(px(0.), px(2.), blue()).blur_radius(px(4.));
1987 let mut style = Style::default();
1988 style.refine(
1989 &StyleRefinement::default()
1990 .ring_2()
1991 .shadow(vec![drop_shadow.clone()]),
1992 );
1993 let current_color = hsla(0.7, 0.5, 0.4, 0.8);
1994 style.refine(&StyleRefinement::default().ring(px(3.5)));
1995 let element_bounds = Bounds {
1996 origin: point(px(10.), px(20.)),
1997 size: size(px(100.), px(40.)),
1998 };
1999 let element_radii = Corners {
2000 top_left: px(0.),
2001 top_right: px(4.),
2002 bottom_right: px(10.),
2003 bottom_left: px(20.),
2004 };
2005 let ring = style
2006 .ring
2007 .outer_quad(element_bounds, element_radii, current_color)
2008 .unwrap();
2009
2010 assert_eq!(ring.bounds.dilate(-px(3.5)), element_bounds);
2011 assert_eq!(ring.border_widths, Edges::all(px(3.5)));
2012 assert_eq!(
2013 ring.corner_radii.map(|radius| *radius - px(3.5)),
2014 element_radii
2015 );
2016 assert!(ring.background.is_transparent());
2017 assert_eq!(ring.border_color, current_color.into());
2018 assert_eq!(ring.border_style, BorderStyle::Solid);
2019 assert_eq!(style.box_shadow, vec![drop_shadow]);
2020
2021 let gradient = ring_gradient();
2022 style.refine(&StyleRefinement::default().ring_color(gradient));
2023 assert_eq!(
2024 style
2025 .ring
2026 .outer_quad(element_bounds, element_radii, current_color)
2027 .unwrap()
2028 .border_color,
2029 gradient
2030 );
2031
2032 style.refine(&StyleRefinement::default().ring_0());
2033 assert!(
2034 style
2035 .ring
2036 .outer_quad(element_bounds, element_radii, current_color)
2037 .is_none()
2038 );
2039 assert_eq!(style.box_shadow.len(), 1);
2040 }
2041
2042 #[test]
2043 fn inset_ring_utility_refines_independently_and_lowers_to_an_inset_shadow() {
2044 let gradient = ring_gradient();
2045 let mut style = Style::default();
2046 style.refine(
2047 &StyleRefinement::default()
2048 .inset_ring(px(1.))
2049 .inset_ring_color(gradient),
2050 );
2051 style.refine(&StyleRefinement::default().inset_ring_4());
2052
2053 let inset = style.inset_ring.inset_shadow(blue()).unwrap();
2054
2055 assert_eq!(inset.offset, point(px(0.), px(0.)));
2056 assert_eq!(inset.blur_radius, px(0.));
2057 assert_eq!(inset.spread_radius, px(4.));
2058 assert_eq!(inset.color, gradient);
2059 assert!(inset.inset);
2060 assert_eq!(style.ring, RingStyle::default());
2061 }
2062}