1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5 <%namespace name="helpers" file="/helpers.mako.rs" />
6
7 <%
8 from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case
9 from itertools import groupby
10 %>
11
12 #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID;
13 use itertools::{EitherOrBoth, Itertools};
14 use crate::properties::{CSSWideKeyword, PropertyDeclaration, NonCustomPropertyIterator};
15 use crate::properties::longhands;
16 use crate::properties::longhands::visibility::computed_value::T as Visibility;
17 use crate::properties::LonghandId;
18 use servo_arc::Arc;
19 use smallvec::SmallVec;
20 use std::ptr;
21 use std::mem;
22 use crate::hash::FxHashMap;
23 use super::ComputedValues;
24 use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
25 use crate::values::animated::effects::AnimatedFilter;
26 #[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
27 use crate::values::computed::{ClipRect, Context};
28 use crate::values::computed::ToComputedValue;
29 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
30 use crate::values::generics::effects::Filter;
31 use void::{self, Void};
32
33 /// Convert nsCSSPropertyID to TransitionProperty
34 #[cfg(feature = "gecko")]
35 #[allow(non_upper_case_globals)]
36 impl From<nsCSSPropertyID> for TransitionProperty {
from(property: nsCSSPropertyID) -> TransitionProperty37 fn from(property: nsCSSPropertyID) -> TransitionProperty {
38 use properties::ShorthandId;
39 match property {
40 % for prop in data.longhands:
41 ${prop.nscsspropertyid()} => {
42 TransitionProperty::Longhand(LonghandId::${prop.camel_case})
43 }
44 % endfor
45 % for prop in data.shorthands_except_all():
46 ${prop.nscsspropertyid()} => {
47 TransitionProperty::Shorthand(ShorthandId::${prop.camel_case})
48 }
49 % endfor
50 nsCSSPropertyID::eCSSPropertyExtra_all_properties => {
51 TransitionProperty::Shorthand(ShorthandId::All)
52 }
53 _ => {
54 panic!("non-convertible nsCSSPropertyID")
55 }
56 }
57 }
58 }
59
60 /// An animated property interpolation between two computed values for that
61 /// property.
62 #[derive(Clone, Debug, PartialEq)]
63 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
64 pub enum AnimatedProperty {
65 % for prop in data.longhands:
66 % if prop.animatable and not prop.logical:
67 <%
68 value_type = "longhands::{}::computed_value::T".format(prop.ident)
69 if not prop.is_animatable_with_computed_value:
70 value_type = "<{} as ToAnimatedValue>::AnimatedValue".format(value_type)
71 %>
72 /// ${prop.name}
73 ${prop.camel_case}(${value_type}, ${value_type}),
74 % endif
75 % endfor
76 }
77
78 impl AnimatedProperty {
79 /// Get the id of the property we're animating.
id(&self) -> LonghandId80 pub fn id(&self) -> LonghandId {
81 match *self {
82 % for prop in data.longhands:
83 % if prop.animatable and not prop.logical:
84 AnimatedProperty::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
85 % endif
86 % endfor
87 }
88 }
89
90 /// Get the name of this property.
name(&self) -> &'static str91 pub fn name(&self) -> &'static str {
92 self.id().name()
93 }
94
95 /// Whether this interpolation does animate, that is, whether the start and
96 /// end values are different.
does_animate(&self) -> bool97 pub fn does_animate(&self) -> bool {
98 match *self {
99 % for prop in data.longhands:
100 % if prop.animatable and not prop.logical:
101 AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
102 % endif
103 % endfor
104 }
105 }
106
107 /// Whether an animated property has the same end value as another.
has_the_same_end_value_as(&self, other: &Self) -> bool108 pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
109 match (self, other) {
110 % for prop in data.longhands:
111 % if prop.animatable and not prop.logical:
112 (&AnimatedProperty::${prop.camel_case}(_, ref this_end_value),
113 &AnimatedProperty::${prop.camel_case}(_, ref other_end_value)) => {
114 this_end_value == other_end_value
115 }
116 % endif
117 % endfor
118 _ => false,
119 }
120 }
121
122 /// Update `style` with the proper computed style corresponding to this
123 /// animation at `progress`.
124 #[cfg_attr(feature = "gecko", allow(unused))]
update(&self, style: &mut ComputedValues, progress: f64)125 pub fn update(&self, style: &mut ComputedValues, progress: f64) {
126 #[cfg(feature = "servo")]
127 {
128 match *self {
129 % for prop in data.longhands:
130 % if prop.animatable and not prop.logical:
131 AnimatedProperty::${prop.camel_case}(ref from, ref to) => {
132 // https://drafts.csswg.org/web-animations/#discrete-animation-type
133 % if prop.animation_value_type == "discrete":
134 let value = if progress < 0.5 { from.clone() } else { to.clone() };
135 % else:
136 let value = match from.animate(to, Procedure::Interpolate { progress }) {
137 Ok(value) => value,
138 Err(()) => return,
139 };
140 % endif
141 % if not prop.is_animatable_with_computed_value:
142 let value: longhands::${prop.ident}::computed_value::T =
143 ToAnimatedValue::from_animated_value(value);
144 % endif
145 style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
146 }
147 % endif
148 % endfor
149 }
150 }
151 }
152
153 /// Get an animatable value from a transition-property, an old style, and a
154 /// new style.
from_longhand( property: LonghandId, old_style: &ComputedValues, new_style: &ComputedValues, ) -> Option<AnimatedProperty>155 pub fn from_longhand(
156 property: LonghandId,
157 old_style: &ComputedValues,
158 new_style: &ComputedValues,
159 ) -> Option<AnimatedProperty> {
160 // FIXME(emilio): Handle the case where old_style and new_style's
161 // writing mode differ.
162 let property = property.to_physical(new_style.writing_mode);
163 Some(match property {
164 % for prop in data.longhands:
165 % if prop.animatable and not prop.logical:
166 LonghandId::${prop.camel_case} => {
167 let old_computed = old_style.clone_${prop.ident}();
168 let new_computed = new_style.clone_${prop.ident}();
169 AnimatedProperty::${prop.camel_case}(
170 % if prop.is_animatable_with_computed_value:
171 old_computed,
172 new_computed,
173 % else:
174 old_computed.to_animated_value(),
175 new_computed.to_animated_value(),
176 % endif
177 )
178 }
179 % endif
180 % endfor
181 _ => return None,
182 })
183 }
184 }
185
186 /// A collection of AnimationValue that were composed on an element.
187 /// This HashMap stores the values that are the last AnimationValue to be
188 /// composed for each TransitionProperty.
189 pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
190
191 /// An enum to represent a single computed value belonging to an animated
192 /// property in order to be interpolated with another one. When interpolating,
193 /// both values need to belong to the same property.
194 ///
195 /// This is different to AnimatedProperty in the sense that AnimatedProperty
196 /// also knows the final value to be used during the animation.
197 ///
198 /// This is to be used in Gecko integration code.
199 ///
200 /// FIXME: We need to add a path for custom properties, but that's trivial after
201 /// this (is a similar path to that of PropertyDeclaration).
202 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
203 #[derive(Debug)]
204 #[repr(u16)]
205 pub enum AnimationValue {
206 % for prop in data.longhands:
207 /// `${prop.name}`
208 % if prop.animatable and not prop.logical:
209 ${prop.camel_case}(${prop.animated_type()}),
210 % else:
211 ${prop.camel_case}(Void),
212 % endif
213 % endfor
214 }
215
216 <%
217 animated = []
218 unanimated = []
219 animated_with_logical = []
220 for prop in data.longhands:
221 if prop.animatable:
222 animated_with_logical.append(prop)
223 if prop.animatable and not prop.logical:
224 animated.append(prop)
225 else:
226 unanimated.append(prop)
227 %>
228
229 #[repr(C)]
230 struct AnimationValueVariantRepr<T> {
231 tag: u16,
232 value: T
233 }
234
235 impl Clone for AnimationValue {
236 #[inline]
clone(&self) -> Self237 fn clone(&self) -> Self {
238 use self::AnimationValue::*;
239
240 <%
241 [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())]
242 %>
243
244 let self_tag = unsafe { *(self as *const _ as *const u16) };
245 if self_tag <= LonghandId::${copy[-1].camel_case} as u16 {
246 #[derive(Clone, Copy)]
247 #[repr(u16)]
248 enum CopyVariants {
249 % for prop in copy:
250 _${prop.camel_case}(${prop.animated_type()}),
251 % endfor
252 }
253
254 unsafe {
255 let mut out = mem::MaybeUninit::uninit();
256 ptr::write(
257 out.as_mut_ptr() as *mut CopyVariants,
258 *(self as *const _ as *const CopyVariants),
259 );
260 return out.assume_init();
261 }
262 }
263
264 match *self {
265 % for ty, props in groupby(others, key=lambda x: x.animated_type()):
266 <% props = list(props) %>
267 ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
268 % if len(props) == 1:
269 ${props[0].camel_case}(value.clone())
270 % else:
271 unsafe {
272 let mut out = mem::MaybeUninit::uninit();
273 ptr::write(
274 out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
275 AnimationValueVariantRepr {
276 tag: *(self as *const _ as *const u16),
277 value: value.clone(),
278 },
279 );
280 out.assume_init()
281 }
282 % endif
283 }
284 % endfor
285 _ => unsafe { debug_unreachable!() }
286 }
287 }
288 }
289
290 impl PartialEq for AnimationValue {
291 #[inline]
eq(&self, other: &Self) -> bool292 fn eq(&self, other: &Self) -> bool {
293 use self::AnimationValue::*;
294
295 unsafe {
296 let this_tag = *(self as *const _ as *const u16);
297 let other_tag = *(other as *const _ as *const u16);
298 if this_tag != other_tag {
299 return false;
300 }
301
302 match *self {
303 % for ty, props in groupby(animated, key=lambda x: x.animated_type()):
304 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
305 let other_repr =
306 &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
307 *this == other_repr.value
308 }
309 % endfor
310 ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
311 void::unreachable(void)
312 }
313 }
314 }
315 }
316 }
317
318 impl AnimationValue {
319 /// Returns the longhand id this animated value corresponds to.
320 #[inline]
id(&self) -> LonghandId321 pub fn id(&self) -> LonghandId {
322 let id = unsafe { *(self as *const _ as *const LonghandId) };
323 debug_assert_eq!(id, match *self {
324 % for prop in data.longhands:
325 % if prop.animatable and not prop.logical:
326 AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
327 % else:
328 AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
329 % endif
330 % endfor
331 });
332 id
333 }
334
335 /// "Uncompute" this animation value in order to be used inside the CSS
336 /// cascade.
uncompute(&self) -> PropertyDeclaration337 pub fn uncompute(&self) -> PropertyDeclaration {
338 use crate::properties::longhands;
339 use self::AnimationValue::*;
340
341 use super::PropertyDeclarationVariantRepr;
342
343 match *self {
344 <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %>
345 % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc):
346 <% props = list(props) %>
347 ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
348 % if not computed:
349 let ref value = ToAnimatedValue::from_animated_value(value.clone());
350 % endif
351 let value = ${ty}::from_computed_value(&value);
352 % if boxed:
353 let value = Box::new(value);
354 % endif
355 % if len(props) == 1:
356 PropertyDeclaration::${props[0].camel_case}(value)
357 % else:
358 unsafe {
359 let mut out = mem::MaybeUninit::uninit();
360 ptr::write(
361 out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>,
362 PropertyDeclarationVariantRepr {
363 tag: *(self as *const _ as *const u16),
364 value,
365 },
366 );
367 out.assume_init()
368 }
369 % endif
370 }
371 % endfor
372 ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
373 void::unreachable(void)
374 }
375 }
376 }
377
378 /// Construct an AnimationValue from a property declaration.
from_declaration( decl: &PropertyDeclaration, context: &mut Context, extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, initial: &ComputedValues ) -> Option<Self>379 pub fn from_declaration(
380 decl: &PropertyDeclaration,
381 context: &mut Context,
382 extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
383 initial: &ComputedValues
384 ) -> Option<Self> {
385 use super::PropertyDeclarationVariantRepr;
386
387 <%
388 keyfunc = lambda x: (
389 x.specified_type(),
390 x.animated_type(),
391 x.boxed,
392 not x.is_animatable_with_computed_value,
393 x.style_struct.inherited,
394 x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko",
395 )
396 %>
397
398 let animatable = match *decl {
399 % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
400 ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
401 let decl_repr = unsafe {
402 &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
403 };
404 let longhand_id = unsafe {
405 *(&decl_repr.tag as *const u16 as *const LonghandId)
406 };
407 % if inherit:
408 context.for_non_inherited_property = None;
409 % else:
410 context.for_non_inherited_property = Some(longhand_id);
411 % endif
412 % if system:
413 if let Some(sf) = value.get_system() {
414 longhands::system_font::resolve_system_font(sf, context)
415 }
416 % endif
417 % if boxed:
418 let value = (**value).to_computed_value(context);
419 % else:
420 let value = value.to_computed_value(context);
421 % endif
422 % if to_animated:
423 let value = value.to_animated_value();
424 % endif
425
426 unsafe {
427 let mut out = mem::MaybeUninit::uninit();
428 ptr::write(
429 out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
430 AnimationValueVariantRepr {
431 tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
432 value,
433 },
434 );
435 out.assume_init()
436 }
437 }
438 % endfor
439 PropertyDeclaration::CSSWideKeyword(ref declaration) => {
440 match declaration.id {
441 // We put all the animatable properties first in the hopes
442 // that it might increase match locality.
443 % for prop in data.longhands:
444 % if prop.animatable:
445 LonghandId::${prop.camel_case} => {
446 // FIXME(emilio, bug 1533327): I think
447 // CSSWideKeyword::Revert handling is not fine here, but
448 // what to do instead?
449 //
450 // Seems we'd need the computed value as if it was
451 // revert, somehow. Treating it as `unset` seems fine
452 // for now...
453 let style_struct = match declaration.keyword {
454 % if not prop.style_struct.inherited:
455 CSSWideKeyword::Revert |
456 CSSWideKeyword::Unset |
457 % endif
458 CSSWideKeyword::Initial => {
459 initial.get_${prop.style_struct.name_lower}()
460 },
461 % if prop.style_struct.inherited:
462 CSSWideKeyword::Revert |
463 CSSWideKeyword::Unset |
464 % endif
465 CSSWideKeyword::Inherit => {
466 context.builder
467 .get_parent_${prop.style_struct.name_lower}()
468 },
469 };
470 let computed = style_struct
471 % if prop.logical:
472 .clone_${prop.ident}(context.builder.writing_mode);
473 % else:
474 .clone_${prop.ident}();
475 % endif
476
477 % if not prop.is_animatable_with_computed_value:
478 let computed = computed.to_animated_value();
479 % endif
480
481 % if prop.logical:
482 let wm = context.builder.writing_mode;
483 <%helpers:logical_setter_helper name="${prop.name}">
484 <%def name="inner(physical_ident)">
485 AnimationValue::${to_camel_case(physical_ident)}(computed)
486 </%def>
487 </%helpers:logical_setter_helper>
488 % else:
489 AnimationValue::${prop.camel_case}(computed)
490 % endif
491 },
492 % endif
493 % endfor
494 % for prop in data.longhands:
495 % if not prop.animatable:
496 LonghandId::${prop.camel_case} => return None,
497 % endif
498 % endfor
499 }
500 },
501 PropertyDeclaration::WithVariables(ref declaration) => {
502 let substituted = {
503 let custom_properties =
504 extra_custom_properties.or_else(|| context.style().custom_properties());
505
506 declaration.value.substitute_variables(
507 declaration.id,
508 custom_properties,
509 context.quirks_mode,
510 context.device(),
511 )
512 };
513 return AnimationValue::from_declaration(
514 &substituted,
515 context,
516 extra_custom_properties,
517 initial,
518 )
519 },
520 _ => return None // non animatable properties will get included because of shorthands. ignore.
521 };
522 Some(animatable)
523 }
524
525 /// Get an AnimationValue for an AnimatableLonghand from a given computed values.
from_computed_values( property: LonghandId, style: &ComputedValues, ) -> Option<Self>526 pub fn from_computed_values(
527 property: LonghandId,
528 style: &ComputedValues,
529 ) -> Option<Self> {
530 let property = property.to_physical(style.writing_mode);
531 Some(match property {
532 % for prop in data.longhands:
533 % if prop.animatable and not prop.logical:
534 LonghandId::${prop.camel_case} => {
535 let computed = style.clone_${prop.ident}();
536 AnimationValue::${prop.camel_case}(
537 % if prop.is_animatable_with_computed_value:
538 computed
539 % else:
540 computed.to_animated_value()
541 % endif
542 )
543 }
544 % endif
545 % endfor
546 _ => return None,
547 })
548 }
549 }
550
animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()>551 fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {
552 if let Procedure::Interpolate { progress } = procedure {
553 Ok(if progress < 0.5 { this.clone() } else { other.clone() })
554 } else {
555 Err(())
556 }
557 }
558
559 impl Animate for AnimationValue {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>560 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
561 Ok(unsafe {
562 use self::AnimationValue::*;
563
564 let this_tag = *(self as *const _ as *const u16);
565 let other_tag = *(other as *const _ as *const u16);
566 if this_tag != other_tag {
567 panic!("Unexpected AnimationValue::animate call");
568 }
569
570 match *self {
571 <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %>
572 % for (ty, discrete), props in groupby(animated, key=keyfunc):
573 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
574 let other_repr =
575 &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
576 % if discrete:
577 let value = animate_discrete(this, &other_repr.value, procedure)?;
578 % else:
579 let value = this.animate(&other_repr.value, procedure)?;
580 % endif
581
582 let mut out = mem::MaybeUninit::uninit();
583 ptr::write(
584 out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
585 AnimationValueVariantRepr {
586 tag: this_tag,
587 value,
588 },
589 );
590 out.assume_init()
591 }
592 % endfor
593 ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
594 void::unreachable(void)
595 }
596 }
597 })
598 }
599 }
600
601 <%
602 nondiscrete = []
603 for prop in animated:
604 if prop.animation_value_type != "discrete":
605 nondiscrete.append(prop)
606 %>
607
608 impl ComputeSquaredDistance for AnimationValue {
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>609 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
610 unsafe {
611 use self::AnimationValue::*;
612
613 let this_tag = *(self as *const _ as *const u16);
614 let other_tag = *(other as *const _ as *const u16);
615 if this_tag != other_tag {
616 panic!("Unexpected AnimationValue::compute_squared_distance call");
617 }
618
619 match *self {
620 % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()):
621 ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
622 let other_repr =
623 &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
624
625 this.compute_squared_distance(&other_repr.value)
626 }
627 % endfor
628 _ => Err(()),
629 }
630 }
631 }
632 }
633
634 impl ToAnimatedZero for AnimationValue {
635 #[inline]
to_animated_zero(&self) -> Result<Self, ()>636 fn to_animated_zero(&self) -> Result<Self, ()> {
637 match *self {
638 % for prop in data.longhands:
639 % if prop.animatable and not prop.logical and prop.animation_value_type != "discrete":
640 AnimationValue::${prop.camel_case}(ref base) => {
641 Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
642 },
643 % endif
644 % endfor
645 _ => Err(()),
646 }
647 }
648 }
649
650 /// A trait to abstract away the different kind of animations over a list that
651 /// there may be.
652 pub trait ListAnimation<T> : Sized {
653 /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> where T: Animate654 fn animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
655 where
656 T: Animate;
657
658 /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()> where T: ComputeSquaredDistance659 fn squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()>
660 where
661 T: ComputeSquaredDistance;
662
663 /// This is the animation used for some of the types like shadows and
664 /// filters, where the interpolation happens with the zero value if one of
665 /// the sides is not present.
animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> where T: Animate + Clone + ToAnimatedZero666 fn animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
667 where
668 T: Animate + Clone + ToAnimatedZero;
669
670 /// This is the animation used for some of the types like shadows and
671 /// filters, where the interpolation happens with the zero value if one of
672 /// the sides is not present.
squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()> where T: ToAnimatedZero + ComputeSquaredDistance673 fn squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()>
674 where
675 T: ToAnimatedZero + ComputeSquaredDistance;
676 }
677
678 macro_rules! animated_list_impl {
679 (<$t:ident> for $ty:ty) => {
680 impl<$t> ListAnimation<$t> for $ty {
681 fn animate_repeatable_list(
682 &self,
683 other: &Self,
684 procedure: Procedure,
685 ) -> Result<Self, ()>
686 where
687 T: Animate,
688 {
689 // If the length of either list is zero, the least common multiple is undefined.
690 if self.is_empty() || other.is_empty() {
691 return Err(());
692 }
693 use num_integer::lcm;
694 let len = lcm(self.len(), other.len());
695 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
696 this.animate(other, procedure)
697 }).collect()
698 }
699
700 fn squared_distance_repeatable_list(
701 &self,
702 other: &Self,
703 ) -> Result<SquaredDistance, ()>
704 where
705 T: ComputeSquaredDistance,
706 {
707 if self.is_empty() || other.is_empty() {
708 return Err(());
709 }
710 use num_integer::lcm;
711 let len = lcm(self.len(), other.len());
712 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
713 this.compute_squared_distance(other)
714 }).sum()
715 }
716
717 fn animate_with_zero(
718 &self,
719 other: &Self,
720 procedure: Procedure,
721 ) -> Result<Self, ()>
722 where
723 T: Animate + Clone + ToAnimatedZero
724 {
725 if procedure == Procedure::Add {
726 return Ok(
727 self.iter().chain(other.iter()).cloned().collect()
728 );
729 }
730 self.iter().zip_longest(other.iter()).map(|it| {
731 match it {
732 EitherOrBoth::Both(this, other) => {
733 this.animate(other, procedure)
734 },
735 EitherOrBoth::Left(this) => {
736 this.animate(&this.to_animated_zero()?, procedure)
737 },
738 EitherOrBoth::Right(other) => {
739 other.to_animated_zero()?.animate(other, procedure)
740 }
741 }
742 }).collect()
743 }
744
745 fn squared_distance_with_zero(
746 &self,
747 other: &Self,
748 ) -> Result<SquaredDistance, ()>
749 where
750 T: ToAnimatedZero + ComputeSquaredDistance
751 {
752 self.iter().zip_longest(other.iter()).map(|it| {
753 match it {
754 EitherOrBoth::Both(this, other) => {
755 this.compute_squared_distance(other)
756 },
757 EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
758 list.to_animated_zero()?.compute_squared_distance(list)
759 },
760 }
761 }).sum()
762 }
763 }
764 }
765 }
766
767 animated_list_impl!(<T> for crate::OwnedSlice<T>);
768 animated_list_impl!(<T> for SmallVec<[T; 1]>);
769 animated_list_impl!(<T> for Vec<T>);
770
771 /// <https://drafts.csswg.org/web-animations-1/#animating-visibility>
772 impl Animate for Visibility {
773 #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>774 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
775 match procedure {
776 Procedure::Interpolate { .. } => {
777 let (this_weight, other_weight) = procedure.weights();
778 match (*self, *other) {
779 (Visibility::Visible, _) => {
780 Ok(if this_weight > 0.0 { *self } else { *other })
781 },
782 (_, Visibility::Visible) => {
783 Ok(if other_weight > 0.0 { *other } else { *self })
784 },
785 _ => Err(()),
786 }
787 },
788 _ => Err(()),
789 }
790 }
791 }
792
793 impl ComputeSquaredDistance for Visibility {
794 #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>795 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
796 Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
797 }
798 }
799
800 impl ToAnimatedZero for Visibility {
801 #[inline]
to_animated_zero(&self) -> Result<Self, ()>802 fn to_animated_zero(&self) -> Result<Self, ()> {
803 Err(())
804 }
805 }
806
807 /// <https://drafts.csswg.org/css-transitions/#animtype-rect>
808 impl Animate for ClipRect {
809 #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>810 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
811 use crate::values::computed::LengthOrAuto;
812 let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| {
813 let result = this.animate(other, procedure)?;
814 if let Procedure::Interpolate { .. } = procedure {
815 return Ok(result);
816 }
817 if result.is_auto() {
818 // FIXME(emilio): Why? A couple SMIL tests fail without this,
819 // but it seems extremely fishy.
820 return Err(());
821 }
822 Ok(result)
823 };
824
825 Ok(ClipRect {
826 top: animate_component(&self.top, &other.top)?,
827 right: animate_component(&self.right, &other.right)?,
828 bottom: animate_component(&self.bottom, &other.bottom)?,
829 left: animate_component(&self.left, &other.left)?,
830 })
831 }
832 }
833
834 <%
835 FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
836 'HueRotate', 'Invert', 'Opacity', 'Saturate',
837 'Sepia' ]
838 %>
839
840 /// <https://drafts.fxtf.org/filters/#animation-of-filters>
841 impl Animate for AnimatedFilter {
animate( &self, other: &Self, procedure: Procedure, ) -> Result<Self, ()>842 fn animate(
843 &self,
844 other: &Self,
845 procedure: Procedure,
846 ) -> Result<Self, ()> {
847 use crate::values::animated::animate_multiplicative_factor;
848 match (self, other) {
849 % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
850 (&Filter::${func}(ref this), &Filter::${func}(ref other)) => {
851 Ok(Filter::${func}(this.animate(other, procedure)?))
852 },
853 % endfor
854 % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
855 (&Filter::${func}(this), &Filter::${func}(other)) => {
856 Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?))
857 },
858 % endfor
859 % if engine == "gecko":
860 (&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => {
861 Ok(Filter::DropShadow(this.animate(other, procedure)?))
862 },
863 % endif
864 _ => Err(()),
865 }
866 }
867 }
868
869 /// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation>
870 impl ToAnimatedZero for AnimatedFilter {
to_animated_zero(&self) -> Result<Self, ()>871 fn to_animated_zero(&self) -> Result<Self, ()> {
872 match *self {
873 % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
874 Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)),
875 % endfor
876 % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
877 Filter::${func}(_) => Ok(Filter::${func}(1.)),
878 % endfor
879 % if engine == "gecko":
880 Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)),
881 % endif
882 _ => Err(()),
883 }
884 }
885 }
886
887 /// An iterator over all the properties that transition on a given style.
888 pub struct TransitionPropertyIterator<'a> {
889 style: &'a ComputedValues,
890 index_range: core::ops::Range<usize>,
891 longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
892 }
893
894 impl<'a> TransitionPropertyIterator<'a> {
895 /// Create a `TransitionPropertyIterator` for the given style.
from_style(style: &'a ComputedValues) -> Self896 pub fn from_style(style: &'a ComputedValues) -> Self {
897 Self {
898 style,
899 index_range: 0..style.get_box().transition_property_count(),
900 longhand_iterator: None,
901 }
902 }
903 }
904
905 /// A single iteration of the TransitionPropertyIterator.
906 pub struct TransitionPropertyIteration {
907 /// The id of the longhand for this property.
908 pub longhand_id: LonghandId,
909
910 /// The index of this property in the list of transition properties for this
911 /// iterator's style.
912 pub index: usize,
913 }
914
915 impl<'a> Iterator for TransitionPropertyIterator<'a> {
916 type Item = TransitionPropertyIteration;
917
next(&mut self) -> Option<Self::Item>918 fn next(&mut self) -> Option<Self::Item> {
919 use crate::values::computed::TransitionProperty;
920 loop {
921 if let Some(ref mut longhand_iterator) = self.longhand_iterator {
922 if let Some(longhand_id) = longhand_iterator.next() {
923 return Some(TransitionPropertyIteration {
924 longhand_id,
925 index: self.index_range.start - 1,
926 });
927 }
928 self.longhand_iterator = None;
929 }
930
931 let index = self.index_range.next()?;
932 match self.style.get_box().transition_property_at(index) {
933 TransitionProperty::Longhand(longhand_id) => {
934 return Some(TransitionPropertyIteration {
935 longhand_id,
936 index,
937 })
938 }
939 // In the other cases, we set up our state so that we are ready to
940 // compute the next value of the iterator and then loop (equivalent
941 // to calling self.next()).
942 TransitionProperty::Shorthand(ref shorthand_id) =>
943 self.longhand_iterator = Some(shorthand_id.longhands()),
944 TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {}
945 }
946 }
947 }
948 }
949