use crate::animation::{valid_keyframes, valid_point_animation};
use crate::{Animated, AnimatedPoint, Keyframe, Paint, Vec2};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ValueProblem {
InvalidKeyframes,
OutOfRange,
PaintKeyframesDiffer,
}
#[derive(Clone, Debug)]
pub(crate) struct ValueIssue {
pub(crate) at: String,
pub(crate) problem: ValueProblem,
}
impl ValueIssue {
pub(crate) fn new(at: impl Into<String>, problem: ValueProblem) -> Self {
Self {
at: at.into(),
problem,
}
}
}
pub(crate) type Checked = Result<(), ValueIssue>;
pub(crate) fn unit(value: f32) -> bool {
(0.0..=1.0).contains(&value)
}
pub(crate) fn non_negative(value: f32) -> bool {
value >= 0.0
}
pub(crate) fn any(_: f32) -> bool {
true
}
pub(crate) fn animated<T>(
animation: &Animated<T>,
at: &str,
check: impl Fn(&T) -> bool,
) -> Checked {
let values_valid = match animation {
Animated::Constant { value } => check(value),
Animated::Keyframes { keyframes } => {
if !valid_keyframes(keyframes) {
return Err(ValueIssue::new(at, ValueProblem::InvalidKeyframes));
}
keyframes.iter().all(|keyframe| check(&keyframe.value))
}
};
values_valid
.then_some(())
.ok_or_else(|| ValueIssue::new(at, ValueProblem::OutOfRange))
}
pub(crate) fn scalar(animation: &Animated<f32>, at: &str, check: fn(f32) -> bool) -> Checked {
animated(animation, at, |value| value.is_finite() && check(*value))
}
pub(crate) fn vec2(animation: &Animated<Vec2>, at: &str, check: fn(Vec2) -> bool) -> Checked {
animated(animation, at, |value| {
value.x.is_finite() && value.y.is_finite() && check(*value)
})
}
pub(crate) fn point(animation: &AnimatedPoint, at: &str) -> Checked {
if let AnimatedPoint::Keyframes { keyframes } = animation
&& (keyframes.is_empty()
|| keyframes
.windows(2)
.any(|pair| pair[0].time >= pair[1].time)
|| !keyframes
.iter()
.all(|keyframe| keyframe.interpolation.is_valid()))
{
return Err(ValueIssue::new(at, ValueProblem::InvalidKeyframes));
}
valid_point_animation(animation)
.then_some(())
.ok_or_else(|| ValueIssue::new(at, ValueProblem::OutOfRange))
}
pub(crate) fn validate_paint(paint: &Animated<Paint>, at: &str) -> Checked {
animated(paint, at, Paint::is_valid)?;
if let Animated::Keyframes { keyframes } = paint {
let first = paint_structure(&keyframes[0]);
if keyframes[1..]
.iter()
.any(|keyframe| paint_structure(keyframe) != first)
{
return Err(ValueIssue::new(at, ValueProblem::PaintKeyframesDiffer));
}
}
Ok(())
}
fn paint_structure(keyframe: &Keyframe<Paint>) -> (u8, usize, Option<crate::Spread>) {
match &keyframe.value {
Paint::Solid { .. } => (0, 1, None),
Paint::LinearGradient { stops, spread, .. } => (1, stops.len(), Some(*spread)),
Paint::RadialGradient { stops, spread, .. } => (2, stops.len(), Some(*spread)),
Paint::ConicGradient { stops, .. } => (3, stops.len(), None),
}
}