/** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "POPAnimationInternal.h" #import "POPPropertyAnimation.h" static void clampValue(CGFloat &value, CGFloat fromValue, CGFloat toValue, NSUInteger clamp) { BOOL increasing = (toValue > fromValue); // Clamp start of animation. if ((kPOPAnimationClampStart & clamp) && ((increasing && (value < fromValue)) || (!increasing && (value > fromValue)))) { value = fromValue; } // Clamp end of animation. if ((kPOPAnimationClampEnd & clamp) && ((increasing && (value > toValue)) || (!increasing && (value < toValue)))) { value = toValue; } } struct _POPPropertyAnimationState : _POPAnimationState { POPAnimatableProperty *property; POPValueType valueType; NSUInteger valueCount; VectorRef fromVec; VectorRef toVec; VectorRef currentVec; VectorRef previousVec; VectorRef previous2Vec; VectorRef velocityVec; VectorRef originalVelocityVec; VectorRef distanceVec; CGFloat roundingFactor; NSUInteger clampMode; NSArray *progressMarkers; POPProgressMarker *progressMarkerState; NSUInteger progressMarkerCount; NSUInteger nextProgressMarkerIdx; CGFloat dynamicsThreshold; _POPPropertyAnimationState(id __unsafe_unretained anim) : _POPAnimationState(anim), property(nil), valueType((POPValueType)0), valueCount(0), fromVec(nullptr), toVec(nullptr), currentVec(nullptr), previousVec(nullptr), previous2Vec(nullptr), velocityVec(nullptr), originalVelocityVec(nullptr), distanceVec(nullptr), roundingFactor(0), clampMode(0), progressMarkers(nil), progressMarkerState(nil), progressMarkerCount(0), nextProgressMarkerIdx(0), dynamicsThreshold(0) { type = kPOPAnimationBasic; } ~_POPPropertyAnimationState() { if (progressMarkerState) { free(progressMarkerState); progressMarkerState = NULL; } } bool canProgress() { return hasValue(); } bool shouldRound() { return 0 != roundingFactor; } bool hasValue() { return 0 != valueCount; } bool isDone() { // inherit done if (_POPAnimationState::isDone()) { return true; } // consider an animation with no values done if (!hasValue() && !isCustom()) { return true; } return false; } // returns a copy of the currentVec, rounding if needed VectorRef currentValue() { VectorRef vec = VectorRef(Vector::new_vector(currentVec.get())); if (shouldRound()) { vec->subRound(1 / roundingFactor); } return vec; } void resetProgressMarkerState() { for (NSUInteger idx = 0; idx < progressMarkerCount; idx++) progressMarkerState[idx].reached = false; nextProgressMarkerIdx = 0; } void updatedProgressMarkers() { if (progressMarkerState) { free(progressMarkerState); progressMarkerState = NULL; } progressMarkerCount = progressMarkers.count; if (0 != progressMarkerCount) { progressMarkerState = (POPProgressMarker *)malloc(progressMarkerCount * sizeof(POPProgressMarker)); [progressMarkers enumerateObjectsUsingBlock:^(NSNumber *progressMarker, NSUInteger idx, BOOL *stop) { progressMarkerState[idx].reached = false; progressMarkerState[idx].progress = [progressMarker floatValue]; }]; } nextProgressMarkerIdx = 0; } virtual void updatedDynamicsThreshold() { dynamicsThreshold = property.threshold; } void finalizeProgress() { progress = 1.0; NSUInteger count = valueCount; VectorRef outVec(Vector::new_vector(count, NULL)); if (outVec && toVec) { *outVec = *toVec; } currentVec = outVec; clampCurrentValue(); delegateProgress(); } void computeProgress() { if (!canProgress()) { return; } static ComputeProgressFunctor func; Vector4r v = vector4(currentVec); Vector4r f = vector4(fromVec); Vector4r t = vector4(toVec); progress = func(v, f, t); } void delegateProgress() { if (!canProgress()) { return; } if (delegateDidProgress && progressMarkerState) { while (nextProgressMarkerIdx < progressMarkerCount) { if (progress < progressMarkerState[nextProgressMarkerIdx].progress) break; if (!progressMarkerState[nextProgressMarkerIdx].reached) { ActionEnabler enabler; [delegate pop_animation:self didReachProgress:progressMarkerState[nextProgressMarkerIdx].progress]; progressMarkerState[nextProgressMarkerIdx].reached = true; } nextProgressMarkerIdx++; } } if (!didReachToValue) { bool didReachToValue = false; if (0 == valueCount) { didReachToValue = true; } else { Vector4r distance = toVec->vector4r(); distance -= currentVec->vector4r(); if (0 == distance.squaredNorm()) { didReachToValue = true; } else { // components if (distanceVec) { didReachToValue = true; const CGFloat *distanceValues = distanceVec->data(); for (NSUInteger idx = 0; idx < valueCount; idx++) { didReachToValue &= (signbit(distance[idx]) != signbit(distanceValues[idx])); } } } } if (didReachToValue) { handleDidReachToValue(); } } } void handleDidReachToValue() { didReachToValue = true; if (delegateDidReachToValue) { ActionEnabler enabler; [delegate pop_animationDidReachToValue:self]; } POPAnimationDidReachToValueBlock block = animationDidReachToValueBlock; if (block != NULL) { ActionEnabler enabler; block(self); } if (tracing) { [tracer didReachToValue:POPBox(currentValue(), valueType, true)]; } } void readObjectValue(VectorRef *ptrVec, id obj) { // use current object value as from value pop_animatable_read_block read = property.readBlock; if (NULL != read) { Vector4r vec = read_values(read, obj, valueCount); *ptrVec = VectorRef(Vector::new_vector(valueCount, vec)); if (tracing) { [tracer readPropertyValue:POPBox(*ptrVec, valueType, true)]; } } } virtual void willRun(bool started, id obj) { // ensure from value initialized if (NULL == fromVec) { readObjectValue(&fromVec, obj); } // ensure to value initialized if (NULL == toVec) { // compute decay to value if (kPOPAnimationDecay == type) { [self toValue]; } else { // read to value readObjectValue(&toVec, obj); } } // handle one time value initialization on start if (started) { // initialize current vec if (!currentVec) { currentVec = VectorRef(Vector::new_vector(valueCount, NULL)); // initialize current value with from value // only do this on initial creation to avoid overwriting current value // on paused animation continuation if (currentVec && fromVec) { *currentVec = *fromVec; } } // ensure velocity values if (!velocityVec) { velocityVec = VectorRef(Vector::new_vector(valueCount, NULL)); } if (!originalVelocityVec) { originalVelocityVec = VectorRef(Vector::new_vector(valueCount, NULL)); } } // ensure distance value initialized // depends on current value set on one time start if (NULL == distanceVec) { // not yet started animations may not have current value VectorRef fromVec2 = NULL != currentVec ? currentVec : fromVec; if (fromVec2 && toVec) { Vector4r distance = toVec->vector4r(); distance -= fromVec2->vector4r(); if (0 != distance.squaredNorm()) { distanceVec = VectorRef(Vector::new_vector(valueCount, distance)); } } } } virtual void reset(bool all) { _POPAnimationState::reset(all); if (all) { currentVec = NULL; previousVec = NULL; previous2Vec = NULL; } progress = 0; resetProgressMarkerState(); didReachToValue = false; distanceVec = NULL; } void clampCurrentValue(NSUInteger clamp) { if (kPOPAnimationClampNone == clamp) return; // Clamp all vector values CGFloat *currentValues = currentVec->data(); const CGFloat *fromValues = fromVec->data(); const CGFloat *toValues = toVec->data(); for (NSUInteger idx = 0; idx < valueCount; idx++) { clampValue(currentValues[idx], fromValues[idx], toValues[idx], clamp); } } void clampCurrentValue() { clampCurrentValue(clampMode); } }; typedef struct _POPPropertyAnimationState POPPropertyAnimationState; @interface POPPropertyAnimation () @end