1use std::{fmt, ops::Range};
4
5use zng_layout::{context::*, unit::*};
6
7use crate::*;
8
9#[derive(Clone, Default, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11pub enum ExtendMode {
12 #[default]
16 Clamp,
17 Repeat,
19 Reflect,
21}
22impl fmt::Debug for ExtendMode {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 if f.alternate() {
25 write!(f, "ExtendMode::")?;
26 }
27 match self {
28 ExtendMode::Clamp => write!(f, "Clamp"),
29 ExtendMode::Repeat => write!(f, "Repeat"),
30 ExtendMode::Reflect => write!(f, "Reflect"),
31 }
32 }
33}
34impl From<ExtendMode> for RenderExtendMode {
35 fn from(mode: ExtendMode) -> Self {
37 match mode {
38 ExtendMode::Clamp => RenderExtendMode::Clamp,
39 ExtendMode::Repeat => RenderExtendMode::Repeat,
40 ExtendMode::Reflect => RenderExtendMode::Repeat,
41 }
42 }
43}
44
45pub type RenderExtendMode = zng_view_api::ExtendMode;
50
51#[derive(Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
59pub enum GradientRadiusBase {
60 ClosestSide,
62 ClosestCorner,
64 FarthestSide,
66 #[default]
70 FarthestCorner,
71}
72impl fmt::Debug for GradientRadiusBase {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 if f.alternate() {
75 write!(f, "GradientRadiusBase::")?;
76 }
77 match self {
78 Self::ClosestSide => write!(f, "ClosestSide"),
79 Self::ClosestCorner => write!(f, "ClosestCorner"),
80 Self::FarthestSide => write!(f, "FarthestSide"),
81 Self::FarthestCorner => write!(f, "FarthestCorner"),
82 }
83 }
84}
85
86#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
88pub struct GradientRadius {
89 pub base: GradientRadiusBase,
91
92 pub circle: bool,
96
97 pub radii: Size,
99}
100impl fmt::Debug for GradientRadius {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("GradientRadius")
103 .field("base", &self.base)
104 .field("radius", &self.radii)
105 .finish()
106 }
107}
108impl Default for GradientRadius {
109 fn default() -> Self {
111 Self::farthest_corner(1.fct())
112 }
113}
114impl GradientRadius {
115 pub fn closest_side(radius: impl Into<Size>) -> Self {
117 Self {
118 base: GradientRadiusBase::ClosestSide,
119 circle: false,
120 radii: radius.into(),
121 }
122 }
123
124 pub fn closest_corner(radius: impl Into<Size>) -> Self {
126 Self {
127 base: GradientRadiusBase::ClosestCorner,
128 circle: false,
129 radii: radius.into(),
130 }
131 }
132
133 pub fn farthest_side(radius: impl Into<Size>) -> Self {
135 Self {
136 base: GradientRadiusBase::FarthestSide,
137 circle: false,
138 radii: radius.into(),
139 }
140 }
141
142 pub fn farthest_corner(radius: impl Into<Size>) -> Self {
144 Self {
145 base: GradientRadiusBase::FarthestCorner,
146 circle: false,
147 radii: radius.into(),
148 }
149 }
150
151 pub fn circle(mut self) -> Self {
153 self.circle = true;
154 self
155 }
156
157 pub fn layout(&self, center: PxPoint) -> PxSize {
161 let size = LAYOUT.constraints().fill_size();
162
163 let min_sides = || {
164 PxSize::new(
165 center.x.min(size.width - center.x).max(Px(0)),
166 center.y.min(size.height - center.y).max(Px(0)),
167 )
168 };
169 let max_sides = || {
170 PxSize::new(
171 center.x.max(size.width - center.x).max(Px(0)),
172 center.y.max(size.height - center.y).max(Px(0)),
173 )
174 };
175
176 let base_size = match self.base {
177 GradientRadiusBase::ClosestSide => {
178 let min = min_sides();
179 if self.circle {
180 PxSize::splat(min.width.min(min.height))
181 } else {
182 min
183 }
184 }
185 GradientRadiusBase::ClosestCorner => {
186 let min = min_sides();
187 if self.circle {
188 let s = min.cast::<f32>();
189 let l = s.width.hypot(s.height);
190 PxSize::splat(Px(l as _))
191 } else {
192 let s = std::f32::consts::FRAC_1_SQRT_2 * 2.0;
194 PxSize::new(min.width * s, min.height * s)
195 }
196 }
197 GradientRadiusBase::FarthestSide => {
198 let max = max_sides();
199 if self.circle {
200 PxSize::splat(max.width.max(max.height))
201 } else {
202 max
203 }
204 }
205 GradientRadiusBase::FarthestCorner => {
206 let max = max_sides();
207 if self.circle {
208 let s = max.cast::<f32>();
209 let l = s.width.hypot(s.height);
210 PxSize::splat(Px(l as _))
211 } else {
212 let s = std::f32::consts::FRAC_1_SQRT_2 * 2.0;
213 PxSize::new(max.width * s, max.height * s)
214 }
215 }
216 };
217
218 LAYOUT.with_constraints(PxConstraints2d::new_exact_size(base_size), || self.radii.layout_dft(base_size))
219 }
220}
221impl_from_and_into_var! {
222 fn from(base: GradientRadiusBase) -> GradientRadius {
224 GradientRadius {
225 base,
226 circle: false,
227 radii: Size::fill(),
228 }
229 }
230
231 fn from<B: Into<GradientRadiusBase>, R: Into<Length>>((base, radius): (B, R)) -> GradientRadius {
233 GradientRadius {
234 base: base.into(),
235 circle: false,
236 radii: Size::splat(radius),
237 }
238 }
239
240 fn from(radius: Length) -> GradientRadius {
242 GradientRadius::farthest_corner(radius)
243 }
244 fn from(radii: Size) -> GradientRadius {
246 GradientRadius::farthest_corner(radii)
247 }
248
249 fn from(percent: FactorPercent) -> GradientRadius {
251 Length::Factor(percent.into()).into()
252 }
253 fn from(norm: Factor) -> GradientRadius {
255 Length::Factor(norm).into()
256 }
257 fn from(f: f32) -> GradientRadius {
259 Length::DipF32(f).into()
260 }
261 fn from(i: i32) -> GradientRadius {
263 Length::Dip(Dip::new(i)).into()
264 }
265 fn from(l: Px) -> GradientRadius {
267 Length::Px(l).into()
268 }
269 fn from(l: Dip) -> GradientRadius {
271 Length::Dip(l).into()
272 }
273}
274
275#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
288pub enum LinearGradientAxis {
289 Angle(AngleRadian),
294
295 Line(Line),
298}
299impl fmt::Debug for LinearGradientAxis {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 if f.alternate() {
302 match self {
303 LinearGradientAxis::Angle(a) => f.debug_tuple("LinearGradientAxis::Angle").field(a).finish(),
304 LinearGradientAxis::Line(l) => f.debug_tuple("LinearGradientAxis::Line").field(l).finish(),
305 }
306 } else {
307 match self {
308 LinearGradientAxis::Angle(a) => write!(f, "{}.deg()", AngleDegree::from(*a).0),
309 LinearGradientAxis::Line(l) => write!(f, "{l:?}"),
310 }
311 }
312 }
313}
314impl Layout2d for LinearGradientAxis {
315 type Px = PxLine;
316
317 fn layout(&self) -> Self::Px {
318 self.layout_dft(PxLine::new(PxPoint::new(Px(0), LAYOUT.viewport().height), PxPoint::zero()))
319 }
320
321 fn layout_dft(&self, default: Self::Px) -> Self::Px {
322 match self {
323 LinearGradientAxis::Angle(rad) => {
324 let dir_x = rad.0.sin();
325 let dir_y = -rad.0.cos();
326
327 let av = LAYOUT.constraints().fill_size();
328 let av_width = av.width.0 as f32;
329 let av_height = av.height.0 as f32;
330
331 let line_length = (dir_x * av_width).abs() + (dir_y * av_height).abs();
332
333 let inv_dir_length = 1.0 / (dir_x * dir_x + dir_y * dir_y).sqrt();
334
335 let delta = euclid::Vector2D::<_, ()>::new(
336 dir_x * inv_dir_length * line_length / 2.0,
337 dir_y * inv_dir_length * line_length / 2.0,
338 );
339
340 let center = euclid::Point2D::new(av_width / 2.0, av_height / 2.0);
341
342 let start = center - delta;
343 let end = center + delta;
344 PxLine::new(
345 PxPoint::new(Px(start.x as i32), Px(start.y as i32)),
346 PxPoint::new(Px(end.x as i32), Px(end.y as i32)),
347 )
348 }
349 LinearGradientAxis::Line(line) => line.layout_dft(default),
350 }
351 }
352
353 fn affect_mask(&self) -> LayoutMask {
354 match self {
355 LinearGradientAxis::Angle(_) => LayoutMask::CONSTRAINTS,
356 LinearGradientAxis::Line(line) => line.affect_mask(),
357 }
358 }
359}
360impl_from_and_into_var! {
361 fn from(angle: AngleRadian) -> LinearGradientAxis {
362 LinearGradientAxis::Angle(angle)
363 }
364 fn from(angle: AngleDegree) -> LinearGradientAxis {
365 LinearGradientAxis::Angle(angle.into())
366 }
367 fn from(angle: AngleTurn) -> LinearGradientAxis {
368 LinearGradientAxis::Angle(angle.into())
369 }
370 fn from(angle: AngleGradian) -> LinearGradientAxis {
371 LinearGradientAxis::Angle(angle.into())
372 }
373 fn from(line: Line) -> LinearGradientAxis {
374 LinearGradientAxis::Line(line)
375 }
376}
377impl Transitionable for LinearGradientAxis {
378 fn lerp(self, to: &Self, step: EasingStep) -> Self {
380 use LinearGradientAxis::*;
381 match (self, to) {
382 (Angle(s), Angle(t)) => Angle(s.lerp(*t, step)),
383 (Line(s), Line(t)) => Line(s.lerp(t, step)),
384 (s, t) => {
385 if step <= 1.fct() {
386 s
387 } else {
388 t.clone()
389 }
390 }
391 }
392 }
393}
394
395#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
397pub struct ColorStop {
398 pub color: Rgba,
400 pub offset: Length,
409}
410impl fmt::Debug for ColorStop {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 if f.alternate() {
413 f.debug_struct("ColorStop")
414 .field("color", &self.color)
415 .field("offset", &self.offset)
416 .finish()
417 } else if self.is_positional() {
418 write!(f, "{:?}", self.color)
419 } else {
420 write!(f, "({:?}, {:?})", self.color, self.offset)
421 }
422 }
423}
424impl ColorStop {
425 pub fn new(color: impl Into<Rgba>, offset: impl Into<Length>) -> Self {
427 ColorStop {
428 color: color.into(),
429 offset: offset.into(),
430 }
431 }
432
433 pub fn new_positional(color: impl Into<Rgba>) -> Self {
439 ColorStop {
440 color: color.into(),
441 offset: Length::Default,
442 }
443 }
444
445 pub fn is_positional(&self) -> bool {
465 self.offset.is_default()
466 }
467
468 pub fn is_layout_positional(layout_offset: f32) -> bool {
475 !f32::is_finite(layout_offset)
476 }
477
478 pub fn layout(&self, axis: LayoutAxis) -> RenderGradientStop {
488 RenderGradientStop {
489 offset: if self.offset.is_default() {
490 f32::INFINITY
491 } else {
492 self.offset.layout_f32(axis)
493 },
494 color: self.color,
495 }
496 }
497}
498impl_from_and_into_var! {
499 fn from<C: Into<Rgba>, O: Into<Length>>((color, offset): (C, O)) -> ColorStop {
500 ColorStop::new(color, offset)
501 }
502
503 fn from(positional_color: Rgba) -> ColorStop {
504 ColorStop::new_positional(positional_color)
505 }
506
507 fn from(positional_color: Hsla) -> ColorStop {
508 ColorStop::new_positional(positional_color)
509 }
510
511 fn from(positional_color: Hsva) -> ColorStop {
512 ColorStop::new_positional(positional_color)
513 }
514}
515impl Transitionable for ColorStop {
516 fn lerp(self, to: &Self, step: EasingStep) -> Self {
517 Self {
518 color: self.color.lerp(&to.color, step),
519 offset: self.offset.lerp(&to.offset, step),
520 }
521 }
522}
523
524pub type RenderGradientStop = zng_view_api::GradientStop;
528
529#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
531pub enum GradientStop {
532 Color(ColorStop),
534 ColorHint(Length),
536}
537impl_from_and_into_var! {
538 fn from<C: Into<Rgba>, O: Into<Length>>(color_offset: (C, O)) -> GradientStop {
539 GradientStop::Color(color_offset.into())
540 }
541
542 fn from(color_stop: ColorStop) -> GradientStop {
543 GradientStop::Color(color_stop)
544 }
545
546 fn from(color_hint: Length) -> GradientStop {
547 GradientStop::ColorHint(color_hint)
548 }
549
550 fn from(color_hint: FactorPercent) -> GradientStop {
552 GradientStop::ColorHint(color_hint.into())
553 }
554
555 fn from(color_hint: Factor) -> GradientStop {
557 GradientStop::ColorHint(color_hint.into())
558 }
559
560 fn from(color_hint: f32) -> GradientStop {
562 GradientStop::ColorHint(color_hint.into())
563 }
564
565 fn from(color_hint: i32) -> GradientStop {
567 GradientStop::ColorHint(color_hint.into())
568 }
569
570 fn from(positional_color: Rgba) -> GradientStop {
572 GradientStop::Color(ColorStop::new_positional(positional_color))
573 }
574
575 fn from(positional_color: Hsla) -> GradientStop {
577 GradientStop::Color(ColorStop::new_positional(positional_color))
578 }
579
580 fn from(positional_color: Hsva) -> GradientStop {
582 GradientStop::Color(ColorStop::new_positional(positional_color))
583 }
584}
585impl fmt::Debug for GradientStop {
586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587 if f.alternate() {
588 match self {
589 GradientStop::Color(c) => f.debug_tuple("GradientStop::Color").field(c).finish(),
590 GradientStop::ColorHint(l) => f.debug_tuple("GradientStop::ColorHint").field(l).finish(),
591 }
592 } else {
593 match self {
594 GradientStop::Color(c) => write!(f, "{c:?}"),
595 GradientStop::ColorHint(l) => write!(f, "{l:?}"),
596 }
597 }
598 }
599}
600
601#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
605pub struct GradientStops {
606 pub start: ColorStop,
608
609 pub middle: Vec<GradientStop>,
611
612 pub end: ColorStop,
614}
615impl fmt::Debug for GradientStops {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 if f.alternate() {
618 f.debug_struct("GradientStops")
619 .field("start", &self.start)
620 .field("middle", &self.middle)
621 .field("end", &self.end)
622 .finish()
623 } else {
624 write!(f, "stops![{:?}, ", self.start)?;
625 for stop in &self.middle {
626 write!(f, "{stop:?}, ")?;
627 }
628 write!(f, "{:?}]", self.end)
629 }
630 }
631}
632#[expect(clippy::len_without_is_empty)] impl GradientStops {
634 pub fn new(start: impl Into<Rgba>, end: impl Into<Rgba>) -> Self {
636 GradientStops {
637 start: ColorStop {
638 color: start.into(),
639 offset: Length::zero(),
640 },
641 middle: vec![],
642 end: ColorStop {
643 color: end.into(),
644 offset: 100.pct().into(),
645 },
646 }
647 }
648
649 fn start_missing() -> ColorStop {
650 ColorStop {
651 color: colors::BLACK.transparent(),
652 offset: Length::zero(),
653 }
654 }
655
656 fn end_missing(start_color: Rgba) -> ColorStop {
657 ColorStop {
658 color: start_color.transparent(),
659 offset: 100.pct().into(),
660 }
661 }
662
663 pub fn from_colors<C: Into<Rgba> + Copy>(colors: &[C]) -> Self {
670 if colors.is_empty() {
671 GradientStops {
672 start: Self::start_missing(),
673 middle: vec![],
674 end: Self::end_missing(colors::BLACK),
675 }
676 } else if colors.len() == 1 {
677 let color = colors[0].into();
678 GradientStops {
679 start: ColorStop {
680 color,
681 offset: Length::zero(),
682 },
683 middle: vec![],
684 end: Self::end_missing(color),
685 }
686 } else {
687 let last = colors.len() - 1;
688 let mut offset = 1.0 / colors.len() as f32;
689 let offset_step = offset;
690 GradientStops {
691 start: ColorStop {
692 color: colors[0].into(),
693 offset: Length::zero(),
694 },
695 middle: colors[1..last]
696 .iter()
697 .map(|&c| {
698 GradientStop::Color(ColorStop {
699 color: c.into(),
700 offset: {
701 let r = offset;
702 offset += offset_step;
703 r.fct().into()
704 },
705 })
706 })
707 .collect(),
708 end: ColorStop {
709 color: colors[last].into(),
710 offset: 100.pct().into(),
711 },
712 }
713 }
714 }
715
716 pub fn from_stripes<C: Into<Rgba> + Copy, T: Into<Factor>>(colors: &[C], transition: T) -> Self {
721 let tran = transition.into().0;
722 let tran = if tran.is_nan() || tran < 0.0 {
723 0.0
724 } else if tran > 1.0 {
725 1.0
726 } else {
727 tran
728 };
729
730 if colors.is_empty() {
731 GradientStops {
732 start: Self::start_missing(),
733 middle: vec![],
734 end: Self::end_missing(colors::BLACK),
735 }
736 } else if colors.len() == 1 {
737 let tran = 0.5 * tran;
738
739 let color = colors[0].into();
740 let end = Self::end_missing(color);
741 GradientStops {
742 start: ColorStop {
743 color,
744 offset: Length::zero(),
745 },
746 middle: vec![
747 GradientStop::Color(ColorStop {
748 color,
749 offset: Length::Factor(Factor(0.5 - tran)),
750 }),
751 GradientStop::Color(ColorStop {
752 color: end.color,
753 offset: Length::Factor(Factor(0.5 + tran)),
754 }),
755 ],
756 end,
757 }
758 } else {
759 let last = colors.len() - 1;
760 let mut offset = 1.0 / colors.len() as f32;
761 let stripe_width = offset;
762 let tran = stripe_width * tran;
763
764 let start = ColorStop {
765 color: colors[0].into(),
766 offset: Length::zero(),
767 };
768 let mut middle = vec![
769 ColorStop {
770 color: start.color,
771 offset: (offset - tran).fct().into(),
772 }
773 .into(),
774 ];
775
776 for &color in &colors[1..last] {
777 let color = color.into();
778 middle.push(
779 ColorStop {
780 color,
781 offset: (offset + tran).fct().into(),
782 }
783 .into(),
784 );
785 offset += stripe_width;
786 middle.push(
787 ColorStop {
788 color,
789 offset: (offset - tran).fct().into(),
790 }
791 .into(),
792 );
793 }
794
795 let end = ColorStop {
796 color: colors[last].into(),
797 offset: Length::Factor(Factor(1.0)),
798 };
799 middle.push(
800 ColorStop {
801 color: end.color,
802 offset: offset.fct().into(),
803 }
804 .into(),
805 );
806
807 GradientStops { start, middle, end }
808 }
809 }
810
811 pub fn from_stops<C: Into<ColorStop> + Copy>(stops: &[C]) -> Self {
815 if stops.is_empty() {
816 GradientStops {
817 start: Self::start_missing(),
818 middle: vec![],
819 end: Self::end_missing(colors::BLACK),
820 }
821 } else if stops.len() == 1 {
822 let start = stops[0].into();
823 GradientStops {
824 end: Self::end_missing(start.color),
825 start,
826 middle: vec![],
827 }
828 } else {
829 let last = stops.len() - 1;
830 GradientStops {
831 start: stops[0].into(),
832 middle: stops[1..last].iter().map(|&c| GradientStop::Color(c.into())).collect(),
833 end: stops[last].into(),
834 }
835 }
836 }
837
838 pub fn set_alpha<A: Into<RgbaComponent>>(&mut self, alpha: A) {
840 let alpha = alpha.into();
841 self.start.color.set_alpha(alpha);
842 for mid in &mut self.middle {
843 if let GradientStop::Color(c) = mid {
844 c.color.set_alpha(alpha);
845 }
846 }
847 self.end.color.set_alpha(alpha);
848 }
849
850 pub fn layout_linear(&self, axis: LayoutAxis, extend_mode: ExtendMode, line: &mut PxLine, render_stops: &mut Vec<RenderGradientStop>) {
860 let (start_offset, end_offset) = self.layout(axis, extend_mode, render_stops);
861
862 let mut l_start = line.start.cast::<f32>();
863 let mut l_end = line.end.cast::<f32>();
864
865 let v = l_end - l_start;
866 let v = v / LAYOUT.constraints_for(axis).fill().0 as f32;
867
868 l_end = l_start + v * end_offset;
869 l_start += v * start_offset;
870
871 line.start = l_start.cast::<Px>();
872 line.end = l_end.cast::<Px>();
873 }
874
875 pub fn layout_radial(&self, axis: LayoutAxis, extend_mode: ExtendMode, render_stops: &mut Vec<RenderGradientStop>) {
879 self.layout(axis, extend_mode, render_stops);
880 }
881
882 fn layout(&self, axis: LayoutAxis, extend_mode: ExtendMode, render_stops: &mut Vec<RenderGradientStop>) -> (f32, f32) {
886 fn is_positional(o: f32) -> bool {
895 ColorStop::is_layout_positional(o)
896 }
897
898 render_stops.clear();
899
900 if extend_mode == ExtendMode::Reflect {
901 render_stops.reserve((self.middle.len() + 2) * 2);
902 } else {
903 render_stops.reserve(self.middle.len() + 2);
904 }
905
906 let mut start = self.start.layout(axis); if is_positional(start.offset) {
908 start.offset = 0.0;
909 }
910 render_stops.push(start);
911
912 let mut prev_offset = start.offset;
913 let mut hints = vec![];
914 let mut positional_start = None;
915
916 for gs in self.middle.iter() {
917 match gs {
918 GradientStop::Color(s) => {
919 let mut stop = s.layout(axis); if is_positional(stop.offset) {
921 if positional_start.is_none() {
922 positional_start = Some(render_stops.len());
923 }
924 render_stops.push(stop);
925 } else {
926 if stop.offset < prev_offset {
927 stop.offset = prev_offset; }
929 prev_offset = stop.offset;
930
931 render_stops.push(stop);
932
933 if let Some(start) = positional_start.take() {
934 Self::calculate_positional(start..render_stops.len(), render_stops, &hints);
937 }
938 }
939 }
940 GradientStop::ColorHint(_) => {
941 hints.push(render_stops.len());
942 render_stops.push(RenderGradientStop {
943 offset: 0.0,
945 color: colors::BLACK,
946 })
947 }
948 }
949 }
950
951 let mut stop = self.end.layout(axis); if is_positional(stop.offset) {
953 stop.offset = LAYOUT.constraints_for(axis).fill().0 as f32;
954 }
955 if stop.offset < prev_offset {
956 stop.offset = prev_offset; }
958 render_stops.push(stop);
959
960 if let Some(start) = positional_start.take() {
961 Self::calculate_positional(start..render_stops.len(), render_stops, &hints);
964 }
965
966 for &i in hints.iter() {
968 let prev = render_stops[i - 1];
969 let after = render_stops[i + 1];
970 let length = after.offset - prev.offset;
971 if length > 0.00001 {
972 if let GradientStop::ColorHint(offset) = &self.middle[i - 1] {
973 let mut offset = LAYOUT.with_constraints_for(
974 axis,
975 LAYOUT.constraints_for(axis).with_new_max(Px(length as i32)).with_fill(true),
976 || offset.layout_f32(axis),
977 );
978 if is_positional(offset) {
979 offset = length / 2.0;
980 } else {
981 offset = offset.clamp(prev.offset, after.offset);
982 }
983 offset += prev.offset;
984
985 let color = prev.color.lerp(&after.color, (100.0 / length / 2.0).fct());
986
987 let stop = &mut render_stops[i];
988 stop.color = color;
989 stop.offset = offset;
990 } else {
991 unreachable!()
992 }
993 } else {
994 render_stops[i] = prev;
995 }
996 }
997
998 if extend_mode == ExtendMode::Reflect {
1000 let last_offset = render_stops[render_stops.len() - 1].offset;
1001 for i in (0..render_stops.len()).rev() {
1002 let mut stop = render_stops[i];
1003 stop.offset = last_offset + last_offset - stop.offset;
1004 render_stops.push(stop);
1005 }
1006 }
1007
1008 let first = render_stops[0];
1009 let last = render_stops[render_stops.len() - 1];
1010
1011 let actual_length = last.offset - first.offset;
1012
1013 if actual_length >= 1.0 {
1014 for stop in render_stops {
1016 stop.offset = (stop.offset - first.offset) / actual_length;
1017 }
1018
1019 (first.offset, last.offset) } else {
1021 match extend_mode {
1023 ExtendMode::Clamp => {
1024 render_stops.clear();
1027 render_stops.push(first);
1028 render_stops.push(first);
1029 render_stops.push(last);
1030 render_stops.push(last);
1031 render_stops[0].offset = 0.0;
1032 render_stops[1].offset = 0.48; render_stops[2].offset = 0.52;
1034 render_stops[3].offset = 1.0;
1035
1036 let offset = last.offset;
1038 (offset - 10.0, offset + 10.0)
1039 }
1040 ExtendMode::Repeat | ExtendMode::Reflect => {
1041 let len = render_stops.len() as f32;
1043 let color = Rgba::new(
1044 render_stops.iter().map(|s| s.color.red).sum::<f32>() / len,
1045 render_stops.iter().map(|s| s.color.green).sum::<f32>() / len,
1046 render_stops.iter().map(|s| s.color.blue).sum::<f32>() / len,
1047 render_stops.iter().map(|s| s.color.alpha).sum::<f32>() / len,
1048 );
1049 render_stops.clear();
1050 render_stops.push(RenderGradientStop { offset: 0.0, color });
1051 render_stops.push(RenderGradientStop { offset: 1.0, color });
1052
1053 (0.0, 10.0) }
1055 }
1056 }
1057 }
1058
1059 fn calculate_positional(range: Range<usize>, render_stops: &mut [RenderGradientStop], hints: &[usize]) {
1060 let sequence_count = range.len() - hints.iter().filter(|i| range.contains(i)).count();
1062 debug_assert!(sequence_count > 1);
1063
1064 let (start_offset, layout_length) = {
1066 let sequence_ender = (range.end..render_stops.len())
1068 .find(|i| !hints.contains(i))
1069 .unwrap_or(range.end - 1);
1070 let sequence_starter = (0..range.start).rev().find(|i| !hints.contains(i)).unwrap_or(range.start);
1072
1073 let start_offset = render_stops[sequence_starter].offset;
1074 let length = render_stops[sequence_ender].offset - start_offset;
1075 (start_offset, length)
1076 };
1077
1078 let d = layout_length / (sequence_count + 1) as f32;
1079 let mut offset = start_offset;
1080
1081 for i in range {
1082 if ColorStop::is_layout_positional(render_stops[i].offset) {
1083 offset += d;
1084 render_stops[i].offset = offset;
1085 }
1086 }
1087 }
1088
1089 pub fn len(&self) -> usize {
1091 self.middle.len() + 2
1092 }
1093}
1094impl_from_and_into_var! {
1095 fn from(colors: &[Rgba]) -> GradientStops {
1097 GradientStops::from_colors(colors)
1098 }
1099
1100 fn from(colors: &[Hsva]) -> GradientStops {
1102 GradientStops::from_colors(colors)
1103 }
1104
1105 fn from(colors: &[Hsla]) -> GradientStops {
1107 GradientStops::from_colors(colors)
1108 }
1109
1110 fn from<L: Into<Length> + Copy>(stops: &[(Rgba, L)]) -> GradientStops {
1112 GradientStops::from_stops(stops)
1113 }
1114 fn from<L: Into<Length> + Copy>(stops: &[(Hsla, L)]) -> GradientStops {
1116 GradientStops::from_stops(stops)
1117 }
1118 fn from<L: Into<Length> + Copy>(stops: &[(Hsva, L)]) -> GradientStops {
1120 GradientStops::from_stops(stops)
1121 }
1122
1123 fn from<const N: usize>(colors: &[Rgba; N]) -> GradientStops {
1125 GradientStops::from_colors(colors)
1126 }
1127
1128 fn from<const N: usize>(colors: &[Hsla; N]) -> GradientStops {
1130 GradientStops::from_colors(colors)
1131 }
1132
1133 fn from<const N: usize>(colors: &[Hsva; N]) -> GradientStops {
1135 GradientStops::from_colors(colors)
1136 }
1137
1138 fn from<L: Into<Length> + Copy, const N: usize>(stops: &[(Rgba, L); N]) -> GradientStops {
1140 GradientStops::from_stops(stops)
1141 }
1142 fn from<L: Into<Length> + Copy, const N: usize>(stops: &[(Hsva, L); N]) -> GradientStops {
1144 GradientStops::from_stops(stops)
1145 }
1146 fn from<L: Into<Length> + Copy, const N: usize>(stops: &[(Hsla, L); N]) -> GradientStops {
1148 GradientStops::from_stops(stops)
1149 }
1150
1151 fn from<const N: usize>(colors: [Rgba; N]) -> GradientStops {
1153 GradientStops::from_colors(&colors)
1154 }
1155 fn from<const N: usize>(colors: [Hsva; N]) -> GradientStops {
1157 GradientStops::from_colors(&colors)
1158 }
1159 fn from<const N: usize>(colors: [Hsla; N]) -> GradientStops {
1161 GradientStops::from_colors(&colors)
1162 }
1163
1164 fn from<L: Into<Length> + Copy, const N: usize>(stops: [(Rgba, L); N]) -> GradientStops {
1166 GradientStops::from_stops(&stops)
1167 }
1168 fn from<L: Into<Length> + Copy, const N: usize>(stops: [(Hsva, L); N]) -> GradientStops {
1170 GradientStops::from_stops(&stops)
1171 }
1172 fn from<L: Into<Length> + Copy, const N: usize>(stops: [(Hsla, L); N]) -> GradientStops {
1174 GradientStops::from_stops(&stops)
1175 }
1176}
1177
1178#[doc(hidden)]
1179#[macro_export]
1180macro_rules! __stops {
1181 (
1187 start: $start:expr,
1188 middle: [$($middle:expr),*],
1189 tail: ($color:expr, $stop0:expr, $stop1:expr), $($stops:tt)+
1190 ) => {
1191 $crate::__stops! {
1192 start: $start,
1193 middle: [$($middle,)* ($color, $stop0), ($color, $stop1)],
1194 tail: $($stops)+
1195 }
1196 };
1197 (
1203 start: $start:expr,
1204 middle: [$($middle:expr),*],
1205 tail: $next_middle:expr, $($stops:tt)+
1206 ) => {
1207 $crate::__stops! {
1208 start: $start,
1209 middle: [$($middle,)* $next_middle],
1210 tail: $($stops)+
1211 }
1212 };
1213 (
1219 start: $start:expr,
1220 middle: [$($middle:expr),*],
1221 tail: ($color:expr, $stop0:expr, $stop1:expr) $(,)?
1222 ) => {
1223 $crate::__stops! {
1224 start: $start,
1225 middle: [$($middle,)* ($color, $stop0)],
1226 tail: ($color, $stop1)
1227 }
1228 };
1229 (
1235 start: $start:expr,
1236 middle: [$($middle:expr),*],
1237 tail: $end:expr $(,)?
1238 ) => {
1239 $crate::gradient::GradientStops {
1240 start: $crate::gradient::ColorStop::from($start),
1241 middle: std::vec![$($crate::gradient::GradientStop::from($middle)),*],
1242 end: $crate::gradient::ColorStop::from($end),
1243 }
1244 };
1245}
1246#[macro_export]
1279macro_rules! stops {
1280 (($color:expr, $stop0:expr, $stop1:expr) $(,)?) => {
1283 $crate::__stops! {
1284 start: ($color, $stop0),
1285 middle: [],
1286 tail: ($color, $stop1)
1287 }
1288 };
1289 (($color:expr, $stop0:expr, $stop1:expr), $($stops:tt)+) => {
1292 $crate::__stops! {
1293 start: ($color, $stop0),
1294 middle: [($color, $stop1)],
1295 tail: $($stops)+
1296 }
1297 };
1298 ($start:expr, $($stops:tt)+) => {
1299 $crate::__stops! {
1300 start: $start,
1301 middle: [],
1302 tail: $($stops)+
1303 }
1304 };
1305}
1306#[doc(inline)]
1307pub use stops;
1308
1309#[cfg(test)]
1310mod tests {
1311 use zng_app_context::{AppId, LocalContext};
1312
1313 use super::*;
1314
1315 #[test]
1316 fn stops_simple_2() {
1317 let stops = stops![colors::BLACK, colors::WHITE];
1318
1319 assert!(stops.start.is_positional());
1320 assert_eq!(stops.start.color, colors::BLACK);
1321
1322 assert!(stops.middle.is_empty());
1323
1324 assert!(stops.end.is_positional());
1325 assert_eq!(stops.end.color, colors::WHITE);
1326 }
1327
1328 fn test_layout_stops(stops: GradientStops) -> Vec<RenderGradientStop> {
1329 let _app = LocalContext::start_app(AppId::new_unique());
1330
1331 let mut render_stops = vec![];
1332
1333 let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(100), Px(100)), Px(0));
1334 LAYOUT.with_context(metrics, || {
1335 stops.layout_linear(
1336 LayoutAxis::X,
1337 ExtendMode::Clamp,
1338 &mut PxLine::new(PxPoint::zero(), PxPoint::new(Px(100), Px(100))),
1339 &mut render_stops,
1340 );
1341 });
1342
1343 render_stops
1344 }
1345
1346 #[test]
1347 fn positional_end_stops() {
1348 let stops = test_layout_stops(stops![colors::BLACK, colors::WHITE]);
1349 assert_eq!(stops.len(), 2);
1350
1351 assert_eq!(
1352 stops[0],
1353 RenderGradientStop {
1354 color: colors::BLACK,
1355 offset: 0.0
1356 }
1357 );
1358 assert_eq!(
1359 stops[1],
1360 RenderGradientStop {
1361 color: colors::WHITE,
1362 offset: 1.0
1363 }
1364 );
1365 }
1366
1367 #[test]
1368 fn single_color_2_stops_only() {
1369 let stops = stops![(colors::BLACK, 0, 100.pct())];
1370
1371 assert_eq!(stops.start, ColorStop::new(colors::BLACK, 0));
1372 assert!(stops.middle.is_empty());
1373 assert_eq!(stops.end, ColorStop::new(colors::BLACK, 100.pct()));
1374 }
1375
1376 #[test]
1377 fn single_color_2_stops_at_start() {
1378 let stops = stops![(colors::BLACK, 0, 50.pct()), colors::WHITE];
1379
1380 assert_eq!(stops.start, ColorStop::new(colors::BLACK, 0));
1381 assert_eq!(stops.middle.len(), 1);
1382 assert_eq!(stops.middle[0], GradientStop::Color(ColorStop::new(colors::BLACK, 50.pct())));
1383 assert_eq!(stops.end, ColorStop::new_positional(colors::WHITE));
1384 }
1385
1386 #[test]
1387 fn single_color_2_stops_at_middle() {
1388 let stops = stops![colors::BLACK, (colors::RED, 10.pct(), 90.pct()), colors::WHITE];
1389
1390 assert_eq!(stops.start, ColorStop::new_positional(colors::BLACK));
1391 assert_eq!(stops.middle.len(), 2);
1392 assert_eq!(stops.middle[0], GradientStop::Color(ColorStop::new(colors::RED, 10.pct())));
1393 assert_eq!(stops.middle[1], GradientStop::Color(ColorStop::new(colors::RED, 90.pct())));
1394 assert_eq!(stops.end, ColorStop::new_positional(colors::WHITE));
1395 }
1396
1397 #[test]
1398 fn single_color_2_stops_at_end() {
1399 let stops = stops![colors::BLACK, (colors::WHITE, 10.pct(), 50.pct())];
1400
1401 assert_eq!(stops.start, ColorStop::new_positional(colors::BLACK));
1402 assert_eq!(stops.middle.len(), 1);
1403 assert_eq!(stops.middle[0], GradientStop::Color(ColorStop::new(colors::WHITE, 10.pct())));
1404 assert_eq!(stops.end, ColorStop::new(colors::WHITE, 50.pct()));
1405 }
1406
1407 #[test]
1408 fn color_hint() {
1409 let stops = stops![colors::BLACK, 30.pct(), colors::WHITE];
1410 assert_eq!(stops.middle.len(), 1);
1411 assert_eq!(stops.middle[0], GradientStop::ColorHint(30.pct().into()));
1412 }
1413
1414 #[test]
1415 fn color_hint_layout() {
1416 let stops = test_layout_stops(stops![colors::BLACK, 30.pct(), colors::WHITE]);
1417 assert_eq!(stops.len(), 3);
1418 assert_eq!(
1419 stops[0],
1420 RenderGradientStop {
1421 color: colors::BLACK,
1422 offset: 0.0
1423 }
1424 );
1425 assert_eq!(
1426 stops[1],
1427 RenderGradientStop {
1428 color: Rgba::new(0.5, 0.5, 0.5, 1.0),
1429 offset: 30.0 / 100.0
1430 }
1431 );
1432 assert_eq!(
1433 stops[2],
1434 RenderGradientStop {
1435 color: colors::WHITE,
1436 offset: 1.0
1437 }
1438 );
1439 }
1440}