use crate::Color;
use crate::gradient::{self, Gradient};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Background {
Color(Color),
Gradient(Gradient),
}
impl Background {
pub fn scale_alpha(self, factor: f32) -> Self {
match self {
Self::Color(color) => Self::Color(color.scale_alpha(factor)),
Self::Gradient(gradient) => Self::Gradient(gradient.scale_alpha(factor)),
}
}
pub fn lerp(self, other: Self, amount: f32) -> Self {
match (self, other) {
(Self::Color(from), Self::Color(to)) => Self::Color(from.lerp(to, amount)),
(Self::Gradient(from), Self::Gradient(to)) => {
from.lerp(to, amount).map_or(other, Self::Gradient)
}
_ => other,
}
}
pub fn lerp_maybe(from: Option<Self>, to: Option<Self>, amount: f32) -> Option<Self> {
match (from, to) {
(Some(from), Some(to)) => Some(from.lerp(to, amount)),
(Some(from), None) => Some(from.scale_alpha(1.0 - amount)),
(None, Some(to)) => Some(to.scale_alpha(amount)),
(None, None) => None,
}
}
}
impl From<Color> for Background {
fn from(color: Color) -> Self {
Background::Color(color)
}
}
impl From<Gradient> for Background {
fn from(gradient: Gradient) -> Self {
Background::Gradient(gradient)
}
}
impl From<gradient::Linear> for Background {
fn from(gradient: gradient::Linear) -> Self {
Background::Gradient(Gradient::Linear(gradient))
}
}
impl From<gradient::Conic> for Background {
fn from(gradient: gradient::Conic) -> Self {
Background::Gradient(Gradient::Conic(gradient))
}
}
#[cfg(test)]
mod tests {
use super::Background;
use crate::gradient::Linear;
use crate::{Color, Gradient};
fn gradient(start: Color, end: Color) -> Background {
Background::Gradient(Gradient::Linear(
Linear::new(0.0).add_stop(0.0, start).add_stop(1.0, end),
))
}
#[test]
fn colors_blend() {
let Background::Color(mid) =
Background::Color(Color::BLACK).lerp(Background::Color(Color::WHITE), 0.5)
else {
panic!("expected a flat color");
};
assert!((mid.r - 0.5).abs() < 1e-3);
}
#[test]
fn a_constant_gradient_survives_the_transition() {
let fill = gradient(Color::BLACK, Color::WHITE);
for step in 0..=10 {
let amount = step as f32 / 10.0;
assert_eq!(
fill.lerp(fill, amount),
fill,
"gradient changed at {amount}"
);
}
}
#[test]
fn gradients_blend_stop_wise() {
let from = gradient(Color::BLACK, Color::BLACK);
let to = gradient(Color::WHITE, Color::WHITE);
let Background::Gradient(Gradient::Linear(mid)) = from.lerp(to, 0.5) else {
panic!("expected a linear gradient");
};
for stop in mid.stops.iter().flatten() {
assert!((stop.color.r - 0.5).abs() < 1e-3);
}
}
#[test]
fn a_gradient_and_a_color_step_to_the_target() {
let color = Background::Color(Color::BLACK);
let fill = gradient(Color::WHITE, Color::WHITE);
assert_eq!(color.lerp(fill, 0.5), fill);
assert_eq!(fill.lerp(color, 0.5), color);
}
#[test]
fn an_absent_background_fades_the_other_side() {
let opaque = Color {
a: 1.0,
..Color::BLACK
};
let flat = Background::Color(opaque);
let Some(Background::Color(fading_in)) = Background::lerp_maybe(None, Some(flat), 0.25)
else {
panic!("expected a flat color");
};
let Some(Background::Color(fading_out)) = Background::lerp_maybe(Some(flat), None, 0.25)
else {
panic!("expected a flat color");
};
assert!((fading_in.a - 0.25).abs() < 1e-3);
assert!((fading_out.a - 0.75).abs() < 1e-3);
let Some(Background::Gradient(Gradient::Linear(wash))) =
Background::lerp_maybe(None, Some(gradient(opaque, opaque)), 0.25)
else {
panic!("expected a linear gradient");
};
for stop in wash.stops.iter().flatten() {
assert!((stop.color.a - 0.25).abs() < 1e-3);
}
}
#[test]
fn two_absent_backgrounds_stay_absent() {
assert_eq!(Background::lerp_maybe(None, None, 0.5), None);
}
}