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 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
112 GradientStop { color: s.color.brighter(factor), position: s.position }
113 })))
114 }
115 Brush::ConicGradient(g) => {
116 let mut new_grad = g.clone();
117 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
119 x.color = x.color.brighter(factor);
120 }
121 Brush::ConicGradient(new_grad)
122 }
123 }
124 }
125
126 #[must_use]
130 pub fn darker(&self, factor: f32) -> Self {
131 match self {
132 Brush::SolidColor(c) => Brush::SolidColor(c.darker(factor)),
133 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
134 g.angle(),
135 g.stops()
136 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
137 )),
138 Brush::RadialGradient(g) => Brush::RadialGradient(RadialGradientBrush::new_circle(
139 g.stops()
140 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
141 )),
142 Brush::ConicGradient(g) => {
143 let mut new_grad = g.clone();
144 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
146 x.color = x.color.darker(factor);
147 }
148 Brush::ConicGradient(new_grad)
149 }
150 }
151 }
152
153 #[must_use]
159 pub fn transparentize(&self, amount: f32) -> Self {
160 match self {
161 Brush::SolidColor(c) => Brush::SolidColor(c.transparentize(amount)),
162 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
163 g.angle(),
164 g.stops().map(|s| GradientStop {
165 color: s.color.transparentize(amount),
166 position: s.position,
167 }),
168 )),
169 Brush::RadialGradient(g) => {
170 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
171 GradientStop { color: s.color.transparentize(amount), position: s.position }
172 })))
173 }
174 Brush::ConicGradient(g) => {
175 let mut new_grad = g.clone();
176 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
178 x.color = x.color.transparentize(amount);
179 }
180 Brush::ConicGradient(new_grad)
181 }
182 }
183 }
184
185 #[must_use]
188 pub fn with_alpha(&self, alpha: f32) -> Self {
189 match self {
190 Brush::SolidColor(c) => Brush::SolidColor(c.with_alpha(alpha)),
191 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
192 g.angle(),
193 g.stops().map(|s| GradientStop {
194 color: s.color.with_alpha(alpha),
195 position: s.position,
196 }),
197 )),
198 Brush::RadialGradient(g) => {
199 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
200 GradientStop { color: s.color.with_alpha(alpha), position: s.position }
201 })))
202 }
203 Brush::ConicGradient(g) => {
204 let mut new_grad = g.clone();
205 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
207 x.color = x.color.with_alpha(alpha);
208 }
209 Brush::ConicGradient(new_grad)
210 }
211 }
212 }
213}
214
215#[derive(Clone, PartialEq, Debug)]
219#[repr(transparent)]
220pub struct LinearGradientBrush(SharedVector<GradientStop>);
221
222impl LinearGradientBrush {
223 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
228 let stop_iter = stops.into_iter();
229 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
230 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
232 encoded_angle_and_stops.extend(stop_iter);
233 Self(encoded_angle_and_stops)
234 }
235 pub fn angle(&self) -> f32 {
237 self.0[0].position
238 }
239 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
242 self.0.iter().skip(1)
244 }
245}
246
247#[derive(Clone, PartialEq, Debug)]
249#[repr(transparent)]
250pub struct RadialGradientBrush(SharedVector<GradientStop>);
251
252impl RadialGradientBrush {
253 pub fn new_circle(stops: impl IntoIterator<Item = GradientStop>) -> Self {
256 Self(stops.into_iter().collect())
257 }
258 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
260 self.0.iter()
261 }
262}
263
264#[derive(Clone, PartialEq, Debug)]
267#[repr(transparent)]
268pub struct ConicGradientBrush(SharedVector<GradientStop>);
269
270impl ConicGradientBrush {
271 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
276 let stop_iter = stops.into_iter();
277 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
278 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
280 encoded_angle_and_stops.extend(stop_iter);
281 let mut result = Self(encoded_angle_and_stops);
282 result.normalize_stops();
283 if angle.abs() > f32::EPSILON {
284 result.apply_rotation(angle);
285 }
286 result
287 }
288
289 fn normalize_stops(&mut self) {
291 let stops_slice = &self.0[1..];
293 let has_stop_at_0 = stops_slice.iter().any(|s| s.position.abs() < f32::EPSILON);
294 let has_stop_at_1 = stops_slice.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
295 let has_stops_outside = stops_slice.iter().any(|s| s.position < 0.0 || s.position > 1.0);
296 let is_empty = stops_slice.is_empty();
297
298 if has_stop_at_0 && has_stop_at_1 && !has_stops_outside && !is_empty {
300 return;
301 }
302
303 let mut stops: alloc::vec::Vec<_> = stops_slice.to_vec();
305
306 if !has_stop_at_0 {
308 let stop_below_0 = stops.iter().filter(|s| s.position < 0.0).max_by(|a, b| {
309 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
310 });
311 let stop_above_0 = stops.iter().filter(|s| s.position > 0.0).min_by(|a, b| {
312 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
313 });
314 if let (Some(below), Some(above)) = (stop_below_0, stop_above_0) {
315 let t = (0.0 - below.position) / (above.position - below.position);
316 let color_at_0 = Self::interpolate_color(&below.color, &above.color, t);
317 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
318 } else if let Some(above) = stop_above_0 {
319 stops.insert(0, GradientStop { position: 0.0, color: above.color });
320 } else if let Some(below) = stop_below_0 {
321 stops.insert(0, GradientStop { position: 0.0, color: below.color });
322 }
323 }
324
325 if !has_stop_at_1 {
327 let stop_below_1 = stops.iter().filter(|s| s.position < 1.0).max_by(|a, b| {
328 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
329 });
330 let stop_above_1 = stops.iter().filter(|s| s.position > 1.0).min_by(|a, b| {
331 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
332 });
333
334 if let (Some(below), Some(above)) = (stop_below_1, stop_above_1) {
335 let t = (1.0 - below.position) / (above.position - below.position);
336 let color_at_1 = Self::interpolate_color(&below.color, &above.color, t);
337 stops.push(GradientStop { position: 1.0, color: color_at_1 });
338 } else if let Some(below) = stop_below_1 {
339 stops.push(GradientStop { position: 1.0, color: below.color });
340 } else if let Some(above) = stop_above_1 {
341 stops.push(GradientStop { position: 1.0, color: above.color });
342 }
343 }
344
345 if has_stops_outside {
347 stops.retain(|s| 0.0 <= s.position && s.position <= 1.0);
348 }
349
350 if stops.is_empty() {
352 stops.push(GradientStop { position: 0.0, color: Color::default() });
353 stops.push(GradientStop { position: 1.0, color: Color::default() });
354 }
355
356 let angle = self.angle();
358 self.0 = SharedVector::with_capacity(stops.len() + 1);
359 self.0.push(GradientStop { color: Default::default(), position: angle });
360 self.0.extend(stops);
361 }
362
363 fn apply_rotation(&mut self, from_angle: f32) {
367 let normalized_from_angle = (from_angle / 360.0) - (from_angle / 360.0).floor();
369
370 if normalized_from_angle.abs() < f32::EPSILON {
372 self.0.make_mut_slice()[0].position = from_angle;
373 return;
374 }
375
376 self.0.make_mut_slice()[0].position = from_angle;
378
379 let mut stops: alloc::vec::Vec<_> = self.0.iter().skip(1).copied().collect();
381
382 if let Some(first) = stops.first_mut()
384 && first.position.abs() < f32::EPSILON
385 {
386 first.position = f32::EPSILON;
387 }
388
389 stops = stops
391 .iter()
392 .map(|stop| {
393 let rotated_position =
396 num_traits::Euclid::rem_euclid(&(stop.position + normalized_from_angle), &1.0);
397 GradientStop { position: rotated_position, color: stop.color }
398 })
399 .collect();
400
401 for i in 0..stops.len() {
403 let j = (i + 1) % stops.len();
404 if (stops[i].position - stops[j].position).abs() < f32::EPSILON
405 && stops[i].color != stops[j].color
406 {
407 stops[i].position = (stops[i].position - f32::EPSILON).max(0.0);
408 stops[j].position = (stops[j].position + f32::EPSILON).min(1.0);
409 }
410 }
411
412 stops.sort_by(|a, b| {
414 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
415 });
416
417 let has_stop_at_0 = stops.iter().any(|s| s.position.abs() < f32::EPSILON);
419 if !has_stop_at_0 && let (Some(last), Some(first)) = (stops.last(), stops.first()) {
420 let gap = 1.0 - last.position + first.position;
421 let color_at_0 = if gap > f32::EPSILON {
422 let t = (1.0 - last.position) / gap;
423 Self::interpolate_color(&last.color, &first.color, t)
424 } else {
425 last.color
426 };
427 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
428 }
429
430 let has_stop_at_1 = stops.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
431 if !has_stop_at_1 && let Some(first) = stops.first() {
432 stops.push(GradientStop { position: 1.0, color: first.color });
433 }
434
435 self.0 = SharedVector::with_capacity(stops.len() + 1);
437 self.0.push(GradientStop { color: Default::default(), position: from_angle });
438 self.0.extend(stops);
439 }
440
441 fn angle(&self) -> f32 {
443 self.0[0].position
444 }
445
446 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
449 self.0.iter().skip(1)
451 }
452
453 fn interpolate_color(c1: &Color, c2: &Color, factor: f32) -> Color {
462 let argb1 = c1.to_argb_u8();
463 let argb2 = c2.to_argb_u8();
464
465 let a1 = argb1.alpha as f32 / 255.0;
467 let a2 = argb2.alpha as f32 / 255.0;
468 let r1 = argb1.red as f32 * a1;
469 let g1 = argb1.green as f32 * a1;
470 let b1 = argb1.blue as f32 * a1;
471 let r2 = argb2.red as f32 * a2;
472 let g2 = argb2.green as f32 * a2;
473 let b2 = argb2.blue as f32 * a2;
474
475 let alpha = (1.0 - factor) * a1 + factor * a2;
477 let red = (1.0 - factor) * r1 + factor * r2;
478 let green = (1.0 - factor) * g1 + factor * g2;
479 let blue = (1.0 - factor) * b1 + factor * b2;
480
481 if alpha > 0.0 {
483 Color::from_argb_u8(
484 (alpha * 255.0) as u8,
485 (red / alpha).min(255.0) as u8,
486 (green / alpha).min(255.0) as u8,
487 (blue / alpha).min(255.0) as u8,
488 )
489 } else {
490 Color::from_argb_u8(0, 0, 0, 0)
491 }
492 }
493}
494
495#[cfg(feature = "ffi")]
497#[unsafe(no_mangle)]
498pub extern "C" fn slint_conic_gradient_normalize_stops(gradient: &mut ConicGradientBrush) {
499 gradient.normalize_stops();
500}
501
502#[cfg(feature = "ffi")]
504#[unsafe(no_mangle)]
505pub extern "C" fn slint_conic_gradient_apply_rotation(
506 gradient: &mut ConicGradientBrush,
507 angle_degrees: f32,
508) {
509 gradient.apply_rotation(angle_degrees);
510}
511
512#[repr(C)]
515#[derive(Copy, Clone, Debug, PartialEq)]
516pub struct GradientStop {
517 pub color: Color,
519 pub position: f32,
521}
522
523pub fn line_for_angle(angle: f32, size: Size2D<f32>) -> (Point2D<f32>, Point2D<f32>) {
525 let angle = (angle + 90.).to_radians();
526 let (s, c) = angle.sin_cos();
527
528 let (a, b) = if s.abs() < f32::EPSILON {
529 let y = size.height / 2.;
530 return if c < 0. {
531 (Point2D::new(0., y), Point2D::new(size.width, y))
532 } else {
533 (Point2D::new(size.width, y), Point2D::new(0., y))
534 };
535 } else if c * s < 0. {
536 let x = (s * size.width + c * size.height) * s / 2.;
538 let y = -c * x / s + size.height;
539 (Point2D::new(x, y), Point2D::new(size.width - x, size.height - y))
540 } else {
541 let x = (s * size.width - c * size.height) * s / 2.;
543 let y = -c * x / s;
544 (Point2D::new(size.width - x, size.height - y), Point2D::new(x, y))
545 };
546
547 if s > 0. { (a, b) } else { (b, a) }
548}
549
550impl InterpolatedPropertyValue for Brush {
551 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
552 match (self, target_value) {
553 (Brush::SolidColor(source_col), Brush::SolidColor(target_col)) => {
554 Brush::SolidColor(source_col.interpolate(target_col, t))
555 }
556 (Brush::SolidColor(col), Brush::LinearGradient(grad)) => {
557 let mut new_grad = grad.clone();
558 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
559 x.color = col.interpolate(&x.color, t);
560 }
561 Brush::LinearGradient(new_grad)
562 }
563 (a @ Brush::LinearGradient(_), b @ Brush::SolidColor(_)) => {
564 Self::interpolate(b, a, 1. - t)
565 }
566 (Brush::LinearGradient(lhs), Brush::LinearGradient(rhs)) => {
567 if lhs.0.len() < rhs.0.len() {
568 Self::interpolate(target_value, self, 1. - t)
569 } else {
570 let mut new_grad = lhs.clone();
571 let mut iter = new_grad.0.make_mut_slice().iter_mut();
572 {
573 let angle = &mut iter.next().unwrap().position;
574 *angle = angle.interpolate(&rhs.angle(), t);
575 }
576 for s2 in rhs.stops() {
577 let s1 = iter.next().unwrap();
578 s1.color = s1.color.interpolate(&s2.color, t);
579 s1.position = s1.position.interpolate(&s2.position, t);
580 }
581 for x in iter {
582 x.position = x.position.interpolate(&1.0, t);
583 }
584 Brush::LinearGradient(new_grad)
585 }
586 }
587 (Brush::SolidColor(col), Brush::RadialGradient(grad)) => {
588 let mut new_grad = grad.clone();
589 for x in new_grad.0.make_mut_slice().iter_mut() {
590 x.color = col.interpolate(&x.color, t);
591 }
592 Brush::RadialGradient(new_grad)
593 }
594 (a @ Brush::RadialGradient(_), b @ Brush::SolidColor(_)) => {
595 Self::interpolate(b, a, 1. - t)
596 }
597 (Brush::RadialGradient(lhs), Brush::RadialGradient(rhs)) => {
598 if lhs.0.len() < rhs.0.len() {
599 Self::interpolate(target_value, self, 1. - t)
600 } else {
601 let mut new_grad = lhs.clone();
602 let mut iter = new_grad.0.make_mut_slice().iter_mut();
603 let mut last_color = Color::default();
604 for s2 in rhs.stops() {
605 let s1 = iter.next().unwrap();
606 last_color = s2.color;
607 s1.color = s1.color.interpolate(&s2.color, t);
608 s1.position = s1.position.interpolate(&s2.position, t);
609 }
610 for x in iter {
611 x.position = x.position.interpolate(&1.0, t);
612 x.color = x.color.interpolate(&last_color, t);
613 }
614 Brush::RadialGradient(new_grad)
615 }
616 }
617 (Brush::SolidColor(col), Brush::ConicGradient(grad)) => {
618 let mut new_grad = grad.clone();
619 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
620 x.color = col.interpolate(&x.color, t);
621 }
622 Brush::ConicGradient(new_grad)
623 }
624 (a @ Brush::ConicGradient(_), b @ Brush::SolidColor(_)) => {
625 Self::interpolate(b, a, 1. - t)
626 }
627 (Brush::ConicGradient(lhs), Brush::ConicGradient(rhs)) => {
628 if lhs.0.len() < rhs.0.len() {
629 Self::interpolate(target_value, self, 1. - t)
630 } else {
631 let mut new_grad = lhs.clone();
632 let mut iter = new_grad.0.make_mut_slice().iter_mut();
633 {
634 let angle = &mut iter.next().unwrap().position;
635 *angle = angle.interpolate(&rhs.angle(), t);
636 }
637 for s2 in rhs.stops() {
638 let s1 = iter.next().unwrap();
639 s1.color = s1.color.interpolate(&s2.color, t);
640 s1.position = s1.position.interpolate(&s2.position, t);
641 }
642 for x in iter {
643 x.position = x.position.interpolate(&1.0, t);
644 }
645 Brush::ConicGradient(new_grad)
646 }
647 }
648 (a @ Brush::LinearGradient(_), b @ Brush::RadialGradient(_))
649 | (a @ Brush::RadialGradient(_), b @ Brush::LinearGradient(_))
650 | (a @ Brush::LinearGradient(_), b @ Brush::ConicGradient(_))
651 | (a @ Brush::ConicGradient(_), b @ Brush::LinearGradient(_))
652 | (a @ Brush::RadialGradient(_), b @ Brush::ConicGradient(_))
653 | (a @ Brush::ConicGradient(_), b @ Brush::RadialGradient(_)) => {
654 let color = Color::interpolate(&b.color(), &a.color(), t);
656 if t < 0.5 {
657 Self::interpolate(a, &Brush::SolidColor(color), t * 2.)
658 } else {
659 Self::interpolate(&Brush::SolidColor(color), b, (t - 0.5) * 2.)
660 }
661 }
662 }
663 }
664}
665
666#[test]
667#[allow(clippy::float_cmp)] fn test_linear_gradient_encoding() {
669 let stops: SharedVector<GradientStop> = [
670 GradientStop { position: 0.0, color: Color::from_argb_u8(255, 255, 0, 0) },
671 GradientStop { position: 0.5, color: Color::from_argb_u8(255, 0, 255, 0) },
672 GradientStop { position: 1.0, color: Color::from_argb_u8(255, 0, 0, 255) },
673 ]
674 .into();
675 let grad = LinearGradientBrush::new(256., stops.clone());
676 assert_eq!(grad.angle(), 256.);
677 assert!(grad.stops().eq(stops.iter()));
678}
679
680#[test]
681fn test_conic_gradient_basic() {
682 let grad = ConicGradientBrush::new(
684 0.0,
685 [
686 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
687 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
688 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
689 ],
690 );
691 assert_eq!(grad.angle(), 0.0);
692 assert_eq!(grad.stops().count(), 3);
693}
694
695#[test]
696fn test_conic_gradient_with_rotation() {
697 let grad = ConicGradientBrush::new(
699 90.0,
700 [
701 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
702 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
703 ],
704 );
705 assert_eq!(grad.angle(), 90.0);
706 assert!(grad.stops().count() >= 2);
708}
709
710#[test]
711fn test_conic_gradient_negative_angle() {
712 let grad = ConicGradientBrush::new(
714 -90.0,
715 [GradientStop { position: 0.5, color: Color::from_rgb_u8(255, 0, 0) }],
716 );
717 assert_eq!(grad.angle(), -90.0); assert!(grad.stops().count() >= 2); }
720
721#[test]
722fn test_conic_gradient_stops_outside_range() {
723 let grad = ConicGradientBrush::new(
725 0.0,
726 [
727 GradientStop { position: -0.2, color: Color::from_rgb_u8(255, 0, 0) },
728 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
729 GradientStop { position: 1.2, color: Color::from_rgb_u8(0, 0, 255) },
730 ],
731 );
732 for stop in grad.stops() {
734 assert!(stop.position >= 0.0 && stop.position <= 1.0);
735 }
736}
737
738#[test]
739fn test_conic_gradient_all_stops_below_zero() {
740 let grad = ConicGradientBrush::new(
742 0.0,
743 [
744 GradientStop { position: -0.5, color: Color::from_rgb_u8(255, 0, 0) },
745 GradientStop { position: -0.3, color: Color::from_rgb_u8(0, 255, 0) },
746 ],
747 );
748 assert!(grad.stops().count() >= 2);
750 let first = grad.stops().next().unwrap();
752 assert!(first.position >= 0.0 && first.position < 0.1);
753}
754
755#[test]
756fn test_conic_gradient_all_stops_above_one() {
757 let grad = ConicGradientBrush::new(
759 0.0,
760 [
761 GradientStop { position: 1.2, color: Color::from_rgb_u8(255, 0, 0) },
762 GradientStop { position: 1.5, color: Color::from_rgb_u8(0, 255, 0) },
763 ],
764 );
765 assert!(grad.stops().count() >= 2);
767 let last = grad.stops().last().unwrap();
769 assert!(last.position > 0.9 && last.position <= 1.0);
770}
771
772#[test]
773fn test_conic_gradient_empty() {
774 let grad = ConicGradientBrush::new(0.0, []);
776 assert_eq!(grad.stops().count(), 2);
778}