1use crate::{color::Color, geometry::Point, render_effect::TileMode};
4
5#[derive(Clone, Debug, PartialEq)]
6pub enum Brush {
7 Solid(Color),
8 LinearGradient {
9 colors: Vec<Color>,
10 stops: Option<Vec<f32>>,
11 start: Point,
12 end: Point,
13 tile_mode: TileMode,
14 },
15 RadialGradient {
16 colors: Vec<Color>,
17 stops: Option<Vec<f32>>,
18 center: Point,
19 radius: f32,
20 tile_mode: TileMode,
21 },
22 SweepGradient {
23 colors: Vec<Color>,
24 stops: Option<Vec<f32>>,
25 center: Point,
26 },
27}
28
29fn split_color_stops(color_stops: Vec<(f32, Color)>) -> (Vec<Color>, Vec<f32>) {
30 let mut colors = Vec::with_capacity(color_stops.len());
31 let mut stops = Vec::with_capacity(color_stops.len());
32 for (stop, color) in color_stops {
33 colors.push(color);
34 stops.push(stop);
35 }
36 (colors, stops)
37}
38
39impl Brush {
40 pub fn solid(color: Color) -> Self {
41 Brush::Solid(color)
42 }
43
44 pub fn linear_gradient(colors: Vec<Color>) -> Self {
47 Self::linear_gradient_with_tile_mode(
48 colors,
49 Point { x: 0.0, y: 0.0 },
50 Point {
51 x: f32::INFINITY,
52 y: f32::INFINITY,
53 },
54 TileMode::Clamp,
55 )
56 }
57
58 pub fn linear_gradient_range(colors: Vec<Color>, start: Point, end: Point) -> Self {
59 Self::linear_gradient_with_tile_mode(colors, start, end, TileMode::Clamp)
60 }
61
62 pub fn linear_gradient_with_tile_mode(
63 colors: Vec<Color>,
64 start: Point,
65 end: Point,
66 tile_mode: TileMode,
67 ) -> Self {
68 Brush::LinearGradient {
69 colors,
70 stops: None,
71 start,
72 end,
73 tile_mode,
74 }
75 }
76
77 pub fn linear_gradient_stops(
78 color_stops: Vec<(f32, Color)>,
79 start: Point,
80 end: Point,
81 tile_mode: TileMode,
82 ) -> Self {
83 let (colors, stops) = split_color_stops(color_stops);
84 Brush::LinearGradient {
85 colors,
86 stops: Some(stops),
87 start,
88 end,
89 tile_mode,
90 }
91 }
92
93 pub fn vertical_gradient(colors: Vec<Color>, start_y: f32, end_y: f32) -> Self {
94 Self::vertical_gradient_tiled(colors, start_y, end_y, TileMode::Clamp)
95 }
96
97 pub fn vertical_gradient_tiled(
98 colors: Vec<Color>,
99 start_y: f32,
100 end_y: f32,
101 tile_mode: TileMode,
102 ) -> Self {
103 Self::linear_gradient_with_tile_mode(
104 colors,
105 Point { x: 0.0, y: start_y },
106 Point { x: 0.0, y: end_y },
107 tile_mode,
108 )
109 }
110
111 pub fn vertical_gradient_default(colors: Vec<Color>) -> Self {
112 Self::vertical_gradient_tiled(colors, 0.0, f32::INFINITY, TileMode::Clamp)
113 }
114
115 pub fn vertical_gradient_stops(
116 color_stops: Vec<(f32, Color)>,
117 start_y: f32,
118 end_y: f32,
119 tile_mode: TileMode,
120 ) -> Self {
121 Self::linear_gradient_stops(
122 color_stops,
123 Point { x: 0.0, y: start_y },
124 Point { x: 0.0, y: end_y },
125 tile_mode,
126 )
127 }
128
129 pub fn horizontal_gradient(colors: Vec<Color>, start_x: f32, end_x: f32) -> Self {
130 Self::horizontal_gradient_tiled(colors, start_x, end_x, TileMode::Clamp)
131 }
132
133 pub fn horizontal_gradient_tiled(
134 colors: Vec<Color>,
135 start_x: f32,
136 end_x: f32,
137 tile_mode: TileMode,
138 ) -> Self {
139 Self::linear_gradient_with_tile_mode(
140 colors,
141 Point { x: start_x, y: 0.0 },
142 Point { x: end_x, y: 0.0 },
143 tile_mode,
144 )
145 }
146
147 pub fn horizontal_gradient_default(colors: Vec<Color>) -> Self {
148 Self::horizontal_gradient_tiled(colors, 0.0, f32::INFINITY, TileMode::Clamp)
149 }
150
151 pub fn horizontal_gradient_stops(
152 color_stops: Vec<(f32, Color)>,
153 start_x: f32,
154 end_x: f32,
155 tile_mode: TileMode,
156 ) -> Self {
157 Self::linear_gradient_stops(
158 color_stops,
159 Point { x: start_x, y: 0.0 },
160 Point { x: end_x, y: 0.0 },
161 tile_mode,
162 )
163 }
164
165 pub fn radial_gradient(colors: Vec<Color>, center: Point, radius: f32) -> Self {
166 Self::radial_gradient_tiled(colors, center, radius, TileMode::Clamp)
167 }
168
169 pub fn radial_gradient_tiled(
170 colors: Vec<Color>,
171 center: Point,
172 radius: f32,
173 tile_mode: TileMode,
174 ) -> Self {
175 Brush::RadialGradient {
176 colors,
177 stops: None,
178 center,
179 radius,
180 tile_mode,
181 }
182 }
183
184 pub fn radial_gradient_stops(
185 color_stops: Vec<(f32, Color)>,
186 center: Point,
187 radius: f32,
188 tile_mode: TileMode,
189 ) -> Self {
190 let (colors, stops) = split_color_stops(color_stops);
191 Brush::RadialGradient {
192 colors,
193 stops: Some(stops),
194 center,
195 radius,
196 tile_mode,
197 }
198 }
199
200 pub fn sweep_gradient(colors: Vec<Color>, center: Point) -> Self {
201 Brush::SweepGradient {
202 colors,
203 stops: None,
204 center,
205 }
206 }
207
208 pub fn sweep_gradient_stops(color_stops: Vec<(f32, Color)>, center: Point) -> Self {
209 let (colors, stops) = split_color_stops(color_stops);
210 Brush::SweepGradient {
211 colors,
212 stops: Some(stops),
213 center,
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn red_to_blue() -> Vec<Color> {
223 vec![Color(1.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 1.0, 1.0)]
224 }
225
226 fn linear_parts(brush: &Brush) -> (&Vec<Color>, &Option<Vec<f32>>, Point, Point, TileMode) {
227 match brush {
228 Brush::LinearGradient {
229 colors,
230 stops,
231 start,
232 end,
233 tile_mode,
234 } => (colors, stops, *start, *end, *tile_mode),
235 other => panic!("expected LinearGradient, got {other:?}"),
236 }
237 }
238
239 #[test]
243 fn an_axis_gradient_varies_along_its_own_axis_only() {
244 let vertical = Brush::vertical_gradient(red_to_blue(), 10.0, 90.0);
245 let (colors, stops, start, end, tile_mode) = linear_parts(&vertical);
246 assert_eq!(colors, &red_to_blue());
247 assert!(stops.is_none());
248 assert_eq!(start, Point { x: 0.0, y: 10.0 });
249 assert_eq!(end, Point { x: 0.0, y: 90.0 });
250 assert_eq!(tile_mode, TileMode::Clamp);
251
252 let horizontal = Brush::horizontal_gradient(red_to_blue(), 10.0, 90.0);
253 let (_, _, start, end, _) = linear_parts(&horizontal);
254 assert_eq!(start, Point { x: 10.0, y: 0.0 });
255 assert_eq!(end, Point { x: 90.0, y: 0.0 });
256 }
257
258 #[test]
261 fn a_default_axis_gradient_spans_whatever_it_is_painted_into() {
262 for brush in [
263 Brush::vertical_gradient_default(red_to_blue()),
264 Brush::horizontal_gradient_default(red_to_blue()),
265 ] {
266 let (_, _, start, end, tile_mode) = linear_parts(&brush);
267 assert_eq!(start, Point { x: 0.0, y: 0.0 });
268 assert!(end.x.is_infinite() || end.y.is_infinite());
269 assert_eq!(tile_mode, TileMode::Clamp);
270 }
271 }
272
273 #[test]
276 fn a_tiled_axis_gradient_carries_its_tile_mode() {
277 let vertical = Brush::vertical_gradient_tiled(red_to_blue(), 0.0, 8.0, TileMode::Repeated);
278 let (_, _, _, _, tile_mode) = linear_parts(&vertical);
279 assert_eq!(tile_mode, TileMode::Repeated);
280
281 let horizontal =
282 Brush::horizontal_gradient_tiled(red_to_blue(), 0.0, 8.0, TileMode::Mirror);
283 let (_, _, _, _, tile_mode) = linear_parts(&horizontal);
284 assert_eq!(tile_mode, TileMode::Mirror);
285 }
286
287 #[test]
291 fn explicit_stops_travel_beside_their_colours() {
292 let stops_in = vec![
293 (0.0, Color(1.0, 0.0, 0.0, 1.0)),
294 (0.8, Color(0.0, 1.0, 0.0, 1.0)),
295 (1.0, Color(0.0, 0.0, 1.0, 1.0)),
296 ];
297 let expected_colors: Vec<Color> = stops_in.iter().map(|(_, color)| *color).collect();
298 let expected_positions: Vec<f32> = stops_in.iter().map(|(at, _)| *at).collect();
299
300 let vertical =
301 Brush::vertical_gradient_stops(stops_in.clone(), 0.0, 100.0, TileMode::Clamp);
302 let (colors, stops, start, end, _) = linear_parts(&vertical);
303 assert_eq!(colors, &expected_colors);
304 assert_eq!(stops.as_ref(), Some(&expected_positions));
305 assert_eq!(start, Point { x: 0.0, y: 0.0 });
306 assert_eq!(end, Point { x: 0.0, y: 100.0 });
307
308 let horizontal =
309 Brush::horizontal_gradient_stops(stops_in.clone(), 0.0, 100.0, TileMode::Clamp);
310 let (colors, stops, start, end, _) = linear_parts(&horizontal);
311 assert_eq!(colors, &expected_colors);
312 assert_eq!(stops.as_ref(), Some(&expected_positions));
313 assert_eq!(start, Point { x: 0.0, y: 0.0 });
314 assert_eq!(end, Point { x: 100.0, y: 0.0 });
315
316 let center = Point { x: 5.0, y: 5.0 };
317 match Brush::radial_gradient_stops(stops_in.clone(), center, 20.0, TileMode::Repeated) {
318 Brush::RadialGradient {
319 colors,
320 stops,
321 center: at,
322 radius,
323 tile_mode,
324 } => {
325 assert_eq!(colors, expected_colors);
326 assert_eq!(stops, Some(expected_positions.clone()));
327 assert_eq!(at, center);
328 assert_eq!(radius, 20.0);
329 assert_eq!(tile_mode, TileMode::Repeated);
330 }
331 other => panic!("expected RadialGradient, got {other:?}"),
332 }
333
334 match Brush::sweep_gradient_stops(stops_in, center) {
335 Brush::SweepGradient {
336 colors,
337 stops,
338 center: at,
339 } => {
340 assert_eq!(colors, expected_colors);
341 assert_eq!(stops, Some(expected_positions));
342 assert_eq!(at, center);
343 }
344 other => panic!("expected SweepGradient, got {other:?}"),
345 }
346 }
347
348 #[test]
349 fn a_tiled_radial_gradient_carries_its_tile_mode_and_geometry() {
350 let center = Point { x: 12.0, y: 34.0 };
351 match Brush::radial_gradient_tiled(red_to_blue(), center, 7.5, TileMode::Mirror) {
352 Brush::RadialGradient {
353 colors,
354 stops,
355 center: at,
356 radius,
357 tile_mode,
358 } => {
359 assert_eq!(colors, red_to_blue());
360 assert!(stops.is_none());
361 assert_eq!(at, center);
362 assert_eq!(radius, 7.5);
363 assert_eq!(tile_mode, TileMode::Mirror);
364 }
365 other => panic!("expected RadialGradient, got {other:?}"),
366 }
367 }
368
369 #[test]
370 fn sweep_gradient_construction() {
371 let colors = vec![Color(1.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 1.0, 1.0)];
372 let center = Point { x: 50.0, y: 50.0 };
373 let brush = Brush::sweep_gradient(colors.clone(), center);
374 match brush {
375 Brush::SweepGradient {
376 colors: c,
377 stops,
378 center: p,
379 } => {
380 assert_eq!(c, colors);
381 assert_eq!(p, center);
382 assert!(stops.is_none());
383 }
384 _ => panic!("expected SweepGradient"),
385 }
386 }
387
388 #[test]
389 fn brush_clone_eq() {
390 let a = Brush::solid(Color(1.0, 0.0, 0.0, 1.0));
391 let b = a.clone();
392 assert_eq!(a, b);
393 }
394
395 #[test]
396 fn vertical_gradient_construction() {
397 let colors = vec![Color(0.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 0.0, 0.0)];
398 let brush = Brush::vertical_gradient(colors.clone(), 24.0, 64.0);
399 match brush {
400 Brush::LinearGradient {
401 colors: c,
402 stops,
403 start,
404 end,
405 tile_mode,
406 } => {
407 assert_eq!(c, colors);
408 assert!(stops.is_none());
409 assert_eq!(tile_mode, TileMode::Clamp);
410 assert_eq!(start, Point { x: 0.0, y: 24.0 });
411 assert_eq!(end, Point { x: 0.0, y: 64.0 });
412 }
413 _ => panic!("expected LinearGradient"),
414 }
415 }
416
417 #[test]
418 fn linear_gradient_defaults_to_infinite_end() {
419 let brush = Brush::linear_gradient(vec![Color::BLACK, Color::WHITE]);
420 match brush {
421 Brush::LinearGradient { start, end, .. } => {
422 assert_eq!(start, Point { x: 0.0, y: 0.0 });
423 assert!(end.x.is_infinite());
424 assert!(end.y.is_infinite());
425 }
426 _ => panic!("expected LinearGradient"),
427 }
428 }
429
430 #[test]
431 fn gradient_color_stops_are_stored() {
432 let brush = Brush::linear_gradient_stops(
433 vec![(0.0, Color::RED), (0.6, Color::GREEN), (1.0, Color::BLUE)],
434 Point { x: 0.0, y: 0.0 },
435 Point { x: 20.0, y: 10.0 },
436 TileMode::Mirror,
437 );
438
439 match brush {
440 Brush::LinearGradient {
441 colors,
442 stops,
443 tile_mode,
444 ..
445 } => {
446 assert_eq!(colors, vec![Color::RED, Color::GREEN, Color::BLUE]);
447 assert_eq!(stops, Some(vec![0.0, 0.6, 1.0]));
448 assert_eq!(tile_mode, TileMode::Mirror);
449 }
450 _ => panic!("expected LinearGradient"),
451 }
452 }
453}