use super::Color;
use crate::properties::InterpolatedPropertyValue;
use crate::SharedVector;
use euclid::default::Point2D;
#[cfg(not(feature = "std"))]
use num_traits::float::Float;
#[derive(Clone, PartialEq, Debug, derive_more::From)]
#[repr(C)]
#[non_exhaustive]
pub enum Brush {
SolidColor(Color),
LinearGradient(LinearGradientBrush),
RadialGradient(RadialGradientBrush),
}
impl Default for Brush {
fn default() -> Self {
Self::SolidColor(Color::default())
}
}
impl Brush {
pub fn color(&self) -> Color {
match self {
Brush::SolidColor(col) => *col,
Brush::LinearGradient(gradient) => {
gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
}
Brush::RadialGradient(gradient) => {
gradient.stops().next().map(|stop| stop.color).unwrap_or_default()
}
}
}
pub fn is_transparent(&self) -> bool {
match self {
Brush::SolidColor(c) => c.alpha() == 0,
Brush::LinearGradient(_) => false,
Brush::RadialGradient(_) => false,
}
}
pub fn is_opaque(&self) -> bool {
match self {
Brush::SolidColor(c) => c.alpha() == 255,
Brush::LinearGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
Brush::RadialGradient(g) => g.stops().all(|s| s.color.alpha() == 255),
}
}
#[must_use]
pub fn brighter(&self, factor: f32) -> Self {
match self {
Brush::SolidColor(c) => Brush::SolidColor(c.brighter(factor)),
Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
g.angle(),
g.stops().map(|s| GradientStop {
color: s.color.brighter(factor),
position: s.position,
}),
)),
Brush::RadialGradient(g) => {
Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
GradientStop { color: s.color.brighter(factor), position: s.position }
})))
}
}
}
#[must_use]
pub fn darker(&self, factor: f32) -> Self {
match self {
Brush::SolidColor(c) => Brush::SolidColor(c.darker(factor)),
Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
g.angle(),
g.stops()
.map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
)),
Brush::RadialGradient(g) => Brush::RadialGradient(RadialGradientBrush::new_circle(
g.stops()
.map(|s| GradientStop { color: s.color.darker(factor), position: s.position }),
)),
}
}
}
#[derive(Clone, PartialEq, Debug)]
#[repr(transparent)]
pub struct LinearGradientBrush(SharedVector<GradientStop>);
impl LinearGradientBrush {
pub fn new(angle: f32, stops: impl IntoIterator<Item = GradientStop>) -> Self {
let stop_iter = stops.into_iter();
let mut encoded_angle_and_stops = SharedVector::with_capacity(stop_iter.size_hint().0 + 1);
encoded_angle_and_stops.push(GradientStop { color: Default::default(), position: angle });
encoded_angle_and_stops.extend(stop_iter);
Self(encoded_angle_and_stops)
}
pub fn angle(&self) -> f32 {
self.0[0].position
}
pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
self.0.iter().skip(1)
}
}
#[derive(Clone, PartialEq, Debug)]
#[repr(transparent)]
pub struct RadialGradientBrush(SharedVector<GradientStop>);
impl RadialGradientBrush {
pub fn new_circle(stops: impl IntoIterator<Item = GradientStop>) -> Self {
Self(stops.into_iter().collect())
}
pub fn stops(&self) -> impl Iterator<Item = &GradientStop> {
self.0.iter()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct GradientStop {
pub color: Color,
pub position: f32,
}
pub fn line_for_angle(angle: f32) -> (Point2D<f32>, Point2D<f32>) {
let angle = angle.to_radians();
let r = (angle.sin().abs() + angle.cos().abs()) / 2.;
let (y, x) = (angle - core::f32::consts::PI / 2.).sin_cos();
let (y, x) = (y * r, x * r);
let start = Point2D::new(0.5 - x, 0.5 - y);
let end = Point2D::new(0.5 + x, 0.5 + y);
(start, end)
}
impl InterpolatedPropertyValue for Brush {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
match (self, target_value) {
(Brush::SolidColor(source_col), Brush::SolidColor(target_col)) => {
Brush::SolidColor(source_col.interpolate(target_col, t))
}
(Brush::SolidColor(col), Brush::LinearGradient(grad)) => {
let mut new_grad = grad.clone();
for x in new_grad.0.make_mut_slice().iter_mut().skip(1) {
x.color = col.interpolate(&x.color, t);
}
Brush::LinearGradient(new_grad)
}
(a @ Brush::LinearGradient(_), b @ Brush::SolidColor(_)) => {
Self::interpolate(b, a, 1. - t)
}
(Brush::LinearGradient(lhs), Brush::LinearGradient(rhs)) => {
if lhs.0.len() < rhs.0.len() {
Self::interpolate(target_value, self, 1. - t)
} else {
let mut new_grad = lhs.clone();
let mut iter = new_grad.0.make_mut_slice().iter_mut();
{
let angle = &mut iter.next().unwrap().position;
*angle = angle.interpolate(&rhs.angle(), t);
}
for s2 in rhs.stops() {
let s1 = iter.next().unwrap();
s1.color = s1.color.interpolate(&s2.color, t);
s1.position = s1.position.interpolate(&s2.position, t);
}
for x in iter {
x.position = x.position.interpolate(&1.0, t);
}
Brush::LinearGradient(new_grad)
}
}
(Brush::SolidColor(col), Brush::RadialGradient(grad)) => {
let mut new_grad = grad.clone();
for x in new_grad.0.make_mut_slice().iter_mut() {
x.color = col.interpolate(&x.color, t);
}
Brush::RadialGradient(new_grad)
}
(a @ Brush::RadialGradient(_), b @ Brush::SolidColor(_)) => {
Self::interpolate(b, a, 1. - t)
}
(Brush::RadialGradient(lhs), Brush::RadialGradient(rhs)) => {
if lhs.0.len() < rhs.0.len() {
Self::interpolate(target_value, self, 1. - t)
} else {
let mut new_grad = lhs.clone();
let mut iter = new_grad.0.make_mut_slice().iter_mut();
let mut last_color = Color::default();
for s2 in rhs.stops() {
let s1 = iter.next().unwrap();
last_color = s2.color;
s1.color = s1.color.interpolate(&s2.color, t);
s1.position = s1.position.interpolate(&s2.position, t);
}
for x in iter {
x.position = x.position.interpolate(&1.0, t);
x.color = x.color.interpolate(&last_color, t);
}
Brush::RadialGradient(new_grad)
}
}
(a @ Brush::LinearGradient(_), b @ Brush::RadialGradient(_))
| (a @ Brush::RadialGradient(_), b @ Brush::LinearGradient(_)) => {
let color = Color::interpolate(&b.color(), &a.color(), t);
if t < 0.5 {
Self::interpolate(a, &Brush::SolidColor(color), t * 2.)
} else {
Self::interpolate(&Brush::SolidColor(color), b, (t - 0.5) * 2.)
}
}
}
}
}
#[test]
#[allow(clippy::float_cmp)] fn test_linear_gradient_encoding() {
let stops: SharedVector<GradientStop> = [
GradientStop { position: 0.0, color: Color::from_argb_u8(255, 255, 0, 0) },
GradientStop { position: 0.5, color: Color::from_argb_u8(255, 0, 255, 0) },
GradientStop { position: 1.0, color: Color::from_argb_u8(255, 0, 0, 255) },
]
.into();
let grad = LinearGradientBrush::new(256., stops.clone());
assert_eq!(grad.angle(), 256.);
assert!(grad.stops().eq(stops.iter()));
}