1use super::Color;
9use crate::SharedVector;
10use crate::properties::InterpolatedPropertyValue;
11use euclid::default::{Point2D, Size2D};
12
13#[cfg(not(feature = "std"))]
14use num_traits::float::Float;
15
16#[derive(Clone, PartialEq, Debug, derive_more::From)]
21#[repr(C)]
22#[non_exhaustive]
23pub enum Brush {
24 SolidColor(Color),
26 LinearGradient(LinearGradientBrush),
29 RadialGradient(RadialGradientBrush),
32 ConicGradient(ConicGradientBrush),
35}
36
37impl Default for Brush {
39 fn default() -> Self {
40 Self::SolidColor(Color::default())
41 }
42}
43
44impl Brush {
45 pub fn color(&self) -> Color {
48 match self {
49 Brush::SolidColor(col) => *col,
50 Brush::LinearGradient(gradient) => {
51 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
52 }
53 Brush::RadialGradient(gradient) => {
54 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
55 }
56 Brush::ConicGradient(gradient) => {
57 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
58 }
59 }
60 }
61
62 pub fn is_transparent(&self) -> bool {
71 match self {
72 Brush::SolidColor(c) => c.alpha() == 0,
73 Brush::LinearGradient(_) => false,
74 Brush::RadialGradient(_) => false,
75 Brush::ConicGradient(_) => false,
76 }
77 }
78
79 pub fn is_opaque(&self) -> bool {
88 match self {
89 Brush::SolidColor(c) => c.alpha() == 255,
90 Brush::LinearGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
91 Brush::RadialGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
92 Brush::ConicGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
93 }
94 }
95
96 #[must_use]
100 pub fn brighter(&self, factor: f32) -> Self {
101 match self {
102 Brush::SolidColor(c) => Brush::SolidColor(c.brighter(factor)),
103 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
104 g.angle(),
105 g.stops().map(|s| GradientStop {
106 color: s.color.brighter(factor),
107 position: s.position,
108 }),
109 )),
110 Brush::RadialGradient(g) => {
111 let mut new_grad = g.clone();
112 for s in new_grad.0.make_mut_slice().iter_mut().skip(RadialGradientBrush::HEADER) {
113 s.color = s.color.brighter(factor);
114 }
115 Brush::RadialGradient(new_grad)
116 }
117 Brush::ConicGradient(g) => {
118 let mut new_grad = g.clone();
119 for x in new_grad.0.make_mut_slice().iter_mut().skip(ConicGradientBrush::HEADER) {
120 x.color = x.color.brighter(factor);
121 }
122 Brush::ConicGradient(new_grad)
123 }
124 }
125 }
126
127 #[must_use]
131 pub fn darker(&self, factor: f32) -> Self {
132 match self {
133 Brush::SolidColor(c) => Brush::SolidColor(c.darker(factor)),
134 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
135 g.angle(),
136 g.stops()
137 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
138 )),
139 Brush::RadialGradient(g) => {
140 let mut new_grad = g.clone();
141 for s in new_grad.0.make_mut_slice().iter_mut().skip(RadialGradientBrush::HEADER) {
142 s.color = s.color.darker(factor);
143 }
144 Brush::RadialGradient(new_grad)
145 }
146 Brush::ConicGradient(g) => {
147 let mut new_grad = g.clone();
148 for x in new_grad.0.make_mut_slice().iter_mut().skip(ConicGradientBrush::HEADER) {
149 x.color = x.color.darker(factor);
150 }
151 Brush::ConicGradient(new_grad)
152 }
153 }
154 }
155
156 #[must_use]
162 pub fn transparentize(&self, amount: f32) -> Self {
163 match self {
164 Brush::SolidColor(c) => Brush::SolidColor(c.transparentize(amount)),
165 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
166 g.angle(),
167 g.stops().map(|s| GradientStop {
168 color: s.color.transparentize(amount),
169 position: s.position,
170 }),
171 )),
172 Brush::RadialGradient(g) => {
173 let mut new_grad = g.clone();
174 for s in new_grad.0.make_mut_slice().iter_mut().skip(RadialGradientBrush::HEADER) {
175 s.color = s.color.transparentize(amount);
176 }
177 Brush::RadialGradient(new_grad)
178 }
179 Brush::ConicGradient(g) => {
180 let mut new_grad = g.clone();
181 for x in new_grad.0.make_mut_slice().iter_mut().skip(ConicGradientBrush::HEADER) {
182 x.color = x.color.transparentize(amount);
183 }
184 Brush::ConicGradient(new_grad)
185 }
186 }
187 }
188
189 #[must_use]
192 pub fn with_alpha(&self, alpha: f32) -> Self {
193 match self {
194 Brush::SolidColor(c) => Brush::SolidColor(c.with_alpha(alpha)),
195 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
196 g.angle(),
197 g.stops().map(|s| GradientStop {
198 color: s.color.with_alpha(alpha),
199 position: s.position,
200 }),
201 )),
202 Brush::RadialGradient(g) => {
203 let mut new_grad = g.clone();
204 for s in new_grad.0.make_mut_slice().iter_mut().skip(RadialGradientBrush::HEADER) {
205 s.color = s.color.with_alpha(alpha);
206 }
207 Brush::RadialGradient(new_grad)
208 }
209 Brush::ConicGradient(g) => {
210 let mut new_grad = g.clone();
211 for x in new_grad.0.make_mut_slice().iter_mut().skip(ConicGradientBrush::HEADER) {
212 x.color = x.color.with_alpha(alpha);
213 }
214 Brush::ConicGradient(new_grad)
215 }
216 }
217 }
218}
219
220#[derive(Clone, PartialEq, Debug)]
224#[repr(transparent)]
225pub struct LinearGradientBrush(SharedVector<GradientStop>);
226
227impl LinearGradientBrush {
228 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
233 let stop_iter = stops.into_iter();
234 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
235 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
237 encoded_angle_and_stops.extend(stop_iter);
238 Self(encoded_angle_and_stops)
239 }
240 pub fn angle(&self) -> f32 {
242 self.0[0].position
243 }
244 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
247 self.0.iter().skip(1)
249 }
250}
251
252#[inline]
254fn nan_eq(a: f32, b: f32) -> bool {
255 a == b || (a.is_nan() && b.is_nan())
256}
257
258#[inline]
261fn center_or_bbox(cx: f32, cy: f32, width: f32, height: f32, scale_factor: f32) -> (f32, f32) {
262 if cx.is_nan() { (width / 2.0, height / 2.0) } else { (cx * scale_factor, cy * scale_factor) }
263}
264
265#[derive(Clone, Debug)]
275#[repr(transparent)]
276pub struct RadialGradientBrush(SharedVector<GradientStop>);
277
278impl RadialGradientBrush {
279 const HEADER: usize = 3;
280
281 pub fn new_circle(stops: impl IntoIterator<Item = GradientStop>) -> Self {
284 let stop_iter = stops.into_iter();
285 let mut v = SharedVector::with_capacity(Self::HEADER + stop_iter.size_hint().0);
286 v.push(GradientStop { color: Default::default(), position: f32::NAN });
288 v.push(GradientStop { color: Default::default(), position: f32::NAN });
289 v.push(GradientStop { color: Default::default(), position: -1.0 });
290 v.extend(stop_iter);
291 Self(v)
292 }
293
294 #[inline]
295 fn center_x(&self) -> f32 {
296 self.0[0].position
297 }
298 #[inline]
299 fn center_y(&self) -> f32 {
300 self.0[1].position
301 }
302 #[inline]
303 fn radius(&self) -> f32 {
304 self.0[2].position
305 }
306
307 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
309 self.0.iter().skip(Self::HEADER)
310 }
311
312 pub fn with_center(mut self, cx: f32, cy: f32) -> Self {
315 let s = self.0.make_mut_slice();
316 s[0].position = cx;
317 s[1].position = cy;
318 self
319 }
320
321 pub fn with_radius(mut self, r: f32) -> Self {
324 self.0.make_mut_slice()[2].position = r;
325 self
326 }
327
328 pub fn center_or_default(&self, width: f32, height: f32) -> (f32, f32) {
332 debug_assert!(
333 self.center_x().is_nan() == self.center_y().is_nan(),
334 "center_x and center_y must both be NaN or both finite"
335 );
336 center_or_bbox(self.center_x(), self.center_y(), width, height, 1.0)
337 }
338
339 pub fn center_or_default_scaled(
345 &self,
346 width: f32,
347 height: f32,
348 scale_factor: f32,
349 ) -> (f32, f32) {
350 debug_assert!(
351 self.center_x().is_nan() == self.center_y().is_nan(),
352 "center_x and center_y must both be NaN or both finite"
353 );
354 center_or_bbox(self.center_x(), self.center_y(), width, height, scale_factor)
355 }
356
357 pub fn radius_or_default(&self, width: f32, height: f32) -> f32 {
362 let r = self.radius();
363 if r < 0.0 { 0.5 * (width * width + height * height).sqrt() } else { r }
364 }
365
366 pub fn radius_or_default_scaled(&self, width: f32, height: f32, scale_factor: f32) -> f32 {
372 let r = self.radius();
373 if r < 0.0 { 0.5 * (width * width + height * height).sqrt() } else { r * scale_factor }
374 }
375}
376
377impl PartialEq for RadialGradientBrush {
380 fn eq(&self, other: &Self) -> bool {
381 if self.0.len() != other.0.len() {
382 return false;
383 }
384 nan_eq(self.center_x(), other.center_x())
385 && nan_eq(self.center_y(), other.center_y())
386 && (self.radius() == other.radius() || (self.radius() < 0.0 && other.radius() < 0.0))
387 && self.0.iter().skip(Self::HEADER).eq(other.0.iter().skip(Self::HEADER))
388 }
389}
390
391#[derive(Clone, Debug)]
400#[repr(transparent)]
401pub struct ConicGradientBrush(SharedVector<GradientStop>);
402
403impl PartialEq for ConicGradientBrush {
406 fn eq(&self, other: &Self) -> bool {
407 if self.0.len() != other.0.len() {
408 return false;
409 }
410 self.0[0].position == other.0[0].position
412 && nan_eq(self.center_x(), other.center_x())
413 && nan_eq(self.center_y(), other.center_y())
414 && self.0.iter().skip(Self::HEADER).eq(other.0.iter().skip(Self::HEADER))
415 }
416}
417
418impl ConicGradientBrush {
419 const HEADER: usize = 3;
420
421 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
426 let stop_iter = stops.into_iter();
427 let mut v = SharedVector::with_capacity(Self::HEADER + stop_iter.size_hint().0);
428 v.push(GradientStop { color: Default::default(), position: angle });
430 v.push(GradientStop { color: Default::default(), position: f32::NAN });
431 v.push(GradientStop { color: Default::default(), position: f32::NAN });
432 v.extend(stop_iter);
433 let mut result = Self(v);
434 result.normalize_stops();
435 if angle.abs() > f32::EPSILON {
436 result.apply_rotation(angle);
437 }
438 result
439 }
440
441 fn normalize_stops(&mut self) {
443 let stops_slice = &self.0[Self::HEADER..];
445 let has_stop_at_0 = stops_slice.iter().any(|s| s.position.abs() < f32::EPSILON);
446 let has_stop_at_1 = stops_slice.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
447 let has_stops_outside = stops_slice.iter().any(|s| s.position < 0.0 || s.position > 1.0);
448 let is_empty = stops_slice.is_empty();
449
450 if has_stop_at_0 && has_stop_at_1 && !has_stops_outside && !is_empty {
452 return;
453 }
454
455 let mut stops: alloc::vec::Vec<_> = stops_slice.to_vec();
457
458 if !has_stop_at_0 {
460 let stop_below_0 = stops.iter().filter(|s| s.position < 0.0).max_by(|a, b| {
461 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
462 });
463 let stop_above_0 = stops.iter().filter(|s| s.position > 0.0).min_by(|a, b| {
464 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
465 });
466 if let (Some(below), Some(above)) = (stop_below_0, stop_above_0) {
467 let t = (0.0 - below.position) / (above.position - below.position);
468 let color_at_0 = Self::interpolate_color(&below.color, &above.color, t);
469 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
470 } else if let Some(above) = stop_above_0 {
471 stops.insert(0, GradientStop { position: 0.0, color: above.color });
472 } else if let Some(below) = stop_below_0 {
473 stops.insert(0, GradientStop { position: 0.0, color: below.color });
474 }
475 }
476
477 if !has_stop_at_1 {
479 let stop_below_1 = stops.iter().filter(|s| s.position < 1.0).max_by(|a, b| {
480 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
481 });
482 let stop_above_1 = stops.iter().filter(|s| s.position > 1.0).min_by(|a, b| {
483 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
484 });
485
486 if let (Some(below), Some(above)) = (stop_below_1, stop_above_1) {
487 let t = (1.0 - below.position) / (above.position - below.position);
488 let color_at_1 = Self::interpolate_color(&below.color, &above.color, t);
489 stops.push(GradientStop { position: 1.0, color: color_at_1 });
490 } else if let Some(below) = stop_below_1 {
491 stops.push(GradientStop { position: 1.0, color: below.color });
492 } else if let Some(above) = stop_above_1 {
493 stops.push(GradientStop { position: 1.0, color: above.color });
494 }
495 }
496
497 if has_stops_outside {
499 stops.retain(|s| 0.0 <= s.position && s.position <= 1.0);
500 }
501
502 if stops.is_empty() {
504 stops.push(GradientStop { position: 0.0, color: Color::default() });
505 stops.push(GradientStop { position: 1.0, color: Color::default() });
506 }
507
508 let angle = self.angle();
510 let cx = self.center_x();
511 let cy = self.center_y();
512 self.0 = SharedVector::with_capacity(stops.len() + Self::HEADER);
513 self.0.push(GradientStop { color: Default::default(), position: angle });
514 self.0.push(GradientStop { color: Default::default(), position: cx });
515 self.0.push(GradientStop { color: Default::default(), position: cy });
516 self.0.extend(stops);
517 }
518
519 fn apply_rotation(&mut self, from_angle: f32) {
523 let normalized_from_angle = (from_angle / 360.0) - (from_angle / 360.0).floor();
525
526 if normalized_from_angle.abs() < f32::EPSILON {
528 self.0.make_mut_slice()[0].position = from_angle;
529 return;
530 }
531
532 self.0.make_mut_slice()[0].position = from_angle;
534
535 let mut stops: alloc::vec::Vec<_> = self.0.iter().skip(Self::HEADER).copied().collect();
537
538 if let Some(first) = stops.first_mut()
540 && first.position.abs() < f32::EPSILON
541 {
542 first.position = f32::EPSILON;
543 }
544
545 stops = stops
547 .iter()
548 .map(|stop| {
549 let rotated_position =
552 num_traits::Euclid::rem_euclid(&(stop.position + normalized_from_angle), &1.0);
553 GradientStop { position: rotated_position, color: stop.color }
554 })
555 .collect();
556
557 for i in 0..stops.len() {
559 let j = (i + 1) % stops.len();
560 if (stops[i].position - stops[j].position).abs() < f32::EPSILON
561 && stops[i].color != stops[j].color
562 {
563 stops[i].position = (stops[i].position - f32::EPSILON).max(0.0);
564 stops[j].position = (stops[j].position + f32::EPSILON).min(1.0);
565 }
566 }
567
568 stops.sort_by(|a, b| {
570 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
571 });
572
573 let has_stop_at_0 = stops.iter().any(|s| s.position.abs() < f32::EPSILON);
575 if !has_stop_at_0 && let (Some(last), Some(first)) = (stops.last(), stops.first()) {
576 let gap = 1.0 - last.position + first.position;
577 let color_at_0 = if gap > f32::EPSILON {
578 let t = (1.0 - last.position) / gap;
579 Self::interpolate_color(&last.color, &first.color, t)
580 } else {
581 last.color
582 };
583 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
584 }
585
586 let has_stop_at_1 = stops.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
587 if !has_stop_at_1 && let Some(first) = stops.first() {
588 stops.push(GradientStop { position: 1.0, color: first.color });
589 }
590
591 let cx = self.center_x();
593 let cy = self.center_y();
594 self.0 = SharedVector::with_capacity(stops.len() + Self::HEADER);
595 self.0.push(GradientStop { color: Default::default(), position: from_angle });
596 self.0.push(GradientStop { color: Default::default(), position: cx });
597 self.0.push(GradientStop { color: Default::default(), position: cy });
598 self.0.extend(stops);
599 }
600
601 fn angle(&self) -> f32 {
603 self.0[0].position
604 }
605
606 #[inline]
607 fn center_x(&self) -> f32 {
608 self.0[1].position
609 }
610 #[inline]
611 fn center_y(&self) -> f32 {
612 self.0[2].position
613 }
614
615 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
618 self.0.iter().skip(Self::HEADER)
619 }
620
621 pub fn with_center(mut self, cx: f32, cy: f32) -> Self {
624 let s = self.0.make_mut_slice();
625 s[1].position = cx;
626 s[2].position = cy;
627 self
628 }
629
630 pub fn center_or_default(&self, width: f32, height: f32) -> (f32, f32) {
634 debug_assert!(
635 self.center_x().is_nan() == self.center_y().is_nan(),
636 "center_x and center_y must both be NaN or both finite"
637 );
638 center_or_bbox(self.center_x(), self.center_y(), width, height, 1.0)
639 }
640
641 pub fn center_or_default_scaled(
647 &self,
648 width: f32,
649 height: f32,
650 scale_factor: f32,
651 ) -> (f32, f32) {
652 debug_assert!(
653 self.center_x().is_nan() == self.center_y().is_nan(),
654 "center_x and center_y must both be NaN or both finite"
655 );
656 center_or_bbox(self.center_x(), self.center_y(), width, height, scale_factor)
657 }
658
659 fn interpolate_color(c1: &Color, c2: &Color, factor: f32) -> Color {
668 let argb1 = c1.to_argb_u8();
669 let argb2 = c2.to_argb_u8();
670
671 let a1 = argb1.alpha as f32 / 255.0;
673 let a2 = argb2.alpha as f32 / 255.0;
674 let r1 = argb1.red as f32 * a1;
675 let g1 = argb1.green as f32 * a1;
676 let b1 = argb1.blue as f32 * a1;
677 let r2 = argb2.red as f32 * a2;
678 let g2 = argb2.green as f32 * a2;
679 let b2 = argb2.blue as f32 * a2;
680
681 let alpha = (1.0 - factor) * a1 + factor * a2;
683 let red = (1.0 - factor) * r1 + factor * r2;
684 let green = (1.0 - factor) * g1 + factor * g2;
685 let blue = (1.0 - factor) * b1 + factor * b2;
686
687 if alpha > 0.0 {
689 Color::from_argb_u8(
690 (alpha * 255.0) as u8,
691 (red / alpha).min(255.0) as u8,
692 (green / alpha).min(255.0) as u8,
693 (blue / alpha).min(255.0) as u8,
694 )
695 } else {
696 Color::from_argb_u8(0, 0, 0, 0)
697 }
698 }
699}
700
701#[cfg(feature = "ffi")]
703#[unsafe(no_mangle)]
704pub extern "C" fn slint_conic_gradient_normalize_stops(gradient: &mut ConicGradientBrush) {
705 gradient.normalize_stops();
706}
707
708#[cfg(feature = "ffi")]
710#[unsafe(no_mangle)]
711pub extern "C" fn slint_conic_gradient_apply_rotation(
712 gradient: &mut ConicGradientBrush,
713 angle_degrees: f32,
714) {
715 gradient.apply_rotation(angle_degrees);
716}
717
718#[cfg(feature = "ffi")]
720#[unsafe(no_mangle)]
721pub extern "C" fn slint_brush_compare_equal(brush1: &Brush, brush2: &Brush) -> bool {
722 brush1.eq(brush2)
723}
724
725#[repr(C)]
728#[derive(Copy, Clone, Debug, PartialEq)]
729pub struct GradientStop {
730 pub color: Color,
732 pub position: f32,
734}
735
736pub fn line_for_angle(angle: f32, size: Size2D<f32>) -> (Point2D<f32>, Point2D<f32>) {
738 let angle = (angle + 90.).to_radians();
739 let (s, c) = angle.sin_cos();
740
741 let (a, b) = if s.abs() < f32::EPSILON {
742 let y = size.height / 2.;
743 return if c < 0. {
744 (Point2D::new(0., y), Point2D::new(size.width, y))
745 } else {
746 (Point2D::new(size.width, y), Point2D::new(0., y))
747 };
748 } else if c * s < 0. {
749 let x = (s * size.width + c * size.height) * s / 2.;
751 let y = -c * x / s + size.height;
752 (Point2D::new(x, y), Point2D::new(size.width - x, size.height - y))
753 } else {
754 let x = (s * size.width - c * size.height) * s / 2.;
756 let y = -c * x / s;
757 (Point2D::new(size.width - x, size.height - y), Point2D::new(x, y))
758 };
759
760 if s > 0. { (a, b) } else { (b, a) }
761}
762
763impl InterpolatedPropertyValue for Brush {
764 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
765 match (self, target_value) {
766 (Brush::SolidColor(source_col), Brush::SolidColor(target_col)) => {
767 Brush::SolidColor(source_col.interpolate(target_col, t))
768 }
769 (Brush::SolidColor(col), Brush::LinearGradient(grad)) => {
770 let mut new_grad = grad.clone();
771 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
772 x.color = col.interpolate(&x.color, t);
773 }
774 Brush::LinearGradient(new_grad)
775 }
776 (a @ Brush::LinearGradient(_), b @ Brush::SolidColor(_)) => {
777 Self::interpolate(b, a, 1. - t)
778 }
779 (Brush::LinearGradient(lhs), Brush::LinearGradient(rhs)) => {
780 if lhs.0.len() < rhs.0.len() {
781 Self::interpolate(target_value, self, 1. - t)
782 } else {
783 let mut new_grad = lhs.clone();
784 let mut iter = new_grad.0.make_mut_slice().iter_mut();
785 {
786 let angle = &mut iter.next().unwrap().position;
787 *angle = angle.interpolate(&rhs.angle(), t);
788 }
789 for s2 in rhs.stops() {
790 let s1 = iter.next().unwrap();
791 s1.color = s1.color.interpolate(&s2.color, t);
792 s1.position = s1.position.interpolate(&s2.position, t);
793 }
794 for x in iter {
795 x.position = x.position.interpolate(&1.0, t);
796 }
797 Brush::LinearGradient(new_grad)
798 }
799 }
800 (Brush::SolidColor(col), Brush::RadialGradient(grad)) => {
801 let mut new_grad = grad.clone();
802 for x in new_grad.0.make_mut_slice().iter_mut().skip(RadialGradientBrush::HEADER) {
803 x.color = col.interpolate(&x.color, t);
804 }
805 Brush::RadialGradient(new_grad)
806 }
807 (a @ Brush::RadialGradient(_), b @ Brush::SolidColor(_)) => {
808 Self::interpolate(b, a, 1. - t)
809 }
810 (Brush::RadialGradient(lhs), Brush::RadialGradient(rhs)) => {
811 if lhs.0.len() < rhs.0.len() {
812 Self::interpolate(target_value, self, 1. - t)
813 } else {
814 let mut new_grad = lhs.clone();
815 {
816 let s = new_grad.0.make_mut_slice();
817 if !lhs.center_x().is_nan() && !rhs.center_x().is_nan() {
820 s[0].position = lhs.center_x().interpolate(&rhs.center_x(), t);
821 s[1].position = lhs.center_y().interpolate(&rhs.center_y(), t);
822 } else if t >= 1.0 {
823 s[0].position = rhs.center_x();
824 s[1].position = rhs.center_y();
825 }
826 if lhs.radius() >= 0.0 && rhs.radius() >= 0.0 {
828 s[2].position = lhs.radius().interpolate(&rhs.radius(), t);
829 } else if t >= 1.0 {
830 s[2].position = rhs.radius();
831 }
832 let mut rhs_stops = rhs.stops();
833 let mut iter = s.iter_mut().skip(RadialGradientBrush::HEADER);
834 let mut last_color = Color::default();
835 for s2 in &mut rhs_stops {
836 let s1 = iter.next().unwrap();
837 last_color = s2.color;
838 s1.color = s1.color.interpolate(&s2.color, t);
839 s1.position = s1.position.interpolate(&s2.position, t);
840 }
841 for x in iter {
842 x.position = x.position.interpolate(&1.0, t);
843 x.color = x.color.interpolate(&last_color, t);
844 }
845 }
846 Brush::RadialGradient(new_grad)
847 }
848 }
849 (Brush::SolidColor(col), Brush::ConicGradient(grad)) => {
850 let mut new_grad = grad.clone();
851 for x in new_grad.0.make_mut_slice().iter_mut().skip(ConicGradientBrush::HEADER) {
852 x.color = col.interpolate(&x.color, t);
853 }
854 Brush::ConicGradient(new_grad)
855 }
856 (a @ Brush::ConicGradient(_), b @ Brush::SolidColor(_)) => {
857 Self::interpolate(b, a, 1. - t)
858 }
859 (Brush::ConicGradient(lhs), Brush::ConicGradient(rhs)) => {
860 if lhs.0.len() < rhs.0.len() {
861 Self::interpolate(target_value, self, 1. - t)
862 } else {
863 let mut new_grad = lhs.clone();
864 {
865 let s = new_grad.0.make_mut_slice();
866 s[0].position = lhs.angle().interpolate(&rhs.angle(), t);
868 if !lhs.center_x().is_nan() && !rhs.center_x().is_nan() {
871 s[1].position = lhs.center_x().interpolate(&rhs.center_x(), t);
872 s[2].position = lhs.center_y().interpolate(&rhs.center_y(), t);
873 } else if t >= 1.0 {
874 s[1].position = rhs.center_x();
875 s[2].position = rhs.center_y();
876 }
877 let mut rhs_stops = rhs.stops();
878 let mut iter = s.iter_mut().skip(ConicGradientBrush::HEADER);
879 for s2 in &mut rhs_stops {
880 let s1 = iter.next().unwrap();
881 s1.color = s1.color.interpolate(&s2.color, t);
882 s1.position = s1.position.interpolate(&s2.position, t);
883 }
884 for x in iter {
885 x.position = x.position.interpolate(&1.0, t);
886 }
887 }
888 Brush::ConicGradient(new_grad)
889 }
890 }
891 (a @ Brush::LinearGradient(_), b @ Brush::RadialGradient(_))
892 | (a @ Brush::RadialGradient(_), b @ Brush::LinearGradient(_))
893 | (a @ Brush::LinearGradient(_), b @ Brush::ConicGradient(_))
894 | (a @ Brush::ConicGradient(_), b @ Brush::LinearGradient(_))
895 | (a @ Brush::RadialGradient(_), b @ Brush::ConicGradient(_))
896 | (a @ Brush::ConicGradient(_), b @ Brush::RadialGradient(_)) => {
897 let color = Color::interpolate(&b.color(), &a.color(), t);
899 if t < 0.5 {
900 Self::interpolate(a, &Brush::SolidColor(color), t * 2.)
901 } else {
902 Self::interpolate(&Brush::SolidColor(color), b, (t - 0.5) * 2.)
903 }
904 }
905 }
906 }
907}
908
909#[test]
910#[allow(clippy::float_cmp)] fn test_linear_gradient_encoding() {
912 let stops: SharedVector<GradientStop> = [
913 GradientStop { position: 0.0, color: Color::from_argb_u8(255, 255, 0, 0) },
914 GradientStop { position: 0.5, color: Color::from_argb_u8(255, 0, 255, 0) },
915 GradientStop { position: 1.0, color: Color::from_argb_u8(255, 0, 0, 255) },
916 ]
917 .into();
918 let grad = LinearGradientBrush::new(256., stops.clone());
919 assert_eq!(grad.angle(), 256.);
920 assert!(grad.stops().eq(stops.iter()));
921}
922
923#[test]
924fn test_conic_gradient_basic() {
925 let grad = ConicGradientBrush::new(
927 0.0,
928 [
929 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
930 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
931 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
932 ],
933 );
934 assert_eq!(grad.angle(), 0.0);
935 assert_eq!(grad.stops().count(), 3);
936}
937
938#[test]
939fn test_conic_gradient_with_rotation() {
940 let grad = ConicGradientBrush::new(
942 90.0,
943 [
944 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
945 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
946 ],
947 );
948 assert_eq!(grad.angle(), 90.0);
949 assert!(grad.stops().count() >= 2);
951}
952
953#[test]
954fn test_conic_gradient_negative_angle() {
955 let grad = ConicGradientBrush::new(
957 -90.0,
958 [GradientStop { position: 0.5, color: Color::from_rgb_u8(255, 0, 0) }],
959 );
960 assert_eq!(grad.angle(), -90.0); assert!(grad.stops().count() >= 2); }
963
964#[test]
965fn test_conic_gradient_stops_outside_range() {
966 let grad = ConicGradientBrush::new(
968 0.0,
969 [
970 GradientStop { position: -0.2, color: Color::from_rgb_u8(255, 0, 0) },
971 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
972 GradientStop { position: 1.2, color: Color::from_rgb_u8(0, 0, 255) },
973 ],
974 );
975 for stop in grad.stops() {
977 assert!(stop.position >= 0.0 && stop.position <= 1.0);
978 }
979}
980
981#[test]
982fn test_conic_gradient_all_stops_below_zero() {
983 let grad = ConicGradientBrush::new(
985 0.0,
986 [
987 GradientStop { position: -0.5, color: Color::from_rgb_u8(255, 0, 0) },
988 GradientStop { position: -0.3, color: Color::from_rgb_u8(0, 255, 0) },
989 ],
990 );
991 assert!(grad.stops().count() >= 2);
993 let first = grad.stops().next().unwrap();
995 assert!(first.position >= 0.0 && first.position < 0.1);
996}
997
998#[test]
999fn test_conic_gradient_all_stops_above_one() {
1000 let grad = ConicGradientBrush::new(
1002 0.0,
1003 [
1004 GradientStop { position: 1.2, color: Color::from_rgb_u8(255, 0, 0) },
1005 GradientStop { position: 1.5, color: Color::from_rgb_u8(0, 255, 0) },
1006 ],
1007 );
1008 assert!(grad.stops().count() >= 2);
1010 let last = grad.stops().last().unwrap();
1012 assert!(last.position > 0.9 && last.position <= 1.0);
1013}
1014
1015#[test]
1016fn test_conic_gradient_empty() {
1017 let grad = ConicGradientBrush::new(0.0, []);
1019 assert_eq!(grad.stops().count(), 2);
1021}
1022
1023#[test]
1024fn test_radial_gradient_preserves_center_on_brighter() {
1025 let grad = RadialGradientBrush::new_circle([
1026 GradientStop { position: 0.0, color: Color::from_rgb_u8(200, 100, 50) },
1027 GradientStop { position: 1.0, color: Color::from_rgb_u8(50, 200, 100) },
1028 ])
1029 .with_center(10.0, 20.0)
1030 .with_radius(30.0);
1031 let brighter = Brush::RadialGradient(grad.clone()).brighter(0.5);
1032 if let Brush::RadialGradient(b) = brighter {
1033 assert_eq!(b.center_x(), 10.0);
1034 assert_eq!(b.center_y(), 20.0);
1035 assert_eq!(b.radius(), 30.0);
1036 } else {
1037 panic!("Expected RadialGradient");
1038 }
1039}
1040
1041#[test]
1042fn test_radial_gradient_default_center() {
1043 let grad = RadialGradientBrush::new_circle([]);
1044 assert!(grad.center_x().is_nan());
1045 assert!(grad.center_y().is_nan());
1046 assert!(grad.radius() < 0.0);
1047 assert_eq!(grad.center_or_default(100.0, 80.0), (50.0, 40.0));
1048 assert!((grad.radius_or_default(60.0, 80.0) - 50.0).abs() < 0.01);
1049}
1050
1051#[test]
1052fn test_radial_gradient_scaled_explicit_values() {
1053 let grad = RadialGradientBrush::new_circle([]).with_center(10.0, 20.0).with_radius(30.0);
1054
1055 assert_eq!(grad.center_or_default_scaled(200.0, 160.0, 2.0), (20.0, 40.0));
1056 assert_eq!(grad.radius_or_default_scaled(200.0, 160.0, 2.0), 60.0);
1057}
1058
1059#[test]
1060fn test_radial_gradient_scaled_defaults_use_physical_frame() {
1061 let grad = RadialGradientBrush::new_circle([]);
1062
1063 assert_eq!(grad.center_or_default_scaled(200.0, 160.0, 2.0), (100.0, 80.0));
1064 assert!((grad.radius_or_default_scaled(120.0, 160.0, 2.0) - 100.0).abs() < 0.01);
1065}
1066
1067#[test]
1068fn test_radial_gradient_interpolation_reaches_explicit_metadata() {
1069 let source = Brush::RadialGradient(RadialGradientBrush::new_circle([
1070 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1071 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1072 ]));
1073 let target_grad = RadialGradientBrush::new_circle([
1074 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1075 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1076 ])
1077 .with_center(10.0, 20.0)
1078 .with_radius(30.0);
1079 let target = Brush::RadialGradient(target_grad.clone());
1080
1081 if let Brush::RadialGradient(result) = source.interpolate(&target, 1.0) {
1082 assert_eq!(result.center_x(), target_grad.center_x());
1083 assert_eq!(result.center_y(), target_grad.center_y());
1084 assert_eq!(result.radius(), target_grad.radius());
1085 } else {
1086 panic!("Expected RadialGradient");
1087 }
1088}
1089
1090#[test]
1091fn test_radial_gradient_interpolation_reaches_default_metadata() {
1092 let source_grad = RadialGradientBrush::new_circle([
1093 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1094 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1095 ])
1096 .with_center(10.0, 20.0)
1097 .with_radius(30.0);
1098 let source = Brush::RadialGradient(source_grad);
1099 let target = Brush::RadialGradient(RadialGradientBrush::new_circle([
1100 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1101 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1102 ]));
1103
1104 if let Brush::RadialGradient(result) = source.interpolate(&target, 1.0) {
1105 assert!(result.center_x().is_nan());
1106 assert!(result.center_y().is_nan());
1107 assert!(result.radius() < 0.0);
1108 } else {
1109 panic!("Expected RadialGradient");
1110 }
1111}
1112
1113#[test]
1114fn test_conic_gradient_interpolation_reaches_explicit_center() {
1115 let source = Brush::ConicGradient(ConicGradientBrush::new(
1116 0.0,
1117 [
1118 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1119 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1120 ],
1121 ));
1122 let target_grad = ConicGradientBrush::new(
1123 0.0,
1124 [
1125 GradientStop { position: 0.0, color: Color::from_rgb_u8(0, 0, 0) },
1126 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 255, 255) },
1127 ],
1128 )
1129 .with_center(40.0, 50.0);
1130 let target = Brush::ConicGradient(target_grad.clone());
1131
1132 if let Brush::ConicGradient(result) = source.interpolate(&target, 1.0) {
1133 assert_eq!(result.center_x(), target_grad.center_x());
1134 assert_eq!(result.center_y(), target_grad.center_y());
1135 } else {
1136 panic!("Expected ConicGradient");
1137 }
1138}