Skip to main content

telar_renderer_core/style/
gradient.rs

1use geometry_core::Point;
2
3use crate::Color;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct GradientStop {
7    pub position: f32,
8    pub color: Color,
9}
10
11impl GradientStop {
12    pub fn new(position: f32, color: Color) -> Self {
13        Self { position, color }
14    }
15}
16
17/// Up to 8 gradient color stops. Fixed-size array preserves the `Copy` bound.
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct GradientStops {
20    stops: [GradientStop; 8],
21    count: u8,
22}
23
24impl GradientStops {
25    pub fn new(stops: &[(f32, Color)]) -> Self {
26        debug_assert!(
27            stops.len() <= 8,
28            "gradient has {} stops, max is 8",
29            stops.len()
30        );
31        let count = stops.len().min(8) as u8;
32        let mut arr = [GradientStop::new(0.0, Color::TRANSPARENT); 8];
33        for (i, &(position, color)) in stops.iter().take(8).enumerate() {
34            arr[i] = GradientStop::new(position, color);
35        }
36        Self { stops: arr, count }
37    }
38
39    pub fn active(&self) -> &[GradientStop] {
40        &self.stops[..self.count as usize]
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum GradientKind {
46    Linear { start: Point, end: Point },
47    Radial { center: Point, radius: f32 },
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct Gradient {
52    pub kind: GradientKind,
53    pub stops: GradientStops,
54}
55
56impl Gradient {
57    pub fn linear(start: Point, end: Point, stops: &[(f32, Color)]) -> Self {
58        Self {
59            kind: GradientKind::Linear { start, end },
60            stops: GradientStops::new(stops),
61        }
62    }
63
64    pub fn radial(center: Point, radius: f32, stops: &[(f32, Color)]) -> Self {
65        Self {
66            kind: GradientKind::Radial { center, radius },
67            stops: GradientStops::new(stops),
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn gradient_stops_new_stores_stops() {
78        let stops = GradientStops::new(&[(0.0, Color::BLACK), (1.0, Color::WHITE)]);
79        assert_eq!(stops.active().len(), 2);
80        assert_eq!(stops.active()[0].color, Color::BLACK);
81        assert_eq!(stops.active()[1].color, Color::WHITE);
82    }
83
84    #[test]
85    fn gradient_stops_new_truncates_to_eight() {
86        let raw: Vec<(f32, Color)> = (0..6).map(|i| (i as f32 / 5.0, Color::BLACK)).collect();
87        let stops = GradientStops::new(&raw);
88        assert_eq!(stops.active().len(), 6);
89    }
90
91    #[test]
92    fn gradient_linear_stores_stops() {
93        let p1 = Point::new(0.0, 0.0);
94        let p2 = Point::new(1.0, 0.0);
95        let g = Gradient::linear(p1, p2, &[(0.0, Color::BLACK), (1.0, Color::WHITE)]);
96        assert_eq!(g.stops.active().len(), 2);
97        assert_eq!(g.stops.active()[0].color, Color::BLACK);
98    }
99
100    #[test]
101    fn gradient_radial_stores_stops() {
102        let c = Point::new(0.5, 0.5);
103        let g = Gradient::radial(c, 1.0, &[(0.0, Color::RED), (1.0, Color::TRANSPARENT)]);
104        assert_eq!(g.stops.active().len(), 2);
105        assert_eq!(g.stops.active()[0].color, Color::RED);
106    }
107}