1use super::Color;
9use crate::SharedVector;
10use crate::properties::InterpolatedPropertyValue;
11use euclid::default::{Point2D, Size2D};
12
13#[cfg(not(feature = "std"))]
14use num_traits::Euclid;
15#[cfg(not(feature = "std"))]
16use num_traits::float::Float;
17
18#[derive(Clone, PartialEq, Debug, derive_more::From)]
23#[repr(C)]
24#[non_exhaustive]
25pub enum Brush {
26 SolidColor(Color),
28 LinearGradient(LinearGradientBrush),
31 RadialGradient(RadialGradientBrush),
34 ConicGradient(ConicGradientBrush),
37}
38
39impl Default for Brush {
41 fn default() -> Self {
42 Self::SolidColor(Color::default())
43 }
44}
45
46impl Brush {
47 pub fn color(&self) -> Color {
50 match self {
51 Brush::SolidColor(col) => *col,
52 Brush::LinearGradient(gradient) => {
53 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
54 }
55 Brush::RadialGradient(gradient) => {
56 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
57 }
58 Brush::ConicGradient(gradient) => {
59 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
60 }
61 }
62 }
63
64 pub fn is_transparent(&self) -> bool {
73 match self {
74 Brush::SolidColor(c) => c.alpha() == 0,
75 Brush::LinearGradient(_) => false,
76 Brush::RadialGradient(_) => false,
77 Brush::ConicGradient(_) => false,
78 }
79 }
80
81 pub fn is_opaque(&self) -> bool {
90 match self {
91 Brush::SolidColor(c) => c.alpha() == 255,
92 Brush::LinearGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
93 Brush::RadialGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
94 Brush::ConicGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
95 }
96 }
97
98 #[must_use]
102 pub fn brighter(&self, factor: f32) -> Self {
103 match self {
104 Brush::SolidColor(c) => Brush::SolidColor(c.brighter(factor)),
105 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
106 g.angle(),
107 g.stops().map(|s| GradientStop {
108 color: s.color.brighter(factor),
109 position: s.position,
110 }),
111 )),
112 Brush::RadialGradient(g) => {
113 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
114 GradientStop { color: s.color.brighter(factor), position: s.position }
115 })))
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(1) {
121 x.color = x.color.brighter(factor);
122 }
123 Brush::ConicGradient(new_grad)
124 }
125 }
126 }
127
128 #[must_use]
132 pub fn darker(&self, factor: f32) -> Self {
133 match self {
134 Brush::SolidColor(c) => Brush::SolidColor(c.darker(factor)),
135 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
136 g.angle(),
137 g.stops()
138 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
139 )),
140 Brush::RadialGradient(g) => Brush::RadialGradient(RadialGradientBrush::new_circle(
141 g.stops()
142 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
143 )),
144 Brush::ConicGradient(g) => {
145 let mut new_grad = g.clone();
146 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
148 x.color = x.color.darker(factor);
149 }
150 Brush::ConicGradient(new_grad)
151 }
152 }
153 }
154
155 #[must_use]
161 pub fn transparentize(&self, amount: f32) -> Self {
162 match self {
163 Brush::SolidColor(c) => Brush::SolidColor(c.transparentize(amount)),
164 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
165 g.angle(),
166 g.stops().map(|s| GradientStop {
167 color: s.color.transparentize(amount),
168 position: s.position,
169 }),
170 )),
171 Brush::RadialGradient(g) => {
172 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
173 GradientStop { color: s.color.transparentize(amount), position: s.position }
174 })))
175 }
176 Brush::ConicGradient(g) => {
177 let mut new_grad = g.clone();
178 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
180 x.color = x.color.transparentize(amount);
181 }
182 Brush::ConicGradient(new_grad)
183 }
184 }
185 }
186
187 #[must_use]
190 pub fn with_alpha(&self, alpha: f32) -> Self {
191 match self {
192 Brush::SolidColor(c) => Brush::SolidColor(c.with_alpha(alpha)),
193 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
194 g.angle(),
195 g.stops().map(|s| GradientStop {
196 color: s.color.with_alpha(alpha),
197 position: s.position,
198 }),
199 )),
200 Brush::RadialGradient(g) => {
201 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
202 GradientStop { color: s.color.with_alpha(alpha), position: s.position }
203 })))
204 }
205 Brush::ConicGradient(g) => {
206 let mut new_grad = g.clone();
207 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
209 x.color = x.color.with_alpha(alpha);
210 }
211 Brush::ConicGradient(new_grad)
212 }
213 }
214 }
215}
216
217#[derive(Clone, PartialEq, Debug)]
221#[repr(transparent)]
222pub struct LinearGradientBrush(SharedVector<GradientStop>);
223
224impl LinearGradientBrush {
225 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
230 let stop_iter = stops.into_iter();
231 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
232 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
234 encoded_angle_and_stops.extend(stop_iter);
235 Self(encoded_angle_and_stops)
236 }
237 pub fn angle(&self) -> f32 {
239 self.0[0].position
240 }
241 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
244 self.0.iter().skip(1)
246 }
247}
248
249#[derive(Clone, PartialEq, Debug)]
251#[repr(transparent)]
252pub struct RadialGradientBrush(SharedVector<GradientStop>);
253
254impl RadialGradientBrush {
255 pub fn new_circle(stops: impl IntoIterator<Item = GradientStop>) -> Self {
258 Self(stops.into_iter().collect())
259 }
260 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
262 self.0.iter()
263 }
264}
265
266#[derive(Clone, PartialEq, Debug)]
269#[repr(transparent)]
270pub struct ConicGradientBrush(SharedVector<GradientStop>);
271
272impl ConicGradientBrush {
273 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
278 let stop_iter = stops.into_iter();
279 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
280 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
282 encoded_angle_and_stops.extend(stop_iter);
283 let mut result = Self(encoded_angle_and_stops);
284 result.normalize_stops();
285 if angle.abs() > f32::EPSILON {
286 result.apply_rotation(angle);
287 }
288 result
289 }
290
291 fn normalize_stops(&mut self) {
293 let stops_slice = &self.0[1..];
295 let has_stop_at_0 = stops_slice.iter().any(|s| s.position.abs() < f32::EPSILON);
296 let has_stop_at_1 = stops_slice.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
297 let has_stops_outside = stops_slice.iter().any(|s| s.position < 0.0 || s.position > 1.0);
298 let is_empty = stops_slice.is_empty();
299
300 if has_stop_at_0 && has_stop_at_1 && !has_stops_outside && !is_empty {
302 return;
303 }
304
305 let mut stops: alloc::vec::Vec<_> = stops_slice.iter().copied().collect();
307
308 if !has_stop_at_0 {
310 let stop_below_0 = stops.iter().filter(|s| s.position < 0.0).max_by(|a, b| {
311 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
312 });
313 let stop_above_0 = stops.iter().filter(|s| s.position > 0.0).min_by(|a, b| {
314 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
315 });
316 if let (Some(below), Some(above)) = (stop_below_0, stop_above_0) {
317 let t = (0.0 - below.position) / (above.position - below.position);
318 let color_at_0 = Self::interpolate_color(&below.color, &above.color, t);
319 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
320 } else if let Some(above) = stop_above_0 {
321 stops.insert(0, GradientStop { position: 0.0, color: above.color });
322 } else if let Some(below) = stop_below_0 {
323 stops.insert(0, GradientStop { position: 0.0, color: below.color });
324 }
325 }
326
327 if !has_stop_at_1 {
329 let stop_below_1 = stops.iter().filter(|s| s.position < 1.0).max_by(|a, b| {
330 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
331 });
332 let stop_above_1 = stops.iter().filter(|s| s.position > 1.0).min_by(|a, b| {
333 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
334 });
335
336 if let (Some(below), Some(above)) = (stop_below_1, stop_above_1) {
337 let t = (1.0 - below.position) / (above.position - below.position);
338 let color_at_1 = Self::interpolate_color(&below.color, &above.color, t);
339 stops.push(GradientStop { position: 1.0, color: color_at_1 });
340 } else if let Some(below) = stop_below_1 {
341 stops.push(GradientStop { position: 1.0, color: below.color });
342 } else if let Some(above) = stop_above_1 {
343 stops.push(GradientStop { position: 1.0, color: above.color });
344 }
345 }
346
347 if has_stops_outside {
349 stops.retain(|s| 0.0 <= s.position && s.position <= 1.0);
350 }
351
352 if stops.is_empty() {
354 stops.push(GradientStop { position: 0.0, color: Color::default() });
355 stops.push(GradientStop { position: 1.0, color: Color::default() });
356 }
357
358 let angle = self.angle();
360 self.0 = SharedVector::with_capacity(stops.len() + 1);
361 self.0.push(GradientStop { color: Default::default(), position: angle });
362 self.0.extend(stops.into_iter());
363 }
364
365 fn apply_rotation(&mut self, from_angle: f32) {
369 let normalized_from_angle = (from_angle / 360.0) - (from_angle / 360.0).floor();
371
372 if normalized_from_angle.abs() < f32::EPSILON {
374 self.0.make_mut_slice()[0].position = from_angle;
375 return;
376 }
377
378 self.0.make_mut_slice()[0].position = from_angle;
380
381 let mut stops: alloc::vec::Vec<_> = self.0.iter().skip(1).copied().collect();
383
384 if let Some(first) = stops.first_mut() {
386 if first.position.abs() < f32::EPSILON {
387 first.position = f32::EPSILON;
388 }
389 }
390
391 stops = stops
393 .iter()
394 .map(|stop| {
395 #[cfg(feature = "std")]
396 let rotated_position = (stop.position + normalized_from_angle).rem_euclid(1.0);
397 #[cfg(not(feature = "std"))]
398 let rotated_position = (stop.position + normalized_from_angle).rem_euclid(&1.0);
399 GradientStop { position: rotated_position, color: stop.color }
400 })
401 .collect();
402
403 for i in 0..stops.len() {
405 let j = (i + 1) % stops.len();
406 if (stops[i].position - stops[j].position).abs() < f32::EPSILON
407 && stops[i].color != stops[j].color
408 {
409 stops[i].position = (stops[i].position - f32::EPSILON).max(0.0);
410 stops[j].position = (stops[j].position + f32::EPSILON).min(1.0);
411 }
412 }
413
414 stops.sort_by(|a, b| {
416 a.position.partial_cmp(&b.position).unwrap_or(core::cmp::Ordering::Equal)
417 });
418
419 let has_stop_at_0 = stops.iter().any(|s| s.position.abs() < f32::EPSILON);
421 if !has_stop_at_0 {
422 if let (Some(last), Some(first)) = (stops.last(), stops.first()) {
423 let gap = 1.0 - last.position + first.position;
424 let color_at_0 = if gap > f32::EPSILON {
425 let t = (1.0 - last.position) / gap;
426 Self::interpolate_color(&last.color, &first.color, t)
427 } else {
428 last.color
429 };
430 stops.insert(0, GradientStop { position: 0.0, color: color_at_0 });
431 }
432 }
433
434 let has_stop_at_1 = stops.iter().any(|s| (s.position - 1.0).abs() < f32::EPSILON);
435 if !has_stop_at_1 {
436 if let Some(first) = stops.first() {
437 stops.push(GradientStop { position: 1.0, color: first.color });
438 }
439 }
440
441 self.0 = SharedVector::with_capacity(stops.len() + 1);
443 self.0.push(GradientStop { color: Default::default(), position: from_angle });
444 self.0.extend(stops.into_iter());
445 }
446
447 fn angle(&self) -> f32 {
449 self.0[0].position
450 }
451
452 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
455 self.0.iter().skip(1)
457 }
458
459 fn interpolate_color(c1: &Color, c2: &Color, factor: f32) -> Color {
468 let argb1 = c1.to_argb_u8();
469 let argb2 = c2.to_argb_u8();
470
471 let a1 = argb1.alpha as f32 / 255.0;
473 let a2 = argb2.alpha as f32 / 255.0;
474 let r1 = argb1.red as f32 * a1;
475 let g1 = argb1.green as f32 * a1;
476 let b1 = argb1.blue as f32 * a1;
477 let r2 = argb2.red as f32 * a2;
478 let g2 = argb2.green as f32 * a2;
479 let b2 = argb2.blue as f32 * a2;
480
481 let alpha = (1.0 - factor) * a1 + factor * a2;
483 let red = (1.0 - factor) * r1 + factor * r2;
484 let green = (1.0 - factor) * g1 + factor * g2;
485 let blue = (1.0 - factor) * b1 + factor * b2;
486
487 if alpha > 0.0 {
489 Color::from_argb_u8(
490 (alpha * 255.0) as u8,
491 (red / alpha).min(255.0) as u8,
492 (green / alpha).min(255.0) as u8,
493 (blue / alpha).min(255.0) as u8,
494 )
495 } else {
496 Color::from_argb_u8(0, 0, 0, 0)
497 }
498 }
499}
500
501#[cfg(feature = "ffi")]
503#[unsafe(no_mangle)]
504pub extern "C" fn slint_conic_gradient_normalize_stops(gradient: &mut ConicGradientBrush) {
505 gradient.normalize_stops();
506}
507
508#[cfg(feature = "ffi")]
510#[unsafe(no_mangle)]
511pub extern "C" fn slint_conic_gradient_apply_rotation(
512 gradient: &mut ConicGradientBrush,
513 angle_degrees: f32,
514) {
515 gradient.apply_rotation(angle_degrees);
516}
517
518#[repr(C)]
521#[derive(Copy, Clone, Debug, PartialEq)]
522pub struct GradientStop {
523 pub color: Color,
525 pub position: f32,
527}
528
529pub fn line_for_angle(angle: f32, size: Size2D<f32>) -> (Point2D<f32>, Point2D<f32>) {
531 let angle = (angle + 90.).to_radians();
532 let (s, c) = angle.sin_cos();
533
534 let (a, b) = if s.abs() < f32::EPSILON {
535 let y = size.height / 2.;
536 return if c < 0. {
537 (Point2D::new(0., y), Point2D::new(size.width, y))
538 } else {
539 (Point2D::new(size.width, y), Point2D::new(0., y))
540 };
541 } else if c * s < 0. {
542 let x = (s * size.width + c * size.height) * s / 2.;
544 let y = -c * x / s + size.height;
545 (Point2D::new(x, y), Point2D::new(size.width - x, size.height - y))
546 } else {
547 let x = (s * size.width - c * size.height) * s / 2.;
549 let y = -c * x / s;
550 (Point2D::new(size.width - x, size.height - y), Point2D::new(x, y))
551 };
552
553 if s > 0. { (a, b) } else { (b, a) }
554}
555
556impl InterpolatedPropertyValue for Brush {
557 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
558 match (self, target_value) {
559 (Brush::SolidColor(source_col), Brush::SolidColor(target_col)) => {
560 Brush::SolidColor(source_col.interpolate(target_col, t))
561 }
562 (Brush::SolidColor(col), Brush::LinearGradient(grad)) => {
563 let mut new_grad = grad.clone();
564 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
565 x.color = col.interpolate(&x.color, t);
566 }
567 Brush::LinearGradient(new_grad)
568 }
569 (a @ Brush::LinearGradient(_), b @ Brush::SolidColor(_)) => {
570 Self::interpolate(b, a, 1. - t)
571 }
572 (Brush::LinearGradient(lhs), Brush::LinearGradient(rhs)) => {
573 if lhs.0.len() < rhs.0.len() {
574 Self::interpolate(target_value, self, 1. - t)
575 } else {
576 let mut new_grad = lhs.clone();
577 let mut iter = new_grad.0.make_mut_slice().iter_mut();
578 {
579 let angle = &mut iter.next().unwrap().position;
580 *angle = angle.interpolate(&rhs.angle(), t);
581 }
582 for s2 in rhs.stops() {
583 let s1 = iter.next().unwrap();
584 s1.color = s1.color.interpolate(&s2.color, t);
585 s1.position = s1.position.interpolate(&s2.position, t);
586 }
587 for x in iter {
588 x.position = x.position.interpolate(&1.0, t);
589 }
590 Brush::LinearGradient(new_grad)
591 }
592 }
593 (Brush::SolidColor(col), Brush::RadialGradient(grad)) => {
594 let mut new_grad = grad.clone();
595 for x in new_grad.0.make_mut_slice().iter_mut() {
596 x.color = col.interpolate(&x.color, t);
597 }
598 Brush::RadialGradient(new_grad)
599 }
600 (a @ Brush::RadialGradient(_), b @ Brush::SolidColor(_)) => {
601 Self::interpolate(b, a, 1. - t)
602 }
603 (Brush::RadialGradient(lhs), Brush::RadialGradient(rhs)) => {
604 if lhs.0.len() < rhs.0.len() {
605 Self::interpolate(target_value, self, 1. - t)
606 } else {
607 let mut new_grad = lhs.clone();
608 let mut iter = new_grad.0.make_mut_slice().iter_mut();
609 let mut last_color = Color::default();
610 for s2 in rhs.stops() {
611 let s1 = iter.next().unwrap();
612 last_color = s2.color;
613 s1.color = s1.color.interpolate(&s2.color, t);
614 s1.position = s1.position.interpolate(&s2.position, t);
615 }
616 for x in iter {
617 x.position = x.position.interpolate(&1.0, t);
618 x.color = x.color.interpolate(&last_color, t);
619 }
620 Brush::RadialGradient(new_grad)
621 }
622 }
623 (Brush::SolidColor(col), Brush::ConicGradient(grad)) => {
624 let mut new_grad = grad.clone();
625 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
626 x.color = col.interpolate(&x.color, t);
627 }
628 Brush::ConicGradient(new_grad)
629 }
630 (a @ Brush::ConicGradient(_), b @ Brush::SolidColor(_)) => {
631 Self::interpolate(b, a, 1. - t)
632 }
633 (Brush::ConicGradient(lhs), Brush::ConicGradient(rhs)) => {
634 if lhs.0.len() < rhs.0.len() {
635 Self::interpolate(target_value, self, 1. - t)
636 } else {
637 let mut new_grad = lhs.clone();
638 let mut iter = new_grad.0.make_mut_slice().iter_mut();
639 {
640 let angle = &mut iter.next().unwrap().position;
641 *angle = angle.interpolate(&rhs.angle(), t);
642 }
643 for s2 in rhs.stops() {
644 let s1 = iter.next().unwrap();
645 s1.color = s1.color.interpolate(&s2.color, t);
646 s1.position = s1.position.interpolate(&s2.position, t);
647 }
648 for x in iter {
649 x.position = x.position.interpolate(&1.0, t);
650 }
651 Brush::ConicGradient(new_grad)
652 }
653 }
654 (a @ Brush::LinearGradient(_), b @ Brush::RadialGradient(_))
655 | (a @ Brush::RadialGradient(_), b @ Brush::LinearGradient(_))
656 | (a @ Brush::LinearGradient(_), b @ Brush::ConicGradient(_))
657 | (a @ Brush::ConicGradient(_), b @ Brush::LinearGradient(_))
658 | (a @ Brush::RadialGradient(_), b @ Brush::ConicGradient(_))
659 | (a @ Brush::ConicGradient(_), b @ Brush::RadialGradient(_)) => {
660 let color = Color::interpolate(&b.color(), &a.color(), t);
662 if t < 0.5 {
663 Self::interpolate(a, &Brush::SolidColor(color), t * 2.)
664 } else {
665 Self::interpolate(&Brush::SolidColor(color), b, (t - 0.5) * 2.)
666 }
667 }
668 }
669 }
670}
671
672#[test]
673#[allow(clippy::float_cmp)] fn test_linear_gradient_encoding() {
675 let stops: SharedVector<GradientStop> = [
676 GradientStop { position: 0.0, color: Color::from_argb_u8(255, 255, 0, 0) },
677 GradientStop { position: 0.5, color: Color::from_argb_u8(255, 0, 255, 0) },
678 GradientStop { position: 1.0, color: Color::from_argb_u8(255, 0, 0, 255) },
679 ]
680 .into();
681 let grad = LinearGradientBrush::new(256., stops.clone());
682 assert_eq!(grad.angle(), 256.);
683 assert!(grad.stops().eq(stops.iter()));
684}
685
686#[test]
687fn test_conic_gradient_basic() {
688 let grad = ConicGradientBrush::new(
690 0.0,
691 [
692 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
693 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
694 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
695 ],
696 );
697 assert_eq!(grad.angle(), 0.0);
698 assert_eq!(grad.stops().count(), 3);
699}
700
701#[test]
702fn test_conic_gradient_with_rotation() {
703 let grad = ConicGradientBrush::new(
705 90.0,
706 [
707 GradientStop { position: 0.0, color: Color::from_rgb_u8(255, 0, 0) },
708 GradientStop { position: 1.0, color: Color::from_rgb_u8(255, 0, 0) },
709 ],
710 );
711 assert_eq!(grad.angle(), 90.0);
712 assert!(grad.stops().count() >= 2);
714}
715
716#[test]
717fn test_conic_gradient_negative_angle() {
718 let grad = ConicGradientBrush::new(
720 -90.0,
721 [GradientStop { position: 0.5, color: Color::from_rgb_u8(255, 0, 0) }],
722 );
723 assert_eq!(grad.angle(), -90.0); assert!(grad.stops().count() >= 2); }
726
727#[test]
728fn test_conic_gradient_stops_outside_range() {
729 let grad = ConicGradientBrush::new(
731 0.0,
732 [
733 GradientStop { position: -0.2, color: Color::from_rgb_u8(255, 0, 0) },
734 GradientStop { position: 0.5, color: Color::from_rgb_u8(0, 255, 0) },
735 GradientStop { position: 1.2, color: Color::from_rgb_u8(0, 0, 255) },
736 ],
737 );
738 for stop in grad.stops() {
740 assert!(stop.position >= 0.0 && stop.position <= 1.0);
741 }
742}
743
744#[test]
745fn test_conic_gradient_all_stops_below_zero() {
746 let grad = ConicGradientBrush::new(
748 0.0,
749 [
750 GradientStop { position: -0.5, color: Color::from_rgb_u8(255, 0, 0) },
751 GradientStop { position: -0.3, color: Color::from_rgb_u8(0, 255, 0) },
752 ],
753 );
754 assert!(grad.stops().count() >= 2);
756 let first = grad.stops().next().unwrap();
758 assert!(first.position >= 0.0 && first.position < 0.1);
759}
760
761#[test]
762fn test_conic_gradient_all_stops_above_one() {
763 let grad = ConicGradientBrush::new(
765 0.0,
766 [
767 GradientStop { position: 1.2, color: Color::from_rgb_u8(255, 0, 0) },
768 GradientStop { position: 1.5, color: Color::from_rgb_u8(0, 255, 0) },
769 ],
770 );
771 assert!(grad.stops().count() >= 2);
773 let last = grad.stops().last().unwrap();
775 assert!(last.position > 0.9 && last.position <= 1.0);
776}
777
778#[test]
779fn test_conic_gradient_empty() {
780 let grad = ConicGradientBrush::new(0.0, []);
782 assert_eq!(grad.stops().count(), 2);
784}