use crate::{MotionValue, Transition};
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Color {
linear: [f64; 4],
}
impl Color {
pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
assert!(
[r, g, b, a].iter().all(|v| (0.0..=1.0).contains(v)),
"invalid color"
);
let linear = |v: f64| {
if v <= 0.04045 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4)
}
};
Self {
linear: [linear(r), linear(g), linear(b), a],
}
}
pub fn rgb(hex: u32) -> Self {
Self::rgba(
f64::from((hex >> 16) & 255) / 255.0,
f64::from((hex >> 8) & 255) / 255.0,
f64::from(hex & 255) / 255.0,
1.0,
)
}
pub fn channels(self) -> [f32; 4] {
let srgb = |v: f64| {
let v = v.clamp(0.0, 1.0);
if v <= 0.0031308 {
12.92 * v
} else {
1.055 * v.powf(1.0 / 2.4) - 0.055
}
};
[
srgb(self.linear[0]) as f32,
srgb(self.linear[1]) as f32,
srgb(self.linear[2]) as f32,
self.linear[3].clamp(0.0, 1.0) as f32,
]
}
pub fn mix(self, other: Self, progress: f64) -> Self {
assert!(progress.is_finite(), "invalid color progress");
let t = progress.clamp(0.0, 1.0);
Self {
linear: std::array::from_fn(|ix| {
self.linear[ix] + (other.linear[ix] - self.linear[ix]) * t
}),
}
}
}
#[derive(Debug)]
pub struct ColorState {
channels: [MotionValue; 4],
}
impl ColorState {
pub fn new(color: Color) -> Self {
Self {
channels: color.linear.map(MotionValue::new),
}
}
pub fn sample(
&mut self,
color: Color,
transition: Transition,
now: Duration,
reduced_motion: bool,
) -> (Color, bool) {
let mut active = false;
let linear = std::array::from_fn(|ix| {
self.channels[ix].animate(color.linear[ix], transition, now);
let frame = self.channels[ix].sample(now, reduced_motion);
active |= frame.is_animating();
frame.value()
});
(Color { linear }, active)
}
}
impl crate::MotionData<4> for Color {
fn channels(self) -> [f64; 4] {
self.linear
}
fn with_channels(self, values: [f64; 4]) -> Self {
Self { linear: values }
}
fn compatible(self, _: Self) -> bool {
true
}
}