use { crate::{ config::{ AnimationCurveConfig, Animations, context::Context, extractor::{Extractor, ExtractorError, bol, n32, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, }, indexmap::IndexMap, thiserror::Error, }; #[derive(Debug, Error)] pub enum AnimationsParserError { #[error(transparent)] Expected(#[from] UnexpectedDataType), #[error(transparent)] Extract(#[from] ExtractorError), #[error("Expected animation curve to be a string or an array")] CurveType, #[error("Cubic-bezier animation curves must contain exactly four values")] CubicBezierLen, #[error("Cubic-bezier animation curve entries must be finite floats or integers")] CubicBezierValue, #[error("Cubic-bezier x control points must be between 0 and 1")] CubicBezierXRange, } pub struct AnimationsParser<'a>(pub &'a Context<'a>); impl Parser for AnimationsParser<'_> { type Value = Animations; type Error = AnimationsParserError; const EXPECTED: &'static [DataType] = &[DataType::Table]; fn parse_table( &mut self, span: Span, table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); let (enabled, duration_ms, style, curve) = ext.extract(( recover(opt(bol("enabled"))), recover(opt(n32("duration-ms"))), recover(opt(str("style"))), opt(val("curve")), ))?; let curve = match curve { Some(curve) => Some(parse_curve(curve)?), None => None, }; Ok(Animations { enabled: enabled.despan(), duration_ms: duration_ms.despan(), style: style.despan().map(|style| style.to_string()), curve, }) } } fn parse_curve( curve: Spanned<&Value>, ) -> Result> { match curve.value { Value::String(s) => Ok(AnimationCurveConfig::Preset(s.clone())), Value::Array(values) => parse_cubic_bezier(curve.span, values), _ => Err(AnimationsParserError::CurveType.spanned(curve.span)), } } fn parse_cubic_bezier( span: Span, values: &[Spanned], ) -> Result> { if values.len() != 4 { return Err(AnimationsParserError::CubicBezierLen.spanned(span)); } let mut points = [0.0; 4]; for (idx, value) in values.iter().enumerate() { let f = match value.value { Value::Float(f) => f, Value::Integer(i) => i as f64, _ => return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)), }; if !f.is_finite() { return Err(AnimationsParserError::CubicBezierValue.spanned(value.span)); } points[idx] = f as f32; } if !(0.0..=1.0).contains(&points[0]) || !(0.0..=1.0).contains(&points[2]) { return Err(AnimationsParserError::CubicBezierXRange.spanned(span)); } Ok(AnimationCurveConfig::CubicBezier(points)) }