use std::borrow::Cow;
use crate::context::PropertyHandlerContext;
use crate::declaration::{DeclarationBlock, DeclarationList};
use crate::error::{ParserError, PrinterError};
use crate::macros::*;
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
use crate::values::ident::DashedIdent;
use crate::values::number::CSSNumber;
use crate::values::percentage::Percentage;
use crate::values::size::Size2D;
use crate::values::string::CSSString;
use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
use itertools::izip;
use smallvec::SmallVec;
use super::{LengthPercentage, LengthPercentageOrAuto};
#[derive(Debug, Clone, PartialEq, Parse)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum AnimationName<'i> {
None,
#[cfg_attr(feature = "serde", serde(borrow))]
Ident(CustomIdent<'i>),
#[cfg_attr(feature = "serde", serde(borrow))]
String(CSSString<'i>),
}
impl<'i> ToCss for AnimationName<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let css_module_animation_enabled =
dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
match self {
AnimationName::None => dest.write_str("none"),
AnimationName::Ident(s) => {
if css_module_animation_enabled {
if let Some(css_module) = &mut dest.css_module {
css_module.reference(&s.0, dest.loc.source_index)
}
}
s.to_css_with_options(dest, css_module_animation_enabled)
}
AnimationName::String(s) => {
if css_module_animation_enabled {
if let Some(css_module) = &mut dest.css_module {
css_module.reference(&s, dest.loc.source_index)
}
}
match_ignore_ascii_case! { &*s,
"none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
serialize_string(&s, dest)?;
Ok(())
},
_ => {
dest.write_ident(s.as_ref(), css_module_animation_enabled)
}
}
}
}
}
}
pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum AnimationIterationCount {
Number(CSSNumber),
Infinite,
}
impl Default for AnimationIterationCount {
fn default() -> Self {
AnimationIterationCount::Number(1.0)
}
}
enum_property! {
pub enum AnimationDirection {
Normal,
Reverse,
Alternate,
AlternateReverse,
}
}
impl Default for AnimationDirection {
fn default() -> Self {
AnimationDirection::Normal
}
}
enum_property! {
pub enum AnimationPlayState {
Running,
Paused,
}
}
impl Default for AnimationPlayState {
fn default() -> Self {
AnimationPlayState::Running
}
}
enum_property! {
pub enum AnimationFillMode {
None,
Forwards,
Backwards,
Both,
}
}
impl Default for AnimationFillMode {
fn default() -> Self {
AnimationFillMode::None
}
}
enum_property! {
pub enum AnimationComposition {
Replace,
Add,
Accumulate,
}
}
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum AnimationTimeline<'i> {
Auto,
None,
#[cfg_attr(feature = "serde", serde(borrow))]
DashedIdent(DashedIdent<'i>),
Scroll(ScrollTimeline),
View(ViewTimeline),
}
impl<'i> Default for AnimationTimeline<'i> {
fn default() -> Self {
AnimationTimeline::Auto
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct ScrollTimeline {
pub scroller: Scroller,
pub axis: ScrollAxis,
}
impl<'i> Parse<'i> for ScrollTimeline {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
input.expect_function_matching("scroll")?;
input.parse_nested_block(|input| {
let mut scroller = None;
let mut axis = None;
loop {
if scroller.is_none() {
scroller = input.try_parse(Scroller::parse).ok();
}
if axis.is_none() {
axis = input.try_parse(ScrollAxis::parse).ok();
if axis.is_some() {
continue;
}
}
break;
}
Ok(ScrollTimeline {
scroller: scroller.unwrap_or_default(),
axis: axis.unwrap_or_default(),
})
})
}
}
impl ToCss for ScrollTimeline {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
dest.write_str("scroll(")?;
let mut needs_space = false;
if self.scroller != Scroller::default() {
self.scroller.to_css(dest)?;
needs_space = true;
}
if self.axis != ScrollAxis::default() {
if needs_space {
dest.write_char(' ')?;
}
self.axis.to_css(dest)?;
}
dest.write_char(')')
}
}
enum_property! {
pub enum Scroller {
"root": Root,
"nearest": Nearest,
"self": SelfElement,
}
}
impl Default for Scroller {
fn default() -> Self {
Scroller::Nearest
}
}
enum_property! {
pub enum ScrollAxis {
Block,
Inline,
X,
Y,
}
}
impl Default for ScrollAxis {
fn default() -> Self {
ScrollAxis::Block
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct ViewTimeline {
pub axis: ScrollAxis,
pub inset: Size2D<LengthPercentageOrAuto>,
}
impl<'i> Parse<'i> for ViewTimeline {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
input.expect_function_matching("view")?;
input.parse_nested_block(|input| {
let mut axis = None;
let mut inset = None;
loop {
if axis.is_none() {
axis = input.try_parse(ScrollAxis::parse).ok();
}
if inset.is_none() {
inset = input.try_parse(Size2D::parse).ok();
if inset.is_some() {
continue;
}
}
break;
}
Ok(ViewTimeline {
axis: axis.unwrap_or_default(),
inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
})
})
}
}
impl ToCss for ViewTimeline {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
dest.write_str("view(")?;
let mut needs_space = false;
if self.axis != ScrollAxis::default() {
self.axis.to_css(dest)?;
needs_space = true;
}
if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto {
if needs_space {
dest.write_char(' ')?;
}
self.inset.to_css(dest)?;
}
dest.write_char(')')
}
}
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum TimelineRangeName {
Cover,
Contain,
Entry,
Exit,
EntryCrossing,
ExitCrossing,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum AnimationAttachmentRange {
Normal,
#[cfg_attr(feature = "serde", serde(untagged))]
LengthPercentage(LengthPercentage),
#[cfg_attr(feature = "serde", serde(untagged))]
TimelineRange {
name: TimelineRangeName,
offset: LengthPercentage,
},
}
impl<'i> AnimationAttachmentRange {
fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
return Ok(AnimationAttachmentRange::Normal);
}
if let Ok(val) = input.try_parse(LengthPercentage::parse) {
return Ok(AnimationAttachmentRange::LengthPercentage(val));
}
let name = TimelineRangeName::parse(input)?;
let offset = input
.try_parse(LengthPercentage::parse)
.unwrap_or(LengthPercentage::Percentage(Percentage(default)));
Ok(AnimationAttachmentRange::TimelineRange { name, offset })
}
fn to_css<W>(&self, dest: &mut Printer<W>, default: f32) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Self::Normal => dest.write_str("normal"),
Self::LengthPercentage(val) => val.to_css(dest),
Self::TimelineRange { name, offset } => {
name.to_css(dest)?;
if *offset != LengthPercentage::Percentage(Percentage(default)) {
dest.write_char(' ')?;
offset.to_css(dest)?;
}
Ok(())
}
}
}
}
impl Default for AnimationAttachmentRange {
fn default() -> Self {
AnimationAttachmentRange::Normal
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct AnimationRangeStart(pub AnimationAttachmentRange);
impl<'i> Parse<'i> for AnimationRangeStart {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let range = AnimationAttachmentRange::parse(input, 0.0)?;
Ok(Self(range))
}
}
impl ToCss for AnimationRangeStart {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.0.to_css(dest, 0.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct AnimationRangeEnd(pub AnimationAttachmentRange);
impl<'i> Parse<'i> for AnimationRangeEnd {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let range = AnimationAttachmentRange::parse(input, 1.0)?;
Ok(Self(range))
}
}
impl ToCss for AnimationRangeEnd {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.0.to_css(dest, 1.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct AnimationRange {
pub start: AnimationRangeStart,
pub end: AnimationRangeEnd,
}
impl<'i> Parse<'i> for AnimationRange {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let start = AnimationRangeStart::parse(input)?;
let end = input
.try_parse(AnimationRangeStart::parse)
.map(|r| AnimationRangeEnd(r.0))
.unwrap_or_else(|_| {
match &start.0 {
AnimationAttachmentRange::TimelineRange { name, .. } => {
AnimationRangeEnd(AnimationAttachmentRange::TimelineRange {
name: name.clone(),
offset: LengthPercentage::Percentage(Percentage(1.0)),
})
}
_ => AnimationRangeEnd(AnimationAttachmentRange::default()),
}
});
Ok(AnimationRange { start, end })
}
}
impl ToCss for AnimationRange {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.start.to_css(dest)?;
let omit_end = match (&self.start.0, &self.end.0) {
(
AnimationAttachmentRange::TimelineRange { name: start_name, .. },
AnimationAttachmentRange::TimelineRange {
name: end_name,
offset: end_offset,
},
) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)),
(_, end) => *end == AnimationAttachmentRange::default(),
};
if !omit_end {
dest.write_char(' ')?;
self.end.to_css(dest)?;
}
Ok(())
}
}
define_list_shorthand! {
pub struct Animation<'i>(VendorPrefix) {
#[cfg_attr(feature = "serde", serde(borrow))]
name: AnimationName(AnimationName<'i>, VendorPrefix),
duration: AnimationDuration(Time, VendorPrefix),
timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix),
iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix),
direction: AnimationDirection(AnimationDirection, VendorPrefix),
play_state: AnimationPlayState(AnimationPlayState, VendorPrefix),
delay: AnimationDelay(Time, VendorPrefix),
fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),
timeline: AnimationTimeline(AnimationTimeline<'i>),
}
}
impl<'i> Parse<'i> for Animation<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut name = None;
let mut duration = None;
let mut timing_function = None;
let mut iteration_count = None;
let mut direction = None;
let mut play_state = None;
let mut delay = None;
let mut fill_mode = None;
let mut timeline = None;
macro_rules! parse_prop {
($var: ident, $type: ident) => {
if $var.is_none() {
if let Ok(value) = input.try_parse($type::parse) {
$var = Some(value);
continue;
}
}
};
}
loop {
parse_prop!(duration, Time);
parse_prop!(timing_function, EasingFunction);
parse_prop!(delay, Time);
parse_prop!(iteration_count, AnimationIterationCount);
parse_prop!(direction, AnimationDirection);
parse_prop!(fill_mode, AnimationFillMode);
parse_prop!(play_state, AnimationPlayState);
parse_prop!(name, AnimationName);
parse_prop!(timeline, AnimationTimeline);
break;
}
Ok(Animation {
name: name.unwrap_or(AnimationName::None),
duration: duration.unwrap_or(Time::Seconds(0.0)),
timing_function: timing_function.unwrap_or(EasingFunction::Ease),
iteration_count: iteration_count.unwrap_or(AnimationIterationCount::Number(1.0)),
direction: direction.unwrap_or(AnimationDirection::Normal),
play_state: play_state.unwrap_or(AnimationPlayState::Running),
delay: delay.unwrap_or(Time::Seconds(0.0)),
fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),
timeline: timeline.unwrap_or(AnimationTimeline::Auto),
})
}
}
impl<'i> ToCss for Animation<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match &self.name {
AnimationName::None => {}
AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => {
if !self.duration.is_zero() || !self.delay.is_zero() {
self.duration.to_css(dest)?;
dest.write_char(' ')?;
}
if !self.timing_function.is_ease() || EasingFunction::is_ident(&name) {
self.timing_function.to_css(dest)?;
dest.write_char(' ')?;
}
if !self.delay.is_zero() {
self.delay.to_css(dest)?;
dest.write_char(' ')?;
}
if self.iteration_count != AnimationIterationCount::default() || name.as_ref() == "infinite" {
self.iteration_count.to_css(dest)?;
dest.write_char(' ')?;
}
if self.direction != AnimationDirection::default() || AnimationDirection::parse_string(&name).is_ok() {
self.direction.to_css(dest)?;
dest.write_char(' ')?;
}
if self.fill_mode != AnimationFillMode::default()
|| (!name.eq_ignore_ascii_case("none") && AnimationFillMode::parse_string(&name).is_ok())
{
self.fill_mode.to_css(dest)?;
dest.write_char(' ')?;
}
if self.play_state != AnimationPlayState::default() || AnimationPlayState::parse_string(&name).is_ok() {
self.play_state.to_css(dest)?;
dest.write_char(' ')?;
}
}
}
self.name.to_css(dest)?;
if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() {
dest.write_char(' ')?;
self.timeline.to_css(dest)?;
}
Ok(())
}
}
pub type AnimationList<'i> = SmallVec<[Animation<'i>; 1]>;
#[derive(Default)]
pub(crate) struct AnimationHandler<'i> {
names: Option<(SmallVec<[AnimationName<'i>; 1]>, VendorPrefix)>,
durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,
iteration_counts: Option<(SmallVec<[AnimationIterationCount; 1]>, VendorPrefix)>,
directions: Option<(SmallVec<[AnimationDirection; 1]>, VendorPrefix)>,
play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>,
delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>,
timelines: Option<SmallVec<[AnimationTimeline<'i>; 1]>>,
range_starts: Option<SmallVec<[AnimationRangeStart; 1]>>,
range_ends: Option<SmallVec<[AnimationRangeEnd; 1]>>,
has_any: bool,
}
impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
fn handle_property(
&mut self,
property: &Property<'i>,
dest: &mut DeclarationList<'i>,
context: &mut PropertyHandlerContext<'i, '_>,
) -> bool {
macro_rules! maybe_flush {
($prop: ident, $val: expr, $vp: ident) => {{
if let Some((val, prefixes)) = &self.$prop {
if val != $val && !prefixes.contains(*$vp) {
self.flush(dest, context);
}
}
}};
}
macro_rules! property {
($prop: ident, $val: expr, $vp: ident) => {{
maybe_flush!($prop, $val, $vp);
if let Some((val, prefixes)) = &mut self.$prop {
*val = $val.clone();
*prefixes |= *$vp;
} else {
self.$prop = Some(($val.clone(), *$vp));
self.has_any = true;
}
}};
}
match property {
Property::AnimationName(val, vp) => property!(names, val, vp),
Property::AnimationDuration(val, vp) => property!(durations, val, vp),
Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),
Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),
Property::AnimationDirection(val, vp) => property!(directions, val, vp),
Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),
Property::AnimationDelay(val, vp) => property!(delays, val, vp),
Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
Property::AnimationTimeline(val) => {
self.timelines = Some(val.clone());
self.has_any = true;
}
Property::AnimationRangeStart(val) => {
self.range_starts = Some(val.clone());
self.has_any = true;
}
Property::AnimationRangeEnd(val) => {
self.range_ends = Some(val.clone());
self.has_any = true;
}
Property::AnimationRange(val) => {
self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect());
self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect());
self.has_any = true;
}
Property::Animation(val, vp) => {
let names = val.iter().map(|b| b.name.clone()).collect();
maybe_flush!(names, &names, vp);
let durations = val.iter().map(|b| b.duration.clone()).collect();
maybe_flush!(durations, &durations, vp);
let timing_functions = val.iter().map(|b| b.timing_function.clone()).collect();
maybe_flush!(timing_functions, &timing_functions, vp);
let iteration_counts = val.iter().map(|b| b.iteration_count.clone()).collect();
maybe_flush!(iteration_counts, &iteration_counts, vp);
let directions = val.iter().map(|b| b.direction.clone()).collect();
maybe_flush!(directions, &directions, vp);
let play_states = val.iter().map(|b| b.play_state.clone()).collect();
maybe_flush!(play_states, &play_states, vp);
let delays = val.iter().map(|b| b.delay.clone()).collect();
maybe_flush!(delays, &delays, vp);
let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect();
maybe_flush!(fill_modes, &fill_modes, vp);
self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect());
property!(names, &names, vp);
property!(durations, &durations, vp);
property!(timing_functions, &timing_functions, vp);
property!(iteration_counts, &iteration_counts, vp);
property!(directions, &directions, vp);
property!(play_states, &play_states, vp);
property!(delays, &delays, vp);
property!(fill_modes, &fill_modes, vp);
self.range_starts = None;
self.range_ends = None;
}
Property::Unparsed(val) if is_animation_property(&val.property_id) => {
let mut val = Cow::Borrowed(val);
if matches!(val.property_id, PropertyId::Animation(_)) {
use crate::properties::custom::Token;
for token in &mut val.to_mut().value.0 {
match token {
TokenOrValue::Token(Token::Ident(id)) => {
if AnimationDirection::parse_string(&id).is_err()
&& AnimationPlayState::parse_string(&id).is_err()
&& AnimationFillMode::parse_string(&id).is_err()
&& !EasingFunction::is_ident(&id)
&& id.as_ref() != "infinite"
&& id.as_ref() != "auto"
{
*token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));
}
}
TokenOrValue::Token(Token::String(s)) => {
*token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone())));
}
_ => {}
}
}
self.range_starts = None;
self.range_ends = None;
}
self.flush(dest, context);
dest.push(Property::Unparsed(
val.get_prefixed(context.targets, Feature::Animation),
));
}
_ => return false,
}
true
}
fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
self.flush(dest, context);
}
}
impl<'i> AnimationHandler<'i> {
fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
if !self.has_any {
return;
}
self.has_any = false;
let mut names = std::mem::take(&mut self.names);
let mut durations = std::mem::take(&mut self.durations);
let mut timing_functions = std::mem::take(&mut self.timing_functions);
let mut iteration_counts = std::mem::take(&mut self.iteration_counts);
let mut directions = std::mem::take(&mut self.directions);
let mut play_states = std::mem::take(&mut self.play_states);
let mut delays = std::mem::take(&mut self.delays);
let mut fill_modes = std::mem::take(&mut self.fill_modes);
let mut timelines_value = std::mem::take(&mut self.timelines);
let range_starts = std::mem::take(&mut self.range_starts);
let range_ends = std::mem::take(&mut self.range_ends);
if let (
Some((names, names_vp)),
Some((durations, durations_vp)),
Some((timing_functions, timing_functions_vp)),
Some((iteration_counts, iteration_counts_vp)),
Some((directions, directions_vp)),
Some((play_states, play_states_vp)),
Some((delays, delays_vp)),
Some((fill_modes, fill_modes_vp)),
) = (
&mut names,
&mut durations,
&mut timing_functions,
&mut iteration_counts,
&mut directions,
&mut play_states,
&mut delays,
&mut fill_modes,
) {
let len = names.len();
let intersection = *names_vp
& *durations_vp
& *timing_functions_vp
& *iteration_counts_vp
& *directions_vp
& *play_states_vp
& *delays_vp
& *fill_modes_vp;
let mut timelines = if let Some(timelines) = &mut timelines_value {
Cow::Borrowed(timelines)
} else if !intersection.contains(VendorPrefix::None) {
Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect())
} else {
Cow::Owned(SmallVec::new())
};
if !intersection.is_empty()
&& durations.len() == len
&& timing_functions.len() == len
&& iteration_counts.len() == len
&& directions.len() == len
&& play_states.len() == len
&& delays.len() == len
&& fill_modes.len() == len
&& timelines.len() == len
{
let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto)
&& (intersection != VendorPrefix::None
|| !context
.targets
.is_compatible(crate::compat::Feature::AnimationTimelineShorthand))
{
Some(Property::AnimationTimeline(timelines.clone().into_owned()))
} else {
None
};
let animations = izip!(
names.drain(..),
durations.drain(..),
timing_functions.drain(..),
iteration_counts.drain(..),
directions.drain(..),
play_states.drain(..),
delays.drain(..),
fill_modes.drain(..),
timelines.to_mut().drain(..)
)
.map(
|(
name,
duration,
timing_function,
iteration_count,
direction,
play_state,
delay,
fill_mode,
timeline,
)| {
Animation {
name,
duration,
timing_function,
iteration_count,
direction,
play_state,
delay,
fill_mode,
timeline: if timeline_property.is_some() {
AnimationTimeline::Auto
} else {
timeline
},
}
},
)
.collect();
let prefix = context.targets.prefixes(intersection, Feature::Animation);
dest.push(Property::Animation(animations, prefix));
names_vp.remove(intersection);
durations_vp.remove(intersection);
timing_functions_vp.remove(intersection);
iteration_counts_vp.remove(intersection);
directions_vp.remove(intersection);
play_states_vp.remove(intersection);
delays_vp.remove(intersection);
fill_modes_vp.remove(intersection);
if let Some(p) = timeline_property {
dest.push(p);
}
timelines_value = None;
}
}
macro_rules! prop {
($var: ident, $property: ident) => {
if let Some((val, vp)) = $var {
if !vp.is_empty() {
let prefix = context.targets.prefixes(vp, Feature::$property);
dest.push(Property::$property(val, prefix))
}
}
};
}
prop!(names, AnimationName);
prop!(durations, AnimationDuration);
prop!(timing_functions, AnimationTimingFunction);
prop!(iteration_counts, AnimationIterationCount);
prop!(directions, AnimationDirection);
prop!(play_states, AnimationPlayState);
prop!(delays, AnimationDelay);
prop!(fill_modes, AnimationFillMode);
if let Some(val) = timelines_value {
dest.push(Property::AnimationTimeline(val));
}
match (range_starts, range_ends) {
(Some(range_starts), Some(range_ends)) => {
if range_starts.len() == range_ends.len() {
dest.push(Property::AnimationRange(
range_starts
.into_iter()
.zip(range_ends.into_iter())
.map(|(start, end)| AnimationRange { start, end })
.collect(),
));
} else {
dest.push(Property::AnimationRangeStart(range_starts));
dest.push(Property::AnimationRangeEnd(range_ends));
}
}
(range_starts, range_ends) => {
if let Some(range_starts) = range_starts {
dest.push(Property::AnimationRangeStart(range_starts));
}
if let Some(range_ends) = range_ends {
dest.push(Property::AnimationRangeEnd(range_ends));
}
}
}
}
}
#[inline]
fn is_animation_property(property_id: &PropertyId) -> bool {
match property_id {
PropertyId::AnimationName(_)
| PropertyId::AnimationDuration(_)
| PropertyId::AnimationTimingFunction(_)
| PropertyId::AnimationIterationCount(_)
| PropertyId::AnimationDirection(_)
| PropertyId::AnimationPlayState(_)
| PropertyId::AnimationDelay(_)
| PropertyId::AnimationFillMode(_)
| PropertyId::AnimationComposition
| PropertyId::AnimationTimeline
| PropertyId::AnimationRange
| PropertyId::AnimationRangeStart
| PropertyId::AnimationRangeEnd
| PropertyId::Animation(_) => true,
_ => false,
}
}