1use crate::color::Color;
4use crate::geometry::Point;
5use crate::render_effect::TileMode;
6
7#[derive(Clone, Debug, PartialEq)]
8pub enum Brush {
9 Solid(Color),
10 LinearGradient {
11 colors: Vec<Color>,
12 stops: Option<Vec<f32>>,
13 start: Point,
14 end: Point,
15 tile_mode: TileMode,
16 },
17 RadialGradient {
18 colors: Vec<Color>,
19 stops: Option<Vec<f32>>,
20 center: Point,
21 radius: f32,
22 tile_mode: TileMode,
23 },
24 SweepGradient {
25 colors: Vec<Color>,
26 stops: Option<Vec<f32>>,
27 center: Point,
28 },
29}
30
31fn split_color_stops(color_stops: Vec<(f32, Color)>) -> (Vec<Color>, Vec<f32>) {
32 let mut colors = Vec::with_capacity(color_stops.len());
33 let mut stops = Vec::with_capacity(color_stops.len());
34 for (stop, color) in color_stops {
35 colors.push(color);
36 stops.push(stop);
37 }
38 (colors, stops)
39}
40
41impl Brush {
42 pub fn solid(color: Color) -> Self {
43 Brush::Solid(color)
44 }
45
46 pub fn linear_gradient(colors: Vec<Color>) -> Self {
49 Self::linear_gradient_with_tile_mode(
50 colors,
51 Point { x: 0.0, y: 0.0 },
52 Point {
53 x: f32::INFINITY,
54 y: f32::INFINITY,
55 },
56 TileMode::Clamp,
57 )
58 }
59
60 pub fn linear_gradient_range(colors: Vec<Color>, start: Point, end: Point) -> Self {
61 Self::linear_gradient_with_tile_mode(colors, start, end, TileMode::Clamp)
62 }
63
64 pub fn linear_gradient_with_tile_mode(
65 colors: Vec<Color>,
66 start: Point,
67 end: Point,
68 tile_mode: TileMode,
69 ) -> Self {
70 Brush::LinearGradient {
71 colors,
72 stops: None,
73 start,
74 end,
75 tile_mode,
76 }
77 }
78
79 pub fn linear_gradient_stops(
80 color_stops: Vec<(f32, Color)>,
81 start: Point,
82 end: Point,
83 tile_mode: TileMode,
84 ) -> Self {
85 let (colors, stops) = split_color_stops(color_stops);
86 Brush::LinearGradient {
87 colors,
88 stops: Some(stops),
89 start,
90 end,
91 tile_mode,
92 }
93 }
94
95 pub fn vertical_gradient(colors: Vec<Color>, start_y: f32, end_y: f32) -> Self {
96 Self::vertical_gradient_tiled(colors, start_y, end_y, TileMode::Clamp)
97 }
98
99 pub fn vertical_gradient_tiled(
100 colors: Vec<Color>,
101 start_y: f32,
102 end_y: f32,
103 tile_mode: TileMode,
104 ) -> Self {
105 Self::linear_gradient_with_tile_mode(
106 colors,
107 Point { x: 0.0, y: start_y },
108 Point { x: 0.0, y: end_y },
109 tile_mode,
110 )
111 }
112
113 pub fn vertical_gradient_default(colors: Vec<Color>) -> Self {
114 Self::vertical_gradient_tiled(colors, 0.0, f32::INFINITY, TileMode::Clamp)
115 }
116
117 pub fn vertical_gradient_stops(
118 color_stops: Vec<(f32, Color)>,
119 start_y: f32,
120 end_y: f32,
121 tile_mode: TileMode,
122 ) -> Self {
123 Self::linear_gradient_stops(
124 color_stops,
125 Point { x: 0.0, y: start_y },
126 Point { x: 0.0, y: end_y },
127 tile_mode,
128 )
129 }
130
131 pub fn horizontal_gradient(colors: Vec<Color>, start_x: f32, end_x: f32) -> Self {
132 Self::horizontal_gradient_tiled(colors, start_x, end_x, TileMode::Clamp)
133 }
134
135 pub fn horizontal_gradient_tiled(
136 colors: Vec<Color>,
137 start_x: f32,
138 end_x: f32,
139 tile_mode: TileMode,
140 ) -> Self {
141 Self::linear_gradient_with_tile_mode(
142 colors,
143 Point { x: start_x, y: 0.0 },
144 Point { x: end_x, y: 0.0 },
145 tile_mode,
146 )
147 }
148
149 pub fn horizontal_gradient_default(colors: Vec<Color>) -> Self {
150 Self::horizontal_gradient_tiled(colors, 0.0, f32::INFINITY, TileMode::Clamp)
151 }
152
153 pub fn horizontal_gradient_stops(
154 color_stops: Vec<(f32, Color)>,
155 start_x: f32,
156 end_x: f32,
157 tile_mode: TileMode,
158 ) -> Self {
159 Self::linear_gradient_stops(
160 color_stops,
161 Point { x: start_x, y: 0.0 },
162 Point { x: end_x, y: 0.0 },
163 tile_mode,
164 )
165 }
166
167 pub fn radial_gradient(colors: Vec<Color>, center: Point, radius: f32) -> Self {
168 Self::radial_gradient_tiled(colors, center, radius, TileMode::Clamp)
169 }
170
171 pub fn radial_gradient_tiled(
172 colors: Vec<Color>,
173 center: Point,
174 radius: f32,
175 tile_mode: TileMode,
176 ) -> Self {
177 Brush::RadialGradient {
178 colors,
179 stops: None,
180 center,
181 radius,
182 tile_mode,
183 }
184 }
185
186 pub fn radial_gradient_stops(
187 color_stops: Vec<(f32, Color)>,
188 center: Point,
189 radius: f32,
190 tile_mode: TileMode,
191 ) -> Self {
192 let (colors, stops) = split_color_stops(color_stops);
193 Brush::RadialGradient {
194 colors,
195 stops: Some(stops),
196 center,
197 radius,
198 tile_mode,
199 }
200 }
201
202 pub fn sweep_gradient(colors: Vec<Color>, center: Point) -> Self {
203 Brush::SweepGradient {
204 colors,
205 stops: None,
206 center,
207 }
208 }
209
210 pub fn sweep_gradient_stops(color_stops: Vec<(f32, Color)>, center: Point) -> Self {
211 let (colors, stops) = split_color_stops(color_stops);
212 Brush::SweepGradient {
213 colors,
214 stops: Some(stops),
215 center,
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn sweep_gradient_construction() {
226 let colors = vec![Color(1.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 1.0, 1.0)];
227 let center = Point { x: 50.0, y: 50.0 };
228 let brush = Brush::sweep_gradient(colors.clone(), center);
229 match brush {
230 Brush::SweepGradient {
231 colors: c,
232 stops,
233 center: p,
234 } => {
235 assert_eq!(c, colors);
236 assert_eq!(p, center);
237 assert!(stops.is_none());
238 }
239 _ => panic!("expected SweepGradient"),
240 }
241 }
242
243 #[test]
244 fn brush_clone_eq() {
245 let a = Brush::solid(Color(1.0, 0.0, 0.0, 1.0));
246 let b = a.clone();
247 assert_eq!(a, b);
248 }
249
250 #[test]
251 fn vertical_gradient_construction() {
252 let colors = vec![Color(0.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 0.0, 0.0)];
253 let brush = Brush::vertical_gradient(colors.clone(), 24.0, 64.0);
254 match brush {
255 Brush::LinearGradient {
256 colors: c,
257 stops,
258 start,
259 end,
260 tile_mode,
261 } => {
262 assert_eq!(c, colors);
263 assert!(stops.is_none());
264 assert_eq!(tile_mode, TileMode::Clamp);
265 assert_eq!(start, Point { x: 0.0, y: 24.0 });
266 assert_eq!(end, Point { x: 0.0, y: 64.0 });
267 }
268 _ => panic!("expected LinearGradient"),
269 }
270 }
271
272 #[test]
273 fn linear_gradient_defaults_to_infinite_end() {
274 let brush = Brush::linear_gradient(vec![Color::BLACK, Color::WHITE]);
275 match brush {
276 Brush::LinearGradient { start, end, .. } => {
277 assert_eq!(start, Point { x: 0.0, y: 0.0 });
278 assert!(end.x.is_infinite());
279 assert!(end.y.is_infinite());
280 }
281 _ => panic!("expected LinearGradient"),
282 }
283 }
284
285 #[test]
286 fn gradient_color_stops_are_stored() {
287 let brush = Brush::linear_gradient_stops(
288 vec![(0.0, Color::RED), (0.6, Color::GREEN), (1.0, Color::BLUE)],
289 Point { x: 0.0, y: 0.0 },
290 Point { x: 20.0, y: 10.0 },
291 TileMode::Mirror,
292 );
293
294 match brush {
295 Brush::LinearGradient {
296 colors,
297 stops,
298 tile_mode,
299 ..
300 } => {
301 assert_eq!(colors, vec![Color::RED, Color::GREEN, Color::BLUE]);
302 assert_eq!(stops, Some(vec![0.0, 0.6, 1.0]));
303 assert_eq!(tile_mode, TileMode::Mirror);
304 }
305 _ => panic!("expected LinearGradient"),
306 }
307 }
308}