1use super::Color;
9use crate::properties::InterpolatedPropertyValue;
10use crate::SharedVector;
11use euclid::default::{Point2D, Size2D};
12
13#[cfg(not(feature = "std"))]
14use num_traits::float::Float;
15
16#[derive(Clone, PartialEq, Debug, derive_more::From)]
21#[repr(C)]
22#[non_exhaustive]
23pub enum Brush {
24 SolidColor(Color),
26 LinearGradient(LinearGradientBrush),
29 RadialGradient(RadialGradientBrush),
32 ConicGradient(ConicGradientBrush),
35}
36
37impl Default for Brush {
39 fn default() -> Self {
40 Self::SolidColor(Color::default())
41 }
42}
43
44impl Brush {
45 pub fn color(&self) -> Color {
48 match self {
49 Brush::SolidColor(col) => *col,
50 Brush::LinearGradient(gradient) => {
51 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
52 }
53 Brush::RadialGradient(gradient) => {
54 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
55 }
56 Brush::ConicGradient(gradient) => {
57 gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
58 }
59 }
60 }
61
62 pub fn is_transparent(&self) -> bool {
71 match self {
72 Brush::SolidColor(c) => c.alpha() == 0,
73 Brush::LinearGradient(_) => false,
74 Brush::RadialGradient(_) => false,
75 Brush::ConicGradient(_) => false,
76 }
77 }
78
79 pub fn is_opaque(&self) -> bool {
88 match self {
89 Brush::SolidColor(c) => c.alpha() == 255,
90 Brush::LinearGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
91 Brush::RadialGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
92 Brush::ConicGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
93 }
94 }
95
96 #[must_use]
100 pub fn brighter(&self, factor: f32) -> Self {
101 match self {
102 Brush::SolidColor(c) => Brush::SolidColor(c.brighter(factor)),
103 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
104 g.angle(),
105 g.stops().map(|s| GradientStop {
106 color: s.color.brighter(factor),
107 position: s.position,
108 }),
109 )),
110 Brush::RadialGradient(g) => {
111 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
112 GradientStop { color: s.color.brighter(factor), position: s.position }
113 })))
114 }
115 Brush::ConicGradient(g) => {
116 Brush::ConicGradient(ConicGradientBrush::new(g.stops().map(|s| GradientStop {
117 color: s.color.brighter(factor),
118 position: s.position,
119 })))
120 }
121 }
122 }
123
124 #[must_use]
128 pub fn darker(&self, factor: f32) -> Self {
129 match self {
130 Brush::SolidColor(c) => Brush::SolidColor(c.darker(factor)),
131 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
132 g.angle(),
133 g.stops()
134 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
135 )),
136 Brush::RadialGradient(g) => Brush::RadialGradient(RadialGradientBrush::new_circle(
137 g.stops()
138 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
139 )),
140 Brush::ConicGradient(g) => Brush::ConicGradient(ConicGradientBrush::new(
141 g.stops()
142 .map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
143 )),
144 }
145 }
146
147 #[must_use]
153 pub fn transparentize(&self, amount: f32) -> Self {
154 match self {
155 Brush::SolidColor(c) => Brush::SolidColor(c.transparentize(amount)),
156 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
157 g.angle(),
158 g.stops().map(|s| GradientStop {
159 color: s.color.transparentize(amount),
160 position: s.position,
161 }),
162 )),
163 Brush::RadialGradient(g) => {
164 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
165 GradientStop { color: s.color.transparentize(amount), position: s.position }
166 })))
167 }
168 Brush::ConicGradient(g) => {
169 Brush::ConicGradient(ConicGradientBrush::new(g.stops().map(|s| GradientStop {
170 color: s.color.transparentize(amount),
171 position: s.position,
172 })))
173 }
174 }
175 }
176
177 #[must_use]
180 pub fn with_alpha(&self, alpha: f32) -> Self {
181 match self {
182 Brush::SolidColor(c) => Brush::SolidColor(c.with_alpha(alpha)),
183 Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
184 g.angle(),
185 g.stops().map(|s| GradientStop {
186 color: s.color.with_alpha(alpha),
187 position: s.position,
188 }),
189 )),
190 Brush::RadialGradient(g) => {
191 Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
192 GradientStop { color: s.color.with_alpha(alpha), position: s.position }
193 })))
194 }
195 Brush::ConicGradient(g) => {
196 Brush::ConicGradient(ConicGradientBrush::new(g.stops().map(|s| GradientStop {
197 color: s.color.with_alpha(alpha),
198 position: s.position,
199 })))
200 }
201 }
202 }
203}
204
205#[derive(Clone, PartialEq, Debug)]
209#[repr(transparent)]
210pub struct LinearGradientBrush(SharedVector<GradientStop>);
211
212impl LinearGradientBrush {
213 pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
218 let stop_iter = stops.into_iter();
219 let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
220 encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
222 encoded_angle_and_stops.extend(stop_iter);
223 Self(encoded_angle_and_stops)
224 }
225 pub fn angle(&self) -> f32 {
227 self.0[0].position
228 }
229 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
232 self.0.iter().skip(1)
234 }
235}
236
237#[derive(Clone, PartialEq, Debug)]
239#[repr(transparent)]
240pub struct RadialGradientBrush(SharedVector<GradientStop>);
241
242impl RadialGradientBrush {
243 pub fn new_circle(stops: impl IntoIterator<Item = GradientStop>) -> Self {
246 Self(stops.into_iter().collect())
247 }
248 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
250 self.0.iter()
251 }
252}
253
254#[derive(Clone, PartialEq, Debug)]
257#[repr(transparent)]
258pub struct ConicGradientBrush(SharedVector<GradientStop>);
259
260impl ConicGradientBrush {
261 pub fn new(stops: impl IntoIterator<Item = GradientStop>) -> Self {
265 Self(stops.into_iter().collect())
266 }
267
268 pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
270 self.0.iter()
271 }
272}
273
274#[repr(C)]
277#[derive(Copy, Clone, Debug, PartialEq)]
278pub struct GradientStop {
279 pub color: Color,
281 pub position: f32,
283}
284
285pub fn line_for_angle(angle: f32, size: Size2D<f32>) -> (Point2D<f32>, Point2D<f32>) {
287 let angle = (angle + 90.).to_radians();
288 let (s, c) = angle.sin_cos();
289
290 let (a, b) = if s.abs() < f32::EPSILON {
291 let y = size.height / 2.;
292 return if c < 0. {
293 (Point2D::new(0., y), Point2D::new(size.width, y))
294 } else {
295 (Point2D::new(size.width, y), Point2D::new(0., y))
296 };
297 } else if c * s < 0. {
298 let x = (s * size.width + c * size.height) * s / 2.;
300 let y = -c * x / s + size.height;
301 (Point2D::new(x, y), Point2D::new(size.width - x, size.height - y))
302 } else {
303 let x = (s * size.width - c * size.height) * s / 2.;
305 let y = -c * x / s;
306 (Point2D::new(size.width - x, size.height - y), Point2D::new(x, y))
307 };
308
309 if s > 0. {
310 (a, b)
311 } else {
312 (b, a)
313 }
314}
315
316impl InterpolatedPropertyValue for Brush {
317 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
318 match (self, target_value) {
319 (Brush::SolidColor(source_col), Brush::SolidColor(target_col)) => {
320 Brush::SolidColor(source_col.interpolate(target_col, t))
321 }
322 (Brush::SolidColor(col), Brush::LinearGradient(grad)) => {
323 let mut new_grad = grad.clone();
324 for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
325 x.color = col.interpolate(&x.color, t);
326 }
327 Brush::LinearGradient(new_grad)
328 }
329 (a @ Brush::LinearGradient(_), b @ Brush::SolidColor(_)) => {
330 Self::interpolate(b, a, 1. - t)
331 }
332 (Brush::LinearGradient(lhs), Brush::LinearGradient(rhs)) => {
333 if lhs.0.len() < rhs.0.len() {
334 Self::interpolate(target_value, self, 1. - t)
335 } else {
336 let mut new_grad = lhs.clone();
337 let mut iter = new_grad.0.make_mut_slice().iter_mut();
338 {
339 let angle = &mut iter.next().unwrap().position;
340 *angle = angle.interpolate(&rhs.angle(), t);
341 }
342 for s2 in rhs.stops() {
343 let s1 = iter.next().unwrap();
344 s1.color = s1.color.interpolate(&s2.color, t);
345 s1.position = s1.position.interpolate(&s2.position, t);
346 }
347 for x in iter {
348 x.position = x.position.interpolate(&1.0, t);
349 }
350 Brush::LinearGradient(new_grad)
351 }
352 }
353 (Brush::SolidColor(col), Brush::RadialGradient(grad)) => {
354 let mut new_grad = grad.clone();
355 for x in new_grad.0.make_mut_slice().iter_mut() {
356 x.color = col.interpolate(&x.color, t);
357 }
358 Brush::RadialGradient(new_grad)
359 }
360 (a @ Brush::RadialGradient(_), b @ Brush::SolidColor(_)) => {
361 Self::interpolate(b, a, 1. - t)
362 }
363 (Brush::RadialGradient(lhs), Brush::RadialGradient(rhs)) => {
364 if lhs.0.len() < rhs.0.len() {
365 Self::interpolate(target_value, self, 1. - t)
366 } else {
367 let mut new_grad = lhs.clone();
368 let mut iter = new_grad.0.make_mut_slice().iter_mut();
369 let mut last_color = Color::default();
370 for s2 in rhs.stops() {
371 let s1 = iter.next().unwrap();
372 last_color = s2.color;
373 s1.color = s1.color.interpolate(&s2.color, t);
374 s1.position = s1.position.interpolate(&s2.position, t);
375 }
376 for x in iter {
377 x.position = x.position.interpolate(&1.0, t);
378 x.color = x.color.interpolate(&last_color, t);
379 }
380 Brush::RadialGradient(new_grad)
381 }
382 }
383 (Brush::SolidColor(col), Brush::ConicGradient(grad)) => {
384 let mut new_grad = grad.clone();
385 for x in new_grad.0.make_mut_slice().iter_mut() {
386 x.color = col.interpolate(&x.color, t);
387 }
388 Brush::ConicGradient(new_grad)
389 }
390 (a @ Brush::ConicGradient(_), b @ Brush::SolidColor(_)) => {
391 Self::interpolate(b, a, 1. - t)
392 }
393 (Brush::ConicGradient(lhs), Brush::ConicGradient(rhs)) => {
394 if lhs.0.len() < rhs.0.len() {
395 Self::interpolate(target_value, self, 1. - t)
396 } else {
397 let mut new_grad = lhs.clone();
398 let mut iter = new_grad.0.make_mut_slice().iter_mut();
399 for s2 in rhs.stops() {
400 let s1 = iter.next().unwrap();
401 s1.color = s1.color.interpolate(&s2.color, t);
402 s1.position = s1.position.interpolate(&s2.position, t);
403 }
404 for x in iter {
405 x.position = x.position.interpolate(&1.0, t);
406 }
407 Brush::ConicGradient(new_grad)
408 }
409 }
410 (a @ Brush::LinearGradient(_), b @ Brush::RadialGradient(_))
411 | (a @ Brush::RadialGradient(_), b @ Brush::LinearGradient(_))
412 | (a @ Brush::LinearGradient(_), b @ Brush::ConicGradient(_))
413 | (a @ Brush::ConicGradient(_), b @ Brush::LinearGradient(_))
414 | (a @ Brush::RadialGradient(_), b @ Brush::ConicGradient(_))
415 | (a @ Brush::ConicGradient(_), b @ Brush::RadialGradient(_)) => {
416 let color = Color::interpolate(&b.color(), &a.color(), t);
418 if t < 0.5 {
419 Self::interpolate(a, &Brush::SolidColor(color), t * 2.)
420 } else {
421 Self::interpolate(&Brush::SolidColor(color), b, (t - 0.5) * 2.)
422 }
423 }
424 }
425 }
426}
427
428#[test]
429#[allow(clippy::float_cmp)] fn test_linear_gradient_encoding() {
431 let stops: SharedVector<GradientStop> = [
432 GradientStop { position: 0.0, color: Color::from_argb_u8(255, 255, 0, 0) },
433 GradientStop { position: 0.5, color: Color::from_argb_u8(255, 0, 255, 0) },
434 GradientStop { position: 1.0, color: Color::from_argb_u8(255, 0, 0, 255) },
435 ]
436 .into();
437 let grad = LinearGradientBrush::new(256., stops.clone());
438 assert_eq!(grad.angle(), 256.);
439 assert!(grad.stops().eq(stops.iter()));
440}