// Go down the composite layer tree, setting properties to match their // content-side counterparts. static void SetShadowProperties(Layer* aLayer) { // FIXME: Bug 717688 -- Do these updates in LayerTransactionParent::RecvUpdate. LayerComposite* layerComposite = aLayer->AsLayerComposite(); // Set the layerComposite's base transform to the layer's base transform. layerComposite->SetShadowTransform(aLayer->GetBaseTransform()); layerComposite->SetShadowTransformSetByAnimation(false); layerComposite->SetShadowVisibleRegion(aLayer->GetVisibleRegion()); layerComposite->SetShadowClipRect(aLayer->GetClipRect()); layerComposite->SetShadowOpacity(aLayer->GetOpacity()); for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { SetShadowProperties(child); } }
static void TranslateShadowLayer2D(Layer* aLayer, const gfxPoint& aTranslation) { // This layer might also be a scrollable layer and have an async transform. // To make sure we don't clobber that, we start with the shadow transform. // Any adjustments to the shadow transform made in this function in previous // frames have been cleared in ClearAsyncTransforms(), so such adjustments // will not compound over successive frames. Matrix layerTransform; if (!aLayer->GetLocalTransform().Is2D(&layerTransform)) { return; } // Apply the 2D translation to the layer transform. layerTransform._31 += aTranslation.x; layerTransform._32 += aTranslation.y; // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. Matrix4x4 layerTransform3D = Matrix4x4::From2D(layerTransform); if (ContainerLayer* c = aLayer->AsContainerLayer()) { layerTransform3D.Scale(1.0f/c->GetPreXScale(), 1.0f/c->GetPreYScale(), 1); } layerTransform3D = layerTransform3D * Matrix4x4().Scale(1.0f/aLayer->GetPostXScale(), 1.0f/aLayer->GetPostYScale(), 1); LayerComposite* layerComposite = aLayer->AsLayerComposite(); layerComposite->SetShadowTransform(layerTransform3D); layerComposite->SetShadowTransformSetByAnimation(false); const nsIntRect* clipRect = aLayer->GetClipRect(); if (clipRect) { nsIntRect transformedClipRect(*clipRect); transformedClipRect.MoveBy(aTranslation.x, aTranslation.y); layerComposite->SetShadowClipRect(&transformedClipRect); } }
static void TranslateShadowLayer2D(Layer* aLayer, const gfxPoint& aTranslation) { gfxMatrix layerTransform; if (!GetBaseTransform2D(aLayer, &layerTransform)) { return; } // Apply the 2D translation to the layer transform. layerTransform.x0 += aTranslation.x; layerTransform.y0 += aTranslation.y; // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. gfx3DMatrix layerTransform3D = gfx3DMatrix::From2D(layerTransform); if (ContainerLayer* c = aLayer->AsContainerLayer()) { layerTransform3D.Scale(1.0f/c->GetPreXScale(), 1.0f/c->GetPreYScale(), 1); } layerTransform3D.ScalePost(1.0f/aLayer->GetPostXScale(), 1.0f/aLayer->GetPostYScale(), 1); LayerComposite* layerComposite = aLayer->AsLayerComposite(); layerComposite->SetShadowTransform(layerTransform3D); layerComposite->SetShadowTransformSetByAnimation(false); const nsIntRect* clipRect = aLayer->GetClipRect(); if (clipRect) { nsIntRect transformedClipRect(*clipRect); transformedClipRect.MoveBy(aTranslation.x, aTranslation.y); layerComposite->SetShadowClipRect(&transformedClipRect); } }
// Go down shadow layer tree and apply transformations for scrollable layers. static void TransformShadowTree(nsDisplayListBuilder* aBuilder, nsFrameLoader* aFrameLoader, nsIFrame* aFrame, Layer* aLayer, const ViewTransform& aTransform, float aTempScaleDiffX = 1.0, float aTempScaleDiffY = 1.0) { LayerComposite* shadow = aLayer->AsLayerComposite(); shadow->SetShadowClipRect(aLayer->GetClipRect()); shadow->SetShadowVisibleRegion(aLayer->GetVisibleRegion()); shadow->SetShadowOpacity(aLayer->GetOpacity()); const FrameMetrics* metrics = GetFrameMetrics(aLayer); gfx3DMatrix shadowTransform = aLayer->GetTransform(); ViewTransform layerTransform = aTransform; if (metrics && metrics->IsScrollable()) { const ViewID scrollId = metrics->mScrollId; const nsContentView* view = aFrameLoader->GetCurrentRemoteFrame()->GetContentView(scrollId); NS_ABORT_IF_FALSE(view, "Array of views should be consistent with layer tree"); const gfx3DMatrix& currentTransform = aLayer->GetTransform(); const ViewConfig& config = view->GetViewConfig(); // With temporary scale we should compensate translation // using temporary scale value aTempScaleDiffX *= GetXScale(shadowTransform) * config.mXScale; aTempScaleDiffY *= GetYScale(shadowTransform) * config.mYScale; ViewTransform viewTransform = ComputeShadowTreeTransform( aFrame, aFrameLoader, metrics, view->GetViewConfig(), aTempScaleDiffX, aTempScaleDiffY ); // Apply the layer's own transform *before* the view transform shadowTransform = gfx3DMatrix(viewTransform) * currentTransform; layerTransform = viewTransform; if (metrics->IsRootScrollable()) { // Apply the translation *before* we do the rest of the transforms. nsIntPoint offset = GetContentRectLayerOffset(aFrame, aBuilder); shadowTransform = shadowTransform * gfx3DMatrix::Translation(float(offset.x), float(offset.y), 0.0); } } if (aLayer->GetIsFixedPosition() && !aLayer->GetParent()->GetIsFixedPosition()) { // Alter the shadow transform of fixed position layers in the situation // that the view transform's scroll position doesn't match the actual // scroll position, due to asynchronous layer scrolling. float offsetX = layerTransform.mTranslation.x; float offsetY = layerTransform.mTranslation.y; ReverseTranslate(shadowTransform, gfxPoint(offsetX, offsetY)); const nsIntRect* clipRect = shadow->GetShadowClipRect(); if (clipRect) { nsIntRect transformedClipRect(*clipRect); transformedClipRect.MoveBy(-offsetX, -offsetY); shadow->SetShadowClipRect(&transformedClipRect); } } // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. if (ContainerLayer* c = aLayer->AsContainerLayer()) { shadowTransform.Scale(1.0f/c->GetPreXScale(), 1.0f/c->GetPreYScale(), 1); } shadowTransform.ScalePost(1.0f/aLayer->GetPostXScale(), 1.0f/aLayer->GetPostYScale(), 1); shadow->SetShadowTransform(shadowTransform); for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { TransformShadowTree(aBuilder, aFrameLoader, aFrame, child, layerTransform, aTempScaleDiffX, aTempScaleDiffY); } }
void AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer, const LayoutDeviceToLayerScale& aResolution) { LayerComposite* layerComposite = aLayer->AsLayerComposite(); ContainerLayer* container = aLayer->AsContainerLayer(); const FrameMetrics& metrics = container->GetFrameMetrics(); // We must apply the resolution scale before a pan/zoom transform, so we call // GetTransform here. const gfx3DMatrix& currentTransform = aLayer->GetTransform(); gfx3DMatrix oldTransform = currentTransform; gfx3DMatrix treeTransform; CSSToLayerScale geckoZoom = metrics.mDevPixelsPerCSSPixel * aResolution; LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.mScrollOffset * geckoZoom); if (mIsFirstPaint) { mContentRect = metrics.mScrollableRect; SetFirstPaintViewport(scrollOffsetLayerPixels, geckoZoom, mContentRect); mIsFirstPaint = false; } else if (!metrics.mScrollableRect.IsEqualEdges(mContentRect)) { mContentRect = metrics.mScrollableRect; SetPageRect(mContentRect); } // We synchronise the viewport information with Java after sending the above // notifications, so that Java can take these into account in its response. // Calculate the absolute display port to send to Java LayerIntRect displayPort = RoundedToInt( (metrics.mCriticalDisplayPort.IsEmpty() ? metrics.mDisplayPort : metrics.mCriticalDisplayPort ) * geckoZoom); displayPort += scrollOffsetLayerPixels; LayerMargin fixedLayerMargins(0, 0, 0, 0); ScreenPoint offset(0, 0); // Ideally we would initialize userZoom to AsyncPanZoomController::CalculateResolution(metrics) // but this causes a reftest-ipc test to fail (see bug 883646 comment 27). The reason for this // appears to be that metrics.mZoom is poorly initialized in some scenarios. In these scenarios, // however, we can assume there is no async zooming in progress and so the following statement // works fine. CSSToScreenScale userZoom(metrics.mDevPixelsPerCSSPixel.scale * metrics.mResolution.scale); ScreenPoint userScroll = metrics.mScrollOffset * userZoom; SyncViewportInfo(displayPort, geckoZoom, mLayersUpdated, userScroll, userZoom, fixedLayerMargins, offset); mLayersUpdated = false; // Apply the render offset mLayerManager->GetCompositor()->SetScreenRenderOffset(offset); // Handle transformations for asynchronous panning and zooming. We determine the // zoom used by Gecko from the transformation set on the root layer, and we // determine the scroll offset used by Gecko from the frame metrics of the // primary scrollable layer. We compare this to the user zoom and scroll // offset in the view transform we obtained from Java in order to compute the // transformation we need to apply. LayerToScreenScale zoomAdjust = userZoom / geckoZoom; LayerIntPoint geckoScroll(0, 0); if (metrics.IsScrollable()) { geckoScroll = scrollOffsetLayerPixels; } LayerPoint translation = (userScroll / zoomAdjust) - geckoScroll; treeTransform = gfx3DMatrix(ViewTransform(-translation, userZoom / metrics.mDevPixelsPerCSSPixel)); // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. gfx3DMatrix computedTransform = treeTransform * currentTransform; computedTransform.Scale(1.0f/container->GetPreXScale(), 1.0f/container->GetPreYScale(), 1); computedTransform.ScalePost(1.0f/container->GetPostXScale(), 1.0f/container->GetPostYScale(), 1); layerComposite->SetShadowTransform(computedTransform); NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), "overwriting animated transform!"); // Apply resolution scaling to the old transform - the layer tree as it is // doesn't have the necessary transform to display correctly. oldTransform.Scale(aResolution.scale, aResolution.scale, 1); // Make sure that overscroll and under-zoom are represented in the old // transform so that fixed position content moves and scales accordingly. // These calculations will effectively scale and offset fixed position layers // in screen space when the compensatory transform is performed in // AlignFixedLayersForAnchorPoint. ScreenRect contentScreenRect = mContentRect * userZoom; gfxPoint3D overscrollTranslation; if (userScroll.x < contentScreenRect.x) { overscrollTranslation.x = contentScreenRect.x - userScroll.x; } else if (userScroll.x + metrics.mCompositionBounds.width > contentScreenRect.XMost()) { overscrollTranslation.x = contentScreenRect.XMost() - (userScroll.x + metrics.mCompositionBounds.width); } if (userScroll.y < contentScreenRect.y) { overscrollTranslation.y = contentScreenRect.y - userScroll.y; } else if (userScroll.y + metrics.mCompositionBounds.height > contentScreenRect.YMost()) { overscrollTranslation.y = contentScreenRect.YMost() - (userScroll.y + metrics.mCompositionBounds.height); } oldTransform.Translate(overscrollTranslation); gfxSize underZoomScale(1.0f, 1.0f); if (mContentRect.width * userZoom.scale < metrics.mCompositionBounds.width) { underZoomScale.width = (mContentRect.width * userZoom.scale) / metrics.mCompositionBounds.width; } if (mContentRect.height * userZoom.scale < metrics.mCompositionBounds.height) { underZoomScale.height = (mContentRect.height * userZoom.scale) / metrics.mCompositionBounds.height; } oldTransform.Scale(underZoomScale.width, underZoomScale.height, 1); // Make sure fixed position layers don't move away from their anchor points // when we're asynchronously panning or zooming AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform, fixedLayerMargins); }
bool AsyncCompositionManager::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame, Layer *aLayer, bool* aWantNextFrame) { bool appliedTransform = false; for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { appliedTransform |= ApplyAsyncContentTransformToTree(aCurrentFrame, child, aWantNextFrame); } ContainerLayer* container = aLayer->AsContainerLayer(); if (!container) { return appliedTransform; } if (AsyncPanZoomController* controller = container->GetAsyncPanZoomController()) { LayerComposite* layerComposite = aLayer->AsLayerComposite(); gfx3DMatrix oldTransform = aLayer->GetTransform(); ViewTransform treeTransform; ScreenPoint scrollOffset; *aWantNextFrame |= controller->SampleContentTransformForFrame(aCurrentFrame, &treeTransform, scrollOffset); const gfx3DMatrix& rootTransform = mLayerManager->GetRoot()->GetTransform(); const FrameMetrics& metrics = container->GetFrameMetrics(); // XXX We use rootTransform instead of metrics.mResolution here because on // Fennec the resolution is set on the root layer rather than the scrollable layer. // The SyncFrameMetrics call and the paintScale variable are used on Fennec only // so it doesn't affect any other platforms. See bug 732971. CSSToLayerScale paintScale = metrics.mDevPixelsPerCSSPixel / LayerToLayoutDeviceScale(rootTransform.GetXScale(), rootTransform.GetYScale()); CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ? metrics.mDisplayPort : metrics.mCriticalDisplayPort); LayerMargin fixedLayerMargins(0, 0, 0, 0); ScreenPoint offset(0, 0); SyncFrameMetrics(scrollOffset, treeTransform.mScale.scale, metrics.mScrollableRect, mLayersUpdated, displayPort, paintScale, mIsFirstPaint, fixedLayerMargins, offset); mIsFirstPaint = false; mLayersUpdated = false; // Apply the render offset mLayerManager->GetCompositor()->SetScreenRenderOffset(offset); gfx3DMatrix transform(gfx3DMatrix(treeTransform) * aLayer->GetTransform()); // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. transform.Scale(1.0f/container->GetPreXScale(), 1.0f/container->GetPreYScale(), 1); transform.ScalePost(1.0f/aLayer->GetPostXScale(), 1.0f/aLayer->GetPostYScale(), 1); layerComposite->SetShadowTransform(transform); NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), "overwriting animated transform!"); // Apply resolution scaling to the old transform - the layer tree as it is // doesn't have the necessary transform to display correctly. #ifdef MOZ_WIDGET_ANDROID // XXX We use rootTransform instead of the resolution on the individual layer's // FrameMetrics on Fennec because the resolution is set on the root layer rather // than the scrollable layer. See bug 732971. On non-Fennec we do the right thing. LayoutDeviceToLayerScale resolution(1.0 / rootTransform.GetXScale(), 1.0 / rootTransform.GetYScale()); #else LayoutDeviceToLayerScale resolution = metrics.mResolution; #endif oldTransform.Scale(resolution.scale, resolution.scale, 1); AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform, fixedLayerMargins); appliedTransform = true; } return appliedTransform; }
static bool SampleAnimations(Layer* aLayer, TimeStamp aPoint) { AnimationArray& animations = aLayer->GetAnimations(); InfallibleTArray<AnimData>& animationData = aLayer->GetAnimationData(); bool activeAnimations = false; for (uint32_t i = animations.Length(); i-- !=0; ) { Animation& animation = animations[i]; AnimData& animData = animationData[i]; double numIterations = animation.numIterations() != -1 ? animation.numIterations() : NS_IEEEPositiveInfinity(); double positionInIteration = ElementAnimations::GetPositionInIteration(aPoint - animation.startTime(), animation.duration(), numIterations, animation.direction()); NS_ABORT_IF_FALSE(0.0 <= positionInIteration && positionInIteration <= 1.0, "position should be in [0-1]"); int segmentIndex = 0; AnimationSegment* segment = animation.segments().Elements(); while (segment->endPortion() < positionInIteration) { ++segment; ++segmentIndex; } double positionInSegment = (positionInIteration - segment->startPortion()) / (segment->endPortion() - segment->startPortion()); double portion = animData.mFunctions[segmentIndex]->GetValue(positionInSegment); activeAnimations = true; // interpolate the property Animatable interpolatedValue; SampleValue(portion, animation, animData.mStartValues[segmentIndex], animData.mEndValues[segmentIndex], &interpolatedValue); LayerComposite* layerComposite = aLayer->AsLayerComposite(); switch (animation.property()) { case eCSSProperty_opacity: { layerComposite->SetShadowOpacity(interpolatedValue.get_float()); break; } case eCSSProperty_transform: { gfx3DMatrix matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value(); if (ContainerLayer* c = aLayer->AsContainerLayer()) { matrix.ScalePost(c->GetInheritedXScale(), c->GetInheritedYScale(), 1); } layerComposite->SetShadowTransform(matrix); layerComposite->SetShadowTransformSetByAnimation(true); break; } default: NS_WARNING("Unhandled animated property"); } } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { activeAnimations |= SampleAnimations(child, aPoint); } return activeAnimations; }
bool AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer) { bool appliedTransform = false; for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { appliedTransform |= ApplyAsyncContentTransformToTree(child); } if (AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController()) { LayerComposite* layerComposite = aLayer->AsLayerComposite(); Matrix4x4 oldTransform = aLayer->GetTransform(); ViewTransform asyncTransformWithoutOverscroll, overscrollTransform; ScreenPoint scrollOffset; controller->SampleContentTransformForFrame(&asyncTransformWithoutOverscroll, scrollOffset, &overscrollTransform); const FrameMetrics& metrics = aLayer->GetFrameMetrics(); CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel(); CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ? metrics.mDisplayPort : metrics.mCriticalDisplayPort); LayerMargin fixedLayerMargins(0, 0, 0, 0); ScreenPoint offset(0, 0); SyncFrameMetrics(scrollOffset, asyncTransformWithoutOverscroll.mScale.scale, metrics.mScrollableRect, mLayersUpdated, displayPort, paintScale, mIsFirstPaint, fixedLayerMargins, offset); mIsFirstPaint = false; mLayersUpdated = false; // Apply the render offset mLayerManager->GetCompositor()->SetScreenRenderOffset(offset); Matrix4x4 transform = AdjustAndCombineWithCSSTransform( asyncTransformWithoutOverscroll * overscrollTransform, aLayer); // GetTransform already takes the pre- and post-scale into account. Since we // will apply the pre- and post-scale again when computing the effective // transform, we must apply the inverses here. if (ContainerLayer* container = aLayer->AsContainerLayer()) { transform.Scale(1.0f/container->GetPreXScale(), 1.0f/container->GetPreYScale(), 1); } transform = transform * Matrix4x4().Scale(1.0f/aLayer->GetPostXScale(), 1.0f/aLayer->GetPostYScale(), 1); layerComposite->SetShadowTransform(transform); NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), "overwriting animated transform!"); // Apply resolution scaling to the old transform - the layer tree as it is // doesn't have the necessary transform to display correctly. LayoutDeviceToLayerScale resolution = metrics.mCumulativeResolution; oldTransform.Scale(resolution.scale, resolution.scale, 1); // For the purpose of aligning fixed and sticky layers, we disregard // the overscroll transform when computing the 'aCurrentTransformForRoot' // parameter. This ensures that the overscroll transform is not unapplied, // and therefore that the visual effect applies to fixed and sticky layers. Matrix4x4 transformWithoutOverscroll = AdjustAndCombineWithCSSTransform( asyncTransformWithoutOverscroll, aLayer); AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform, transformWithoutOverscroll, fixedLayerMargins); appliedTransform = true; } if (aLayer->AsContainerLayer() && aLayer->GetScrollbarDirection() != Layer::NONE) { ApplyAsyncTransformToScrollbar(aLayer->AsContainerLayer()); } return appliedTransform; }
static bool SampleAnimations(Layer* aLayer, TimeStamp aPoint) { AnimationArray& animations = aLayer->GetAnimations(); InfallibleTArray<AnimData>& animationData = aLayer->GetAnimationData(); bool activeAnimations = false; for (uint32_t i = animations.Length(); i-- !=0; ) { Animation& animation = animations[i]; AnimData& animData = animationData[i]; activeAnimations = true; TimeDuration elapsedDuration = aPoint - animation.startTime(); // Skip animations that are yet to start. // // Currently, this should only happen when the refresh driver is under test // control and is made to produce a time in the past or is restored from // test control causing it to jump backwards in time. // // Since activeAnimations is true, this could mean we keep compositing // unnecessarily during the delay, but so long as this only happens while // the refresh driver is under test control that should be ok. if (elapsedDuration.ToSeconds() < 0) { continue; } AnimationTiming timing; timing.mIterationDuration = animation.duration(); // Currently animations run on the compositor have their delay factored // into their start time, hence the delay is effectively zero. timing.mDelay = TimeDuration(0); timing.mIterationCount = animation.iterationCount(); timing.mDirection = animation.direction(); // Animations typically only run on the compositor during their active // interval but if we end up sampling them outside that range (for // example, while they are waiting to be removed) we currently just // assume that we should fill. timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH; ComputedTiming computedTiming = dom::Animation::GetComputedTimingAt( Nullable<TimeDuration>(elapsedDuration), timing); NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction && computedTiming.mTimeFraction <= 1.0, "time fraction should be in [0-1]"); int segmentIndex = 0; AnimationSegment* segment = animation.segments().Elements(); while (segment->endPortion() < computedTiming.mTimeFraction) { ++segment; ++segmentIndex; } double positionInSegment = (computedTiming.mTimeFraction - segment->startPortion()) / (segment->endPortion() - segment->startPortion()); double portion = animData.mFunctions[segmentIndex]->GetValue(positionInSegment); // interpolate the property Animatable interpolatedValue; SampleValue(portion, animation, animData.mStartValues[segmentIndex], animData.mEndValues[segmentIndex], &interpolatedValue); LayerComposite* layerComposite = aLayer->AsLayerComposite(); switch (animation.property()) { case eCSSProperty_opacity: { layerComposite->SetShadowOpacity(interpolatedValue.get_float()); break; } case eCSSProperty_transform: { Matrix4x4 matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value(); if (ContainerLayer* c = aLayer->AsContainerLayer()) { matrix = matrix * Matrix4x4().Scale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1); } layerComposite->SetShadowTransform(matrix); layerComposite->SetShadowTransformSetByAnimation(true); break; } default: NS_WARNING("Unhandled animated property"); } } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { activeAnimations |= SampleAnimations(child, aPoint); } return activeAnimations; }