/// <summary> /// Calculates the value this animation believes should be the current value for the property. /// </summary> /// <param name="defaultOriginValue"> /// This value is the suggested origin value provided to the animation /// to be used if the animation does not have its own concept of a /// start value. If this animation is the first in a composition chain /// this value will be the snapshot value if one is available or the /// base property value if it is not; otherise this value will be the /// value returned by the previous animation in the chain with an /// animationClock that is not Stopped. /// </param> /// <param name="defaultDestinationValue"> /// This value is the suggested destination value provided to the animation /// to be used if the animation does not have its own concept of an /// end value. This value will be the base value if the animation is /// in the first composition layer of animations on a property; /// otherwise this value will be the output value from the previous /// composition layer of animations for the property. /// </param> /// <param name="animationClock"> /// This is the animationClock which can generate the CurrentTime or /// CurrentProgress value to be used by the animation to generate its /// output value. /// </param> /// <returns> /// The value this animation believes should be the current value for the property. /// </returns> protected sealed override Byte GetCurrentValueCore( Byte defaultOriginValue, Byte defaultDestinationValue, AnimationClock animationClock) { Debug.Assert(animationClock.CurrentState != ClockState.Stopped); if (_keyFrames == null) { return(defaultDestinationValue); } // We resolved our KeyTimes when we froze, but also got notified // of the frozen state and therefore invalidated ourselves. if (!_areKeyTimesValid) { ResolveKeyTimes(); } if (_sortedResolvedKeyFrames == null) { return(defaultDestinationValue); } TimeSpan currentTime = animationClock.CurrentTime.Value; Int32 keyFrameCount = _sortedResolvedKeyFrames.Length; Int32 maxKeyFrameIndex = keyFrameCount - 1; Byte currentIterationValue; Debug.Assert(maxKeyFrameIndex >= 0, "maxKeyFrameIndex is less than zero which means we don't actually have any key frames."); Int32 currentResolvedKeyFrameIndex = 0; // Skip all the key frames with key times lower than the current time. // currentResolvedKeyFrameIndex will be greater than maxKeyFrameIndex // if we are past the last key frame. while (currentResolvedKeyFrameIndex < keyFrameCount && currentTime > _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime) { currentResolvedKeyFrameIndex++; } // If there are multiple key frames at the same key time, be sure to go to the last one. while (currentResolvedKeyFrameIndex < maxKeyFrameIndex && currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex + 1]._resolvedKeyTime) { currentResolvedKeyFrameIndex++; } if (currentResolvedKeyFrameIndex == keyFrameCount) { // Past the last key frame. currentIterationValue = GetResolvedKeyFrameValue(maxKeyFrameIndex); } else if (currentTime == _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime) { // Exactly on a key frame. currentIterationValue = GetResolvedKeyFrameValue(currentResolvedKeyFrameIndex); } else { // Between two key frames. Double currentSegmentProgress = 0.0; Byte fromValue; if (currentResolvedKeyFrameIndex == 0) { // The current key frame is the first key frame so we have // some special rules for determining the fromValue and an // optimized method of calculating the currentSegmentProgress. // If we're additive we want the base value to be a zero value // so that if there isn't a key frame at time 0.0, we'll use // the zero value for the time 0.0 value and then add that // later to the base value. if (IsAdditive) { fromValue = AnimatedTypeHelpers.GetZeroValueByte(defaultOriginValue); } else { fromValue = defaultOriginValue; } // Current segment time divided by the segment duration. // Note: the reason this works is that we know that we're in // the first segment, so we can assume: // // currentTime.TotalMilliseconds = current segment time // _sortedResolvedKeyFrames[0]._resolvedKeyTime.TotalMilliseconds = current segment duration currentSegmentProgress = currentTime.TotalMilliseconds / _sortedResolvedKeyFrames[0]._resolvedKeyTime.TotalMilliseconds; } else { Int32 previousResolvedKeyFrameIndex = currentResolvedKeyFrameIndex - 1; TimeSpan previousResolvedKeyTime = _sortedResolvedKeyFrames[previousResolvedKeyFrameIndex]._resolvedKeyTime; fromValue = GetResolvedKeyFrameValue(previousResolvedKeyFrameIndex); TimeSpan segmentCurrentTime = currentTime - previousResolvedKeyTime; TimeSpan segmentDuration = _sortedResolvedKeyFrames[currentResolvedKeyFrameIndex]._resolvedKeyTime - previousResolvedKeyTime; currentSegmentProgress = segmentCurrentTime.TotalMilliseconds / segmentDuration.TotalMilliseconds; } currentIterationValue = GetResolvedKeyFrame(currentResolvedKeyFrameIndex).InterpolateValue(fromValue, currentSegmentProgress); } // If we're cumulative, we need to multiply the final key frame // value by the current repeat count and add this to the return // value. if (IsCumulative) { Double currentRepeat = (Double)(animationClock.CurrentIteration - 1); if (currentRepeat > 0.0) { currentIterationValue = AnimatedTypeHelpers.AddByte( currentIterationValue, AnimatedTypeHelpers.ScaleByte(GetResolvedKeyFrameValue(maxKeyFrameIndex), currentRepeat)); } } // If we're additive we need to add the base value to the return value. if (IsAdditive) { return(AnimatedTypeHelpers.AddByte(defaultOriginValue, currentIterationValue)); } return(currentIterationValue); }
/// <summary> /// Calculates the value this animation believes should be the current value for the property. /// </summary> /// <param name="defaultOriginValue"> /// This value is the suggested origin value provided to the animation /// to be used if the animation does not have its own concept of a /// start value. If this animation is the first in a composition chain /// this value will be the snapshot value if one is available or the /// base property value if it is not; otherise this value will be the /// value returned by the previous animation in the chain with an /// animationClock that is not Stopped. /// </param> /// <param name="defaultDestinationValue"> /// This value is the suggested destination value provided to the animation /// to be used if the animation does not have its own concept of an /// end value. This value will be the base value if the animation is /// in the first composition layer of animations on a property; /// otherwise this value will be the output value from the previous /// composition layer of animations for the property. /// </param> /// <param name="animationClock"> /// This is the animationClock which can generate the CurrentTime or /// CurrentProgress value to be used by the animation to generate its /// output value. /// </param> /// <returns> /// The value this animation believes should be the current value for the property. /// </returns> protected override Byte GetCurrentValueCore(Byte defaultOriginValue, Byte defaultDestinationValue, AnimationClock animationClock) { Debug.Assert(animationClock.CurrentState != ClockState.Stopped); if (!_isAnimationFunctionValid) { ValidateAnimationFunction(); } double progress = animationClock.CurrentProgress.Value; IEasingFunction easingFunction = EasingFunction; if (easingFunction != null) { progress = easingFunction.Ease(progress); } Byte from = new Byte(); Byte to = new Byte(); Byte accumulated = new Byte(); Byte foundation = new Byte(); // need to validate the default origin and destination values if // the animation uses them as the from, to, or foundation values bool validateOrigin = false; bool validateDestination = false; switch (_animationType) { case AnimationType.Automatic: from = defaultOriginValue; to = defaultDestinationValue; validateOrigin = true; validateDestination = true; break; case AnimationType.From: from = _keyValues[0]; to = defaultDestinationValue; validateDestination = true; break; case AnimationType.To: from = defaultOriginValue; to = _keyValues[0]; validateOrigin = true; break; case AnimationType.By: // According to the SMIL specification, a By animation is // always additive. But we don't force this so that a // user can re-use a By animation and have it replace the // animations that precede it in the list without having // to manually set the From value to the base value. to = _keyValues[0]; foundation = defaultOriginValue; validateOrigin = true; break; case AnimationType.FromTo: from = _keyValues[0]; to = _keyValues[1]; if (IsAdditive) { foundation = defaultOriginValue; validateOrigin = true; } break; case AnimationType.FromBy: from = _keyValues[0]; to = AnimatedTypeHelpers.AddByte(_keyValues[0], _keyValues[1]); if (IsAdditive) { foundation = defaultOriginValue; validateOrigin = true; } break; default: Debug.Fail("Unknown animation type."); break; } if (validateOrigin && !AnimatedTypeHelpers.IsValidAnimationValueByte(defaultOriginValue)) { throw new InvalidOperationException( SR.Get( SRID.Animation_Invalid_DefaultValue, this.GetType(), "origin", defaultOriginValue.ToString(CultureInfo.InvariantCulture))); } if (validateDestination && !AnimatedTypeHelpers.IsValidAnimationValueByte(defaultDestinationValue)) { throw new InvalidOperationException( SR.Get( SRID.Animation_Invalid_DefaultValue, this.GetType(), "destination", defaultDestinationValue.ToString(CultureInfo.InvariantCulture))); } if (IsCumulative) { double currentRepeat = (double)(animationClock.CurrentIteration - 1); if (currentRepeat > 0.0) { Byte accumulator = AnimatedTypeHelpers.SubtractByte(to, from); accumulated = AnimatedTypeHelpers.ScaleByte(accumulator, currentRepeat); } } // return foundation + accumulated + from + ((to - from) * progress) return(AnimatedTypeHelpers.AddByte( foundation, AnimatedTypeHelpers.AddByte( accumulated, AnimatedTypeHelpers.InterpolateByte(from, to, progress)))); }