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]
240 fn an_axis_gradient_varies_along_its_own_axis_only() {
241 let vertical = Brush::vertical_gradient(red_to_blue(), 10.0, 90.0);
242 let (colors, stops, start, end, tile_mode) = linear_parts(&vertical);
243 assert_eq!(colors, &red_to_blue());
244 assert!(stops.is_none());
245 assert_eq!(start, Point { x: 0.0, y: 10.0 });
246 assert_eq!(end, Point { x: 0.0, y: 90.0 });
247 assert_eq!(tile_mode, TileMode::Clamp);
248
249 let horizontal = Brush::horizontal_gradient(red_to_blue(), 10.0, 90.0);
250 let (_, _, start, end, _) = linear_parts(&horizontal);
251 assert_eq!(start, Point { x: 10.0, y: 0.0 });
252 assert_eq!(end, Point { x: 90.0, y: 0.0 });
253 }
254
255 #[test]
256 fn a_default_axis_gradient_spans_whatever_it_is_painted_into() {
257 for brush in [
258 Brush::vertical_gradient_default(red_to_blue()),
259 Brush::horizontal_gradient_default(red_to_blue()),
260 ] {
261 let (_, _, start, end, tile_mode) = linear_parts(&brush);
262 assert_eq!(start, Point { x: 0.0, y: 0.0 });
263 assert!(end.x.is_infinite() || end.y.is_infinite());
264 assert_eq!(tile_mode, TileMode::Clamp);
265 }
266 }
267
268 #[test]
269 fn a_tiled_axis_gradient_carries_its_tile_mode() {
270 let vertical = Brush::vertical_gradient_tiled(red_to_blue(), 0.0, 8.0, TileMode::Repeated);
271 let (_, _, _, _, tile_mode) = linear_parts(&vertical);
272 assert_eq!(tile_mode, TileMode::Repeated);
273
274 let horizontal =
275 Brush::horizontal_gradient_tiled(red_to_blue(), 0.0, 8.0, TileMode::Mirror);
276 let (_, _, _, _, tile_mode) = linear_parts(&horizontal);
277 assert_eq!(tile_mode, TileMode::Mirror);
278 }
279
280 #[test]
281 fn explicit_stops_travel_beside_their_colours() {
282 let stops_in = vec![
283 (0.0, Color(1.0, 0.0, 0.0, 1.0)),
284 (0.8, Color(0.0, 1.0, 0.0, 1.0)),
285 (1.0, Color(0.0, 0.0, 1.0, 1.0)),
286 ];
287 let expected_colors: Vec<Color> = stops_in.iter().map(|(_, color)| *color).collect();
288 let expected_positions: Vec<f32> = stops_in.iter().map(|(at, _)| *at).collect();
289
290 let vertical =
291 Brush::vertical_gradient_stops(stops_in.clone(), 0.0, 100.0, TileMode::Clamp);
292 let (colors, stops, start, end, _) = linear_parts(&vertical);
293 assert_eq!(colors, &expected_colors);
294 assert_eq!(stops.as_ref(), Some(&expected_positions));
295 assert_eq!(start, Point { x: 0.0, y: 0.0 });
296 assert_eq!(end, Point { x: 0.0, y: 100.0 });
297
298 let horizontal =
299 Brush::horizontal_gradient_stops(stops_in.clone(), 0.0, 100.0, TileMode::Clamp);
300 let (colors, stops, start, end, _) = linear_parts(&horizontal);
301 assert_eq!(colors, &expected_colors);
302 assert_eq!(stops.as_ref(), Some(&expected_positions));
303 assert_eq!(start, Point { x: 0.0, y: 0.0 });
304 assert_eq!(end, Point { x: 100.0, y: 0.0 });
305
306 let center = Point { x: 5.0, y: 5.0 };
307 match Brush::radial_gradient_stops(stops_in.clone(), center, 20.0, TileMode::Repeated) {
308 Brush::RadialGradient {
309 colors,
310 stops,
311 center: at,
312 radius,
313 tile_mode,
314 } => {
315 assert_eq!(colors, expected_colors);
316 assert_eq!(stops, Some(expected_positions.clone()));
317 assert_eq!(at, center);
318 assert_eq!(radius, 20.0);
319 assert_eq!(tile_mode, TileMode::Repeated);
320 }
321 other => panic!("expected RadialGradient, got {other:?}"),
322 }
323
324 match Brush::sweep_gradient_stops(stops_in, center) {
325 Brush::SweepGradient {
326 colors,
327 stops,
328 center: at,
329 } => {
330 assert_eq!(colors, expected_colors);
331 assert_eq!(stops, Some(expected_positions));
332 assert_eq!(at, center);
333 }
334 other => panic!("expected SweepGradient, got {other:?}"),
335 }
336 }
337
338 #[test]
339 fn a_tiled_radial_gradient_carries_its_tile_mode_and_geometry() {
340 let center = Point { x: 12.0, y: 34.0 };
341 match Brush::radial_gradient_tiled(red_to_blue(), center, 7.5, TileMode::Mirror) {
342 Brush::RadialGradient {
343 colors,
344 stops,
345 center: at,
346 radius,
347 tile_mode,
348 } => {
349 assert_eq!(colors, red_to_blue());
350 assert!(stops.is_none());
351 assert_eq!(at, center);
352 assert_eq!(radius, 7.5);
353 assert_eq!(tile_mode, TileMode::Mirror);
354 }
355 other => panic!("expected RadialGradient, got {other:?}"),
356 }
357 }
358
359 #[test]
360 fn sweep_gradient_construction() {
361 let colors = vec![Color(1.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 1.0, 1.0)];
362 let center = Point { x: 50.0, y: 50.0 };
363 let brush = Brush::sweep_gradient(colors.clone(), center);
364 match brush {
365 Brush::SweepGradient {
366 colors: c,
367 stops,
368 center: p,
369 } => {
370 assert_eq!(c, colors);
371 assert_eq!(p, center);
372 assert!(stops.is_none());
373 }
374 _ => panic!("expected SweepGradient"),
375 }
376 }
377
378 #[test]
379 fn brush_clone_eq() {
380 let a = Brush::solid(Color(1.0, 0.0, 0.0, 1.0));
381 let b = a.clone();
382 assert_eq!(a, b);
383 }
384
385 #[test]
386 fn vertical_gradient_construction() {
387 let colors = vec![Color(0.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 0.0, 0.0)];
388 let brush = Brush::vertical_gradient(colors.clone(), 24.0, 64.0);
389 match brush {
390 Brush::LinearGradient {
391 colors: c,
392 stops,
393 start,
394 end,
395 tile_mode,
396 } => {
397 assert_eq!(c, colors);
398 assert!(stops.is_none());
399 assert_eq!(tile_mode, TileMode::Clamp);
400 assert_eq!(start, Point { x: 0.0, y: 24.0 });
401 assert_eq!(end, Point { x: 0.0, y: 64.0 });
402 }
403 _ => panic!("expected LinearGradient"),
404 }
405 }
406
407 #[test]
408 fn linear_gradient_defaults_to_infinite_end() {
409 let brush = Brush::linear_gradient(vec![Color::BLACK, Color::WHITE]);
410 match brush {
411 Brush::LinearGradient { start, end, .. } => {
412 assert_eq!(start, Point { x: 0.0, y: 0.0 });
413 assert!(end.x.is_infinite());
414 assert!(end.y.is_infinite());
415 }
416 _ => panic!("expected LinearGradient"),
417 }
418 }
419
420 #[test]
421 fn gradient_color_stops_are_stored() {
422 let brush = Brush::linear_gradient_stops(
423 vec![(0.0, Color::RED), (0.6, Color::GREEN), (1.0, Color::BLUE)],
424 Point { x: 0.0, y: 0.0 },
425 Point { x: 20.0, y: 10.0 },
426 TileMode::Mirror,
427 );
428
429 match brush {
430 Brush::LinearGradient {
431 colors,
432 stops,
433 tile_mode,
434 ..
435 } => {
436 assert_eq!(colors, vec![Color::RED, Color::GREEN, Color::BLUE]);
437 assert_eq!(stops, Some(vec![0.0, 0.6, 1.0]));
438 assert_eq!(tile_mode, TileMode::Mirror);
439 }
440 _ => panic!("expected LinearGradient"),
441 }
442 }
443}