Skip to main content

telar_renderer_core/style/
paint.rs

1use crate::Color;
2
3use super::gradient::Gradient;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum Paint {
7    Solid(Color),
8    Gradient(Gradient),
9}
10
11impl From<Color> for Paint {
12    fn from(color: Color) -> Self {
13        Self::Solid(color)
14    }
15}
16
17impl Paint {
18    pub fn solid_color(&self) -> Color {
19        match self {
20            Paint::Solid(c) => *c,
21            Paint::Gradient(g) => g
22                .stops
23                .active()
24                .first()
25                .map_or(Color::TRANSPARENT, |s| s.color),
26        }
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub enum LineCap {
32    #[default]
33    Butt,
34    Round,
35    Square,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum LineJoin {
40    #[default]
41    Miter,
42    Round,
43    Bevel,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub enum FillRule {
48    #[default]
49    Winding,
50    EvenOdd,
51}
52
53/// Stroke style for drawing primitives. Includes `join` to control how corners are rendered in paths and rects; for line segments `join` is unused and defaults to `Miter`.
54#[derive(Debug, Clone, Copy, PartialEq)]
55pub struct Stroke {
56    pub paint: Paint,
57    pub width: f32,
58    pub cap: LineCap,
59    pub join: LineJoin,
60}
61
62impl Stroke {
63    pub fn new(paint: impl Into<Paint>, width: f32) -> Self {
64        Self {
65            paint: paint.into(),
66            width,
67            cap: LineCap::default(),
68            join: LineJoin::default(),
69        }
70    }
71
72    pub fn with_cap(mut self, cap: LineCap) -> Self {
73        self.cap = cap;
74        self
75    }
76
77    pub fn with_join(mut self, join: LineJoin) -> Self {
78        self.join = join;
79        self
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq)]
84pub struct Shadow {
85    pub offset_x: f32,
86    pub offset_y: f32,
87    pub blur_radius: f32,
88    pub spread: f32,
89    pub color: Color,
90}
91
92impl Shadow {
93    pub fn new(offset_x: f32, offset_y: f32, blur_radius: f32, color: Color) -> Self {
94        Self {
95            offset_x,
96            offset_y,
97            blur_radius,
98            spread: 0.0,
99            color,
100        }
101    }
102
103    pub fn with_spread(mut self, spread: f32) -> Self {
104        self.spread = spread;
105        self
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn paint_from_color_creates_solid() {
115        let color = Color::GREEN;
116        let fill: Paint = color.into();
117        assert_eq!(fill, Paint::Solid(color));
118    }
119
120    #[test]
121    fn line_cap_default_is_butt() {
122        assert_eq!(LineCap::default(), LineCap::Butt);
123    }
124
125    #[test]
126    fn line_join_default_is_miter() {
127        assert_eq!(LineJoin::default(), LineJoin::Miter);
128    }
129
130    #[test]
131    fn fill_rule_default_is_winding() {
132        assert_eq!(FillRule::default(), FillRule::Winding);
133    }
134
135    #[test]
136    fn shadow_new_stores_fields_and_zero_spread() {
137        let shadow = Shadow::new(2.0, 4.0, 8.0, Color::BLACK);
138        assert_eq!(shadow.offset_x, 2.0);
139        assert_eq!(shadow.offset_y, 4.0);
140        assert_eq!(shadow.blur_radius, 8.0);
141        assert_eq!(shadow.spread, 0.0);
142        assert_eq!(shadow.color, Color::BLACK);
143    }
144
145    #[test]
146    fn shadow_with_spread_sets_spread() {
147        let shadow = Shadow::new(0.0, 0.0, 4.0, Color::RED).with_spread(6.0);
148        assert_eq!(shadow.spread, 6.0);
149    }
150
151    #[test]
152    fn stroke_new_stores_color_and_width() {
153        let s = Stroke::new(Color::RED, 3.0);
154        assert_eq!(s.paint, Paint::Solid(Color::RED));
155        assert_eq!(s.width, 3.0);
156    }
157
158    #[test]
159    fn stroke_new_defaults_cap_to_butt() {
160        let s = Stroke::new(Color::BLACK, 1.0);
161        assert_eq!(s.cap, LineCap::Butt);
162    }
163
164    #[test]
165    fn stroke_new_defaults_join_to_miter() {
166        let s = Stroke::new(Color::BLACK, 1.0);
167        assert_eq!(s.join, LineJoin::Miter);
168    }
169
170    #[test]
171    fn stroke_with_cap_sets_cap() {
172        let s = Stroke::new(Color::BLACK, 1.0).with_cap(LineCap::Square);
173        assert_eq!(s.cap, LineCap::Square);
174    }
175
176    #[test]
177    fn stroke_with_join_sets_join() {
178        let s = Stroke::new(Color::BLACK, 1.0).with_join(LineJoin::Bevel);
179        assert_eq!(s.join, LineJoin::Bevel);
180    }
181}