| Index: ios/chrome/browser/ui/reversed_animation.mm
|
| diff --git a/ios/chrome/browser/ui/reversed_animation.mm b/ios/chrome/browser/ui/reversed_animation.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a46b0dcdd4954209e95340119ebe24a8e658cfcc
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/reversed_animation.mm
|
| @@ -0,0 +1,319 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "ios/chrome/browser/ui/reversed_animation.h"
|
| +
|
| +#import <QuartzCore/QuartzCore.h>
|
| +#include <algorithm>
|
| +#include <cmath>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/mac/objc_property_releaser.h"
|
| +
|
| +@protocol ReversedAnimationProtocol;
|
| +typedef CAAnimation<ReversedAnimationProtocol> ReversedAnimation;
|
| +
|
| +namespace {
|
| +// Enum type used to denote the direction of a reversed animation relative to
|
| +// the original animation's direction.
|
| +typedef enum {
|
| + ANIMATION_DIRECTION_NORMAL,
|
| + ANIMATION_DIRECTION_REVERSE
|
| +} AnimationDirection;
|
| +// Returns the AnimationDirection opposite of |direction|.
|
| +AnimationDirection AnimationDirectionOpposite(AnimationDirection direction) {
|
| + return direction == ANIMATION_DIRECTION_NORMAL ? ANIMATION_DIRECTION_REVERSE
|
| + : ANIMATION_DIRECTION_NORMAL;
|
| +}
|
| +} // namespace
|
| +
|
| +// Returns an animation that reverses |animation| when added to |layer|, given
|
| +// that |animation| is in |parent|'s timespace, which begins at
|
| +// |parentBeginTime|.
|
| +CAAnimation* CAAnimationMakeReverse(CAAnimation* animation,
|
| + CALayer* layer,
|
| + CAAnimationGroup* parent,
|
| + CFTimeInterval parentBeginTime);
|
| +// Updates |reversedAnimation|'s properties for |animationToReverse|, given that
|
| +// |animationToReverse| is in |parent|'s timespace, which begins at
|
| +// |parentBeginTime|.
|
| +void UpdateReversedAnimation(ReversedAnimation* reversedAnimation,
|
| + CAAnimation* animationToReverse,
|
| + CALayer* layer,
|
| + CAAnimationGroup* parent,
|
| + CFTimeInterval parentBeginTime);
|
| +
|
| +#pragma mark - ReversedAnimation protocol
|
| +
|
| +@protocol ReversedAnimationProtocol<NSObject>
|
| +
|
| +// The original animation that's being played in reverse.
|
| +@property(nonatomic, retain) CAAnimation* originalAnimation;
|
| +// The current direction for the animation.
|
| +@property(nonatomic, assign) AnimationDirection animationDirection;
|
| +// The offset into the original animation's duration at the begining of the
|
| +// reverse animation.
|
| +@property(nonatomic, assign) CFTimeInterval animationTimeOffset;
|
| +
|
| +@end
|
| +
|
| +#pragma mark - ReversedBasicAnimation
|
| +
|
| +@interface ReversedBasicAnimation
|
| + : CABasicAnimation<ReversedAnimationProtocol> {
|
| + base::mac::ObjCPropertyReleaser _propertyReleaser_ReversedBasicAnimation;
|
| +}
|
| +
|
| +// Returns an animation that performs |animation| in reverse when added to
|
| +// |layer|. |parentBeginTime| should be set to the beginTime in absolute time
|
| +// of |animation|'s parent in the timing hierarchy.
|
| ++ (instancetype)reversedAnimationForAnimation:(CABasicAnimation*)animation
|
| + forLayer:(CALayer*)layer
|
| + parent:(CAAnimationGroup*)parent
|
| + parentBeginTime:(CFTimeInterval)parentBeginTime;
|
| +
|
| +@end
|
| +
|
| +@implementation ReversedBasicAnimation
|
| +
|
| +@synthesize originalAnimation = _originalAnimation;
|
| +@synthesize animationDirection = _animationDirection;
|
| +@synthesize animationTimeOffset = _animationTimeOffset;
|
| +
|
| +- (instancetype)init {
|
| + self = [super init];
|
| + if (self) {
|
| + _propertyReleaser_ReversedBasicAnimation.Init(
|
| + self, [ReversedBasicAnimation class]);
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)copyWithZone:(NSZone*)zone {
|
| + ReversedBasicAnimation* copy = [super copyWithZone:zone];
|
| + copy.originalAnimation = self.originalAnimation;
|
| + copy.animationDirection = self.animationDirection;
|
| + copy.animationTimeOffset = self.animationTimeOffset;
|
| + return copy;
|
| +}
|
| +
|
| ++ (instancetype)reversedAnimationForAnimation:(CABasicAnimation*)animation
|
| + forLayer:(CALayer*)layer
|
| + parent:(CAAnimationGroup*)parent
|
| + parentBeginTime:(CFTimeInterval)parentBeginTime {
|
| + // Create new animation and copy properties. Note that we can't use |-copy|
|
| + // because we need the new animation to be the correct class.
|
| + NSString* keyPath = animation.keyPath;
|
| + CFTimeInterval now =
|
| + [layer convertTime:CACurrentMediaTime() fromLayer:nil] - parentBeginTime;
|
| + ReversedBasicAnimation* reversedAnimation =
|
| + [ReversedBasicAnimation animationWithKeyPath:keyPath];
|
| + UpdateReversedAnimation(reversedAnimation, animation, layer, parent,
|
| + parentBeginTime);
|
| +
|
| + // Update from and to values.
|
| + BOOL isReversed =
|
| + reversedAnimation.animationDirection == ANIMATION_DIRECTION_REVERSE;
|
| + CABasicAnimation* originalBasicAnimation =
|
| + static_cast<CABasicAnimation*>(reversedAnimation.originalAnimation);
|
| + reversedAnimation.toValue = isReversed ? originalBasicAnimation.fromValue
|
| + : originalBasicAnimation.toValue;
|
| + if (now > animation.beginTime &&
|
| + now < animation.beginTime + animation.duration) {
|
| + // Use the presentation layer's current value for reversals that occur mid-
|
| + // animation.
|
| + reversedAnimation.fromValue =
|
| + [[layer presentationLayer] valueForKeyPath:keyPath];
|
| + } else {
|
| + reversedAnimation.fromValue = isReversed ? originalBasicAnimation.toValue
|
| + : originalBasicAnimation.fromValue;
|
| + }
|
| + return reversedAnimation;
|
| +}
|
| +
|
| +@end
|
| +
|
| +#pragma mark - ReversedAnimationGroup
|
| +
|
| +@interface ReversedAnimationGroup
|
| + : CAAnimationGroup<ReversedAnimationProtocol> {
|
| + base::mac::ObjCPropertyReleaser _propertyReleaser_ReversedAnimationGroup;
|
| +}
|
| +
|
| +// Returns an animation that performs |animation| in reverse when added to
|
| +// |layer|. |parentBeginTime| should be set to the beginTime in absolute time
|
| +// of the animation group to which |animation| belongs.
|
| ++ (instancetype)reversedAnimationGroupForGroup:(CAAnimationGroup*)group
|
| + forLayer:(CALayer*)layer
|
| + parent:(CAAnimationGroup*)parent
|
| + parentBeginTime:(CFTimeInterval)parentBeginTime;
|
| +
|
| +@end
|
| +
|
| +@implementation ReversedAnimationGroup
|
| +
|
| +@synthesize originalAnimation = _originalAnimation;
|
| +@synthesize animationDirection = _animationDirection;
|
| +@synthesize animationTimeOffset = _animationTimeOffset;
|
| +
|
| +- (instancetype)init {
|
| + self = [super init];
|
| + if (self) {
|
| + _propertyReleaser_ReversedAnimationGroup.Init(
|
| + self, [ReversedAnimationGroup class]);
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)copyWithZone:(NSZone*)zone {
|
| + ReversedAnimationGroup* copy = [super copyWithZone:zone];
|
| + copy.originalAnimation = self.originalAnimation;
|
| + copy.animationDirection = self.animationDirection;
|
| + copy.animationTimeOffset = self.animationTimeOffset;
|
| + return copy;
|
| +}
|
| +
|
| ++ (instancetype)reversedAnimationGroupForGroup:(CAAnimationGroup*)group
|
| + forLayer:(CALayer*)layer
|
| + parent:(CAAnimationGroup*)parent
|
| + parentBeginTime:(CFTimeInterval)parentBeginTime {
|
| + // Create new animation and copy properties. Note that we can't use |-copy|
|
| + // because we need the new animation to be the correct class.
|
| + ReversedAnimationGroup* reversedGroup = [ReversedAnimationGroup animation];
|
| + UpdateReversedAnimation(reversedGroup, group, layer, parent, parentBeginTime);
|
| +
|
| + // Reverse the animations of the original group.
|
| + NSMutableArray* reversedAnimations = [NSMutableArray array];
|
| + for (CAAnimation* animation in group.animations) {
|
| + CAAnimation* reversedAnimation = CAAnimationMakeReverse(
|
| + animation, layer, group, group.beginTime + parentBeginTime);
|
| + [reversedAnimations addObject:reversedAnimation];
|
| + }
|
| + reversedGroup.animations = reversedAnimations;
|
| +
|
| + return reversedGroup;
|
| +}
|
| +
|
| +@end
|
| +
|
| +#pragma mark - animation_util functions
|
| +
|
| +CAAnimation* CAAnimationMakeReverse(CAAnimation* animation, CALayer* layer) {
|
| + return CAAnimationMakeReverse(animation, layer, nil, layer.beginTime);
|
| +}
|
| +
|
| +CAAnimation* CAAnimationMakeReverse(CAAnimation* animation,
|
| + CALayer* layer,
|
| + CAAnimationGroup* parent,
|
| + CFTimeInterval parentBeginTime) {
|
| + CAAnimation* reversedAnimation = nil;
|
| + if ([animation isKindOfClass:[CABasicAnimation class]]) {
|
| + CABasicAnimation* basicAnimation =
|
| + static_cast<CABasicAnimation*>(animation);
|
| + reversedAnimation =
|
| + [ReversedBasicAnimation reversedAnimationForAnimation:basicAnimation
|
| + forLayer:layer
|
| + parent:parent
|
| + parentBeginTime:parentBeginTime];
|
| + } else if ([animation isKindOfClass:[CAAnimationGroup class]]) {
|
| + CAAnimationGroup* animationGroup =
|
| + static_cast<CAAnimationGroup*>(animation);
|
| + reversedAnimation =
|
| + [ReversedAnimationGroup reversedAnimationGroupForGroup:animationGroup
|
| + forLayer:layer
|
| + parent:parent
|
| + parentBeginTime:parentBeginTime];
|
| + } else {
|
| + // TODO(kkhorimoto): Investigate possible general-case reversals. It may
|
| + // be possible to implement this by manipulating the CAMediaTiming
|
| + // properties.
|
| + }
|
| + return reversedAnimation;
|
| +}
|
| +
|
| +void UpdateReversedAnimation(ReversedAnimation* reversedAnimation,
|
| + CAAnimation* animationToReverse,
|
| + CALayer* layer,
|
| + CAAnimationGroup* parent,
|
| + CFTimeInterval parentBeginTime) {
|
| + // Copy properties.
|
| + CFTimeInterval now =
|
| + [layer convertTime:CACurrentMediaTime() fromLayer:nil] - parentBeginTime;
|
| + reversedAnimation.fillMode = animationToReverse.fillMode;
|
| + reversedAnimation.removedOnCompletion =
|
| + animationToReverse.removedOnCompletion;
|
| + reversedAnimation.timingFunction = animationToReverse.timingFunction;
|
| +
|
| + // Extract the previous reversal if it exists.
|
| + ReversedAnimation* previousReversedAnimation = nil;
|
| + Protocol* reversedAnimationProtocol = @protocol(ReversedAnimationProtocol);
|
| + if ([animationToReverse conformsToProtocol:reversedAnimationProtocol]) {
|
| + previousReversedAnimation =
|
| + static_cast<ReversedAnimation*>(animationToReverse);
|
| + animationToReverse = previousReversedAnimation.originalAnimation;
|
| + }
|
| + reversedAnimation.originalAnimation = animationToReverse;
|
| + reversedAnimation.animationDirection =
|
| + previousReversedAnimation
|
| + ? AnimationDirectionOpposite(
|
| + previousReversedAnimation.animationDirection)
|
| + : ANIMATION_DIRECTION_REVERSE;
|
| +
|
| + CAAnimation* previousAnimation = previousReversedAnimation
|
| + ? previousReversedAnimation
|
| + : animationToReverse;
|
| + BOOL isReversed =
|
| + reversedAnimation.animationDirection == ANIMATION_DIRECTION_REVERSE;
|
| + if (now < previousAnimation.beginTime) {
|
| + // Reversal occurs before previous animation begins.
|
| + reversedAnimation.beginTime = 2.0 * now - previousAnimation.beginTime -
|
| + reversedAnimation.originalAnimation.duration;
|
| + reversedAnimation.duration = reversedAnimation.originalAnimation.duration;
|
| + reversedAnimation.animationTimeOffset =
|
| + isReversed ? 0 : reversedAnimation.originalAnimation.duration;
|
| + } else if (now < previousAnimation.beginTime + previousAnimation.duration) {
|
| + // Reversal occurs while the previous animation is occurring.
|
| + reversedAnimation.beginTime = 0;
|
| + CFTimeInterval timeDelta = now - previousAnimation.beginTime;
|
| + reversedAnimation.animationTimeOffset =
|
| + previousReversedAnimation.animationTimeOffset +
|
| + (isReversed ? 1.0 : -1.0) * timeDelta;
|
| + reversedAnimation.duration =
|
| + isReversed ? reversedAnimation.animationTimeOffset
|
| + : reversedAnimation.originalAnimation.duration -
|
| + reversedAnimation.animationTimeOffset;
|
| + } else {
|
| + // Reversal occurs after the previous animation has ended.
|
| + if (!parentBeginTime) {
|
| + // If the parent's begin time is zero, the animation is using absolute
|
| + // time as its beginTime.
|
| + reversedAnimation.beginTime =
|
| + 2.0 * now - previousAnimation.beginTime - previousAnimation.duration;
|
| + } else {
|
| + CFTimeInterval previousEndTime =
|
| + previousAnimation.beginTime + previousAnimation.duration;
|
| + if (now > parent.duration) {
|
| + // The animation's parent has already ended, so use the difference
|
| + // between the parent's ending and the previous animation's end.
|
| + reversedAnimation.beginTime = parent.duration - previousEndTime;
|
| + } else {
|
| + // The parent hasn't ended, so use the difference between the current
|
| + // time and the previous animation's end.
|
| + reversedAnimation.beginTime = now - previousEndTime;
|
| + }
|
| + }
|
| + reversedAnimation.duration = reversedAnimation.originalAnimation.duration;
|
| + reversedAnimation.animationTimeOffset =
|
| + isReversed ? reversedAnimation.originalAnimation.duration : 0;
|
| + }
|
| +}
|
| +
|
| +void ReverseAnimationsForKeyForLayers(NSString* key, NSArray* layers) {
|
| + for (CALayer* layer in layers) {
|
| + CAAnimation* reversedAnimation =
|
| + CAAnimationMakeReverse([layer animationForKey:key], layer);
|
| + [layer removeAnimationForKey:key];
|
| + [layer addAnimation:reversedAnimation forKey:key];
|
| + }
|
| +}
|
|
|