telar_renderer_core/style/
paint.rs1use 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#[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}