Inheritance: IAnimationDescription
        private Storyboard CreateEnterAnimation(Panel layoutRoot)
        {
            var enterAnimation = new Storyboard();
            Storyboard.SetTarget(enterAnimation, layoutRoot);

            var ad = new AnimationDescription(AnimationEffect.EnterPage, AnimationEffectTarget.Primary);
            for (int i = 0; i < layoutRoot.Children.Count; i++)
            {
                // Add a render transform to the existing one just for animations
                var element = layoutRoot.Children[i];
                var tg = new TransformGroup();
                tg.Children.Add(new TranslateTransform());
                tg.Children.Add(element.RenderTransform);
                element.RenderTransform = tg;

                // Calculate the stagger for each animation. Note that this has a max
                var delayMs = Math.Min(ad.StaggerDelay.TotalMilliseconds * i * ad.StaggerDelayFactor, ad.DelayLimit.Milliseconds);
                var delay = TimeSpan.FromMilliseconds(delayMs);

                foreach (var description in ad.Animations)
                {
                    var animation = new DoubleAnimationUsingKeyFrames();

                    // Start the animation at the right offset
                    var startSpline = new SplineDoubleKeyFrame();
                    startSpline.KeyTime = TimeSpan.FromMilliseconds(0);
                    Storyboard.SetTarget(animation, element);

                    // Hold at that offset until the stagger delay is hit
                    var middleSpline = new SplineDoubleKeyFrame();
                    middleSpline.KeyTime = delay;

                    // Animation from delayed time to last time
                    var endSpline = new SplineDoubleKeyFrame();
                    endSpline.KeySpline = new KeySpline() { ControlPoint1 = description.Control1, ControlPoint2 = description.Control2 };
                    endSpline.KeyTime = description.Duration + delay;

                    // Do the translation
                    if (description.Type == PropertyAnimationType.Translation)
                    {
                        startSpline.Value = ANIMATION_TRANSLATION_START;
                        middleSpline.Value = ANIMATION_TRANSLATION_START;
                        endSpline.Value = ANIMATION_TRANSLATION_END;

                        Storyboard.SetTargetProperty(animation, "(UIElement.RenderTransform).(TransformGroup.Children)[0].X");
                    }
                    // Opacity
                    else if (description.Type == PropertyAnimationType.Opacity)
                    {
                        startSpline.Value = ANIMATION_OPACITY_START;
                        middleSpline.Value = ANIMATION_OPACITY_START;
                        endSpline.Value = ANIMATION_OPACITY_END;

                        Storyboard.SetTargetProperty(animation, "Opacity");
                    }
                    else
                    {
                        throw new Exception("Encountered an unexpected animation type.");
                    }

                    // Put the final animation together
                    animation.KeyFrames.Add(startSpline);
                    animation.KeyFrames.Add(middleSpline);
                    animation.KeyFrames.Add(endSpline);
                    enterAnimation.Children.Add(animation);
                }
            }

            return enterAnimation;
        }
        /// <summary>
        /// Retrieves the specified metrics and displays them in textual form.
        /// </summary>
        /// <param name="effect">The AnimationEffect whose metrics are to be displayed.</param>
        /// <param name="target">The AnimationEffecTarget whose metrics are to be displayed.</param>
        private void DisplayMetrics(AnimationEffect effect, AnimationEffectTarget target)
        {
            var s = new System.Text.StringBuilder();
            AnimationDescription animationDescription = new AnimationDescription(effect, target);
            s.AppendFormat("Stagger delay = {0}ms", animationDescription.StaggerDelay.TotalMilliseconds);
            s.AppendLine();
            s.AppendFormat("Stagger delay factor = {0}", animationDescription.StaggerDelayFactor);
            s.AppendLine();
            s.AppendFormat("Delay limit = {0}ms", animationDescription.DelayLimit.TotalMilliseconds);
            s.AppendLine();
            s.AppendFormat("ZOrder = {0}", animationDescription.ZOrder);
            s.AppendLine();
            s.AppendLine();

            int animationIndex = 0;
            foreach (var animation in animationDescription.Animations)
            {
                s.AppendFormat("Animation #{0}:", ++animationIndex);
                s.AppendLine();

                switch (animation.Type)
                {
                    case PropertyAnimationType.Scale:
                        {
                            ScaleAnimation scale = animation as ScaleAnimation;
                            s.AppendLine("Type = Scale");
                            if (scale.InitialScaleX.HasValue)
                            {
                                s.AppendFormat("InitialScaleX = {0}", scale.InitialScaleX.Value);
                                s.AppendLine();
                            }
                            if (scale.InitialScaleY.HasValue)
                            {
                                s.AppendFormat("InitialScaleY = {0}", scale.InitialScaleY.Value);
                                s.AppendLine();
                            }
                            s.AppendFormat("FinalScaleX = {0}", scale.FinalScaleX);
                            s.AppendLine();
                            s.AppendFormat("FinalScaleY = {0}", scale.FinalScaleY);
                            s.AppendLine();
                            s.AppendFormat("Origin = {0}, {1}", scale.NormalizedOrigin.X, scale.NormalizedOrigin.Y);
                            s.AppendLine();
                        }
                        break;
                    case PropertyAnimationType.Translation:
                        s.AppendLine("Type = Translation");
                        break;
                    case PropertyAnimationType.Opacity:
                        {
                            OpacityAnimation opacity = animation as OpacityAnimation;
                            s.AppendLine("Type = Opacity");
                            if (opacity.InitialOpacity.HasValue)
                            {
                                s.AppendFormat("InitialOpacity = {0}", opacity.InitialOpacity.Value);
                                s.AppendLine();
                            }
                            s.AppendFormat("FinalOpacity = {0}", opacity.FinalOpacity);
                            s.AppendLine();
                        }
                        break;
                }

                s.AppendFormat("Delay = {0}ms", animation.Delay.TotalMilliseconds);
                s.AppendLine();
                s.AppendFormat("Duration = {0}ms", animation.Duration.TotalMilliseconds);
                s.AppendLine();
                s.AppendFormat("Cubic Bezier control points");
                s.AppendLine();
                s.AppendFormat("    X1 = {0}, Y1 = {1}", animation.Control1.X, animation.Control1.Y);
                s.AppendLine();
                s.AppendFormat("    X2 = {0}, Y2 = {1}", animation.Control2.X, animation.Control2.Y);
                s.AppendLine();
                s.AppendLine();
            }

            Metrics.Text = s.ToString();
        }