Skip to main content

cranpose_ui_graphics/
brush.rs

1//! Brush definitions for painting (solid colors, gradients, etc.)
2
3use 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    /// Creates a linear gradient that defaults to Compose semantics:
47    /// start at `(0,0)`, end at `(+inf,+inf)`, and `TileMode::Clamp`.
48    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}