| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ios/chrome/browser/ui/reversed_animation.h" |
| 6 |
| 7 #import <QuartzCore/QuartzCore.h> |
| 8 #include <algorithm> |
| 9 #include <cmath> |
| 10 |
| 11 #include "base/logging.h" |
| 12 #include "base/mac/objc_property_releaser.h" |
| 13 |
| 14 @protocol ReversedAnimationProtocol; |
| 15 typedef CAAnimation<ReversedAnimationProtocol> ReversedAnimation; |
| 16 |
| 17 namespace { |
| 18 // Enum type used to denote the direction of a reversed animation relative to |
| 19 // the original animation's direction. |
| 20 typedef enum { |
| 21 ANIMATION_DIRECTION_NORMAL, |
| 22 ANIMATION_DIRECTION_REVERSE |
| 23 } AnimationDirection; |
| 24 // Returns the AnimationDirection opposite of |direction|. |
| 25 AnimationDirection AnimationDirectionOpposite(AnimationDirection direction) { |
| 26 return direction == ANIMATION_DIRECTION_NORMAL ? ANIMATION_DIRECTION_REVERSE |
| 27 : ANIMATION_DIRECTION_NORMAL; |
| 28 } |
| 29 } // namespace |
| 30 |
| 31 // Returns an animation that reverses |animation| when added to |layer|, given |
| 32 // that |animation| is in |parent|'s timespace, which begins at |
| 33 // |parentBeginTime|. |
| 34 CAAnimation* CAAnimationMakeReverse(CAAnimation* animation, |
| 35 CALayer* layer, |
| 36 CAAnimationGroup* parent, |
| 37 CFTimeInterval parentBeginTime); |
| 38 // Updates |reversedAnimation|'s properties for |animationToReverse|, given that |
| 39 // |animationToReverse| is in |parent|'s timespace, which begins at |
| 40 // |parentBeginTime|. |
| 41 void UpdateReversedAnimation(ReversedAnimation* reversedAnimation, |
| 42 CAAnimation* animationToReverse, |
| 43 CALayer* layer, |
| 44 CAAnimationGroup* parent, |
| 45 CFTimeInterval parentBeginTime); |
| 46 |
| 47 #pragma mark - ReversedAnimation protocol |
| 48 |
| 49 @protocol ReversedAnimationProtocol<NSObject> |
| 50 |
| 51 // The original animation that's being played in reverse. |
| 52 @property(nonatomic, retain) CAAnimation* originalAnimation; |
| 53 // The current direction for the animation. |
| 54 @property(nonatomic, assign) AnimationDirection animationDirection; |
| 55 // The offset into the original animation's duration at the begining of the |
| 56 // reverse animation. |
| 57 @property(nonatomic, assign) CFTimeInterval animationTimeOffset; |
| 58 |
| 59 @end |
| 60 |
| 61 #pragma mark - ReversedBasicAnimation |
| 62 |
| 63 @interface ReversedBasicAnimation |
| 64 : CABasicAnimation<ReversedAnimationProtocol> { |
| 65 base::mac::ObjCPropertyReleaser _propertyReleaser_ReversedBasicAnimation; |
| 66 } |
| 67 |
| 68 // Returns an animation that performs |animation| in reverse when added to |
| 69 // |layer|. |parentBeginTime| should be set to the beginTime in absolute time |
| 70 // of |animation|'s parent in the timing hierarchy. |
| 71 + (instancetype)reversedAnimationForAnimation:(CABasicAnimation*)animation |
| 72 forLayer:(CALayer*)layer |
| 73 parent:(CAAnimationGroup*)parent |
| 74 parentBeginTime:(CFTimeInterval)parentBeginTime; |
| 75 |
| 76 @end |
| 77 |
| 78 @implementation ReversedBasicAnimation |
| 79 |
| 80 @synthesize originalAnimation = _originalAnimation; |
| 81 @synthesize animationDirection = _animationDirection; |
| 82 @synthesize animationTimeOffset = _animationTimeOffset; |
| 83 |
| 84 - (instancetype)init { |
| 85 self = [super init]; |
| 86 if (self) { |
| 87 _propertyReleaser_ReversedBasicAnimation.Init( |
| 88 self, [ReversedBasicAnimation class]); |
| 89 } |
| 90 return self; |
| 91 } |
| 92 |
| 93 - (instancetype)copyWithZone:(NSZone*)zone { |
| 94 ReversedBasicAnimation* copy = [super copyWithZone:zone]; |
| 95 copy.originalAnimation = self.originalAnimation; |
| 96 copy.animationDirection = self.animationDirection; |
| 97 copy.animationTimeOffset = self.animationTimeOffset; |
| 98 return copy; |
| 99 } |
| 100 |
| 101 + (instancetype)reversedAnimationForAnimation:(CABasicAnimation*)animation |
| 102 forLayer:(CALayer*)layer |
| 103 parent:(CAAnimationGroup*)parent |
| 104 parentBeginTime:(CFTimeInterval)parentBeginTime { |
| 105 // Create new animation and copy properties. Note that we can't use |-copy| |
| 106 // because we need the new animation to be the correct class. |
| 107 NSString* keyPath = animation.keyPath; |
| 108 CFTimeInterval now = |
| 109 [layer convertTime:CACurrentMediaTime() fromLayer:nil] - parentBeginTime; |
| 110 ReversedBasicAnimation* reversedAnimation = |
| 111 [ReversedBasicAnimation animationWithKeyPath:keyPath]; |
| 112 UpdateReversedAnimation(reversedAnimation, animation, layer, parent, |
| 113 parentBeginTime); |
| 114 |
| 115 // Update from and to values. |
| 116 BOOL isReversed = |
| 117 reversedAnimation.animationDirection == ANIMATION_DIRECTION_REVERSE; |
| 118 CABasicAnimation* originalBasicAnimation = |
| 119 static_cast<CABasicAnimation*>(reversedAnimation.originalAnimation); |
| 120 reversedAnimation.toValue = isReversed ? originalBasicAnimation.fromValue |
| 121 : originalBasicAnimation.toValue; |
| 122 if (now > animation.beginTime && |
| 123 now < animation.beginTime + animation.duration) { |
| 124 // Use the presentation layer's current value for reversals that occur mid- |
| 125 // animation. |
| 126 reversedAnimation.fromValue = |
| 127 [[layer presentationLayer] valueForKeyPath:keyPath]; |
| 128 } else { |
| 129 reversedAnimation.fromValue = isReversed ? originalBasicAnimation.toValue |
| 130 : originalBasicAnimation.fromValue; |
| 131 } |
| 132 return reversedAnimation; |
| 133 } |
| 134 |
| 135 @end |
| 136 |
| 137 #pragma mark - ReversedAnimationGroup |
| 138 |
| 139 @interface ReversedAnimationGroup |
| 140 : CAAnimationGroup<ReversedAnimationProtocol> { |
| 141 base::mac::ObjCPropertyReleaser _propertyReleaser_ReversedAnimationGroup; |
| 142 } |
| 143 |
| 144 // Returns an animation that performs |animation| in reverse when added to |
| 145 // |layer|. |parentBeginTime| should be set to the beginTime in absolute time |
| 146 // of the animation group to which |animation| belongs. |
| 147 + (instancetype)reversedAnimationGroupForGroup:(CAAnimationGroup*)group |
| 148 forLayer:(CALayer*)layer |
| 149 parent:(CAAnimationGroup*)parent |
| 150 parentBeginTime:(CFTimeInterval)parentBeginTime; |
| 151 |
| 152 @end |
| 153 |
| 154 @implementation ReversedAnimationGroup |
| 155 |
| 156 @synthesize originalAnimation = _originalAnimation; |
| 157 @synthesize animationDirection = _animationDirection; |
| 158 @synthesize animationTimeOffset = _animationTimeOffset; |
| 159 |
| 160 - (instancetype)init { |
| 161 self = [super init]; |
| 162 if (self) { |
| 163 _propertyReleaser_ReversedAnimationGroup.Init( |
| 164 self, [ReversedAnimationGroup class]); |
| 165 } |
| 166 return self; |
| 167 } |
| 168 |
| 169 - (instancetype)copyWithZone:(NSZone*)zone { |
| 170 ReversedAnimationGroup* copy = [super copyWithZone:zone]; |
| 171 copy.originalAnimation = self.originalAnimation; |
| 172 copy.animationDirection = self.animationDirection; |
| 173 copy.animationTimeOffset = self.animationTimeOffset; |
| 174 return copy; |
| 175 } |
| 176 |
| 177 + (instancetype)reversedAnimationGroupForGroup:(CAAnimationGroup*)group |
| 178 forLayer:(CALayer*)layer |
| 179 parent:(CAAnimationGroup*)parent |
| 180 parentBeginTime:(CFTimeInterval)parentBeginTime { |
| 181 // Create new animation and copy properties. Note that we can't use |-copy| |
| 182 // because we need the new animation to be the correct class. |
| 183 ReversedAnimationGroup* reversedGroup = [ReversedAnimationGroup animation]; |
| 184 UpdateReversedAnimation(reversedGroup, group, layer, parent, parentBeginTime); |
| 185 |
| 186 // Reverse the animations of the original group. |
| 187 NSMutableArray* reversedAnimations = [NSMutableArray array]; |
| 188 for (CAAnimation* animation in group.animations) { |
| 189 CAAnimation* reversedAnimation = CAAnimationMakeReverse( |
| 190 animation, layer, group, group.beginTime + parentBeginTime); |
| 191 [reversedAnimations addObject:reversedAnimation]; |
| 192 } |
| 193 reversedGroup.animations = reversedAnimations; |
| 194 |
| 195 return reversedGroup; |
| 196 } |
| 197 |
| 198 @end |
| 199 |
| 200 #pragma mark - animation_util functions |
| 201 |
| 202 CAAnimation* CAAnimationMakeReverse(CAAnimation* animation, CALayer* layer) { |
| 203 return CAAnimationMakeReverse(animation, layer, nil, layer.beginTime); |
| 204 } |
| 205 |
| 206 CAAnimation* CAAnimationMakeReverse(CAAnimation* animation, |
| 207 CALayer* layer, |
| 208 CAAnimationGroup* parent, |
| 209 CFTimeInterval parentBeginTime) { |
| 210 CAAnimation* reversedAnimation = nil; |
| 211 if ([animation isKindOfClass:[CABasicAnimation class]]) { |
| 212 CABasicAnimation* basicAnimation = |
| 213 static_cast<CABasicAnimation*>(animation); |
| 214 reversedAnimation = |
| 215 [ReversedBasicAnimation reversedAnimationForAnimation:basicAnimation |
| 216 forLayer:layer |
| 217 parent:parent |
| 218 parentBeginTime:parentBeginTime]; |
| 219 } else if ([animation isKindOfClass:[CAAnimationGroup class]]) { |
| 220 CAAnimationGroup* animationGroup = |
| 221 static_cast<CAAnimationGroup*>(animation); |
| 222 reversedAnimation = |
| 223 [ReversedAnimationGroup reversedAnimationGroupForGroup:animationGroup |
| 224 forLayer:layer |
| 225 parent:parent |
| 226 parentBeginTime:parentBeginTime]; |
| 227 } else { |
| 228 // TODO(kkhorimoto): Investigate possible general-case reversals. It may |
| 229 // be possible to implement this by manipulating the CAMediaTiming |
| 230 // properties. |
| 231 } |
| 232 return reversedAnimation; |
| 233 } |
| 234 |
| 235 void UpdateReversedAnimation(ReversedAnimation* reversedAnimation, |
| 236 CAAnimation* animationToReverse, |
| 237 CALayer* layer, |
| 238 CAAnimationGroup* parent, |
| 239 CFTimeInterval parentBeginTime) { |
| 240 // Copy properties. |
| 241 CFTimeInterval now = |
| 242 [layer convertTime:CACurrentMediaTime() fromLayer:nil] - parentBeginTime; |
| 243 reversedAnimation.fillMode = animationToReverse.fillMode; |
| 244 reversedAnimation.removedOnCompletion = |
| 245 animationToReverse.removedOnCompletion; |
| 246 reversedAnimation.timingFunction = animationToReverse.timingFunction; |
| 247 |
| 248 // Extract the previous reversal if it exists. |
| 249 ReversedAnimation* previousReversedAnimation = nil; |
| 250 Protocol* reversedAnimationProtocol = @protocol(ReversedAnimationProtocol); |
| 251 if ([animationToReverse conformsToProtocol:reversedAnimationProtocol]) { |
| 252 previousReversedAnimation = |
| 253 static_cast<ReversedAnimation*>(animationToReverse); |
| 254 animationToReverse = previousReversedAnimation.originalAnimation; |
| 255 } |
| 256 reversedAnimation.originalAnimation = animationToReverse; |
| 257 reversedAnimation.animationDirection = |
| 258 previousReversedAnimation |
| 259 ? AnimationDirectionOpposite( |
| 260 previousReversedAnimation.animationDirection) |
| 261 : ANIMATION_DIRECTION_REVERSE; |
| 262 |
| 263 CAAnimation* previousAnimation = previousReversedAnimation |
| 264 ? previousReversedAnimation |
| 265 : animationToReverse; |
| 266 BOOL isReversed = |
| 267 reversedAnimation.animationDirection == ANIMATION_DIRECTION_REVERSE; |
| 268 if (now < previousAnimation.beginTime) { |
| 269 // Reversal occurs before previous animation begins. |
| 270 reversedAnimation.beginTime = 2.0 * now - previousAnimation.beginTime - |
| 271 reversedAnimation.originalAnimation.duration; |
| 272 reversedAnimation.duration = reversedAnimation.originalAnimation.duration; |
| 273 reversedAnimation.animationTimeOffset = |
| 274 isReversed ? 0 : reversedAnimation.originalAnimation.duration; |
| 275 } else if (now < previousAnimation.beginTime + previousAnimation.duration) { |
| 276 // Reversal occurs while the previous animation is occurring. |
| 277 reversedAnimation.beginTime = 0; |
| 278 CFTimeInterval timeDelta = now - previousAnimation.beginTime; |
| 279 reversedAnimation.animationTimeOffset = |
| 280 previousReversedAnimation.animationTimeOffset + |
| 281 (isReversed ? 1.0 : -1.0) * timeDelta; |
| 282 reversedAnimation.duration = |
| 283 isReversed ? reversedAnimation.animationTimeOffset |
| 284 : reversedAnimation.originalAnimation.duration - |
| 285 reversedAnimation.animationTimeOffset; |
| 286 } else { |
| 287 // Reversal occurs after the previous animation has ended. |
| 288 if (!parentBeginTime) { |
| 289 // If the parent's begin time is zero, the animation is using absolute |
| 290 // time as its beginTime. |
| 291 reversedAnimation.beginTime = |
| 292 2.0 * now - previousAnimation.beginTime - previousAnimation.duration; |
| 293 } else { |
| 294 CFTimeInterval previousEndTime = |
| 295 previousAnimation.beginTime + previousAnimation.duration; |
| 296 if (now > parent.duration) { |
| 297 // The animation's parent has already ended, so use the difference |
| 298 // between the parent's ending and the previous animation's end. |
| 299 reversedAnimation.beginTime = parent.duration - previousEndTime; |
| 300 } else { |
| 301 // The parent hasn't ended, so use the difference between the current |
| 302 // time and the previous animation's end. |
| 303 reversedAnimation.beginTime = now - previousEndTime; |
| 304 } |
| 305 } |
| 306 reversedAnimation.duration = reversedAnimation.originalAnimation.duration; |
| 307 reversedAnimation.animationTimeOffset = |
| 308 isReversed ? reversedAnimation.originalAnimation.duration : 0; |
| 309 } |
| 310 } |
| 311 |
| 312 void ReverseAnimationsForKeyForLayers(NSString* key, NSArray* layers) { |
| 313 for (CALayer* layer in layers) { |
| 314 CAAnimation* reversedAnimation = |
| 315 CAAnimationMakeReverse([layer animationForKey:key], layer); |
| 316 [layer removeAnimationForKey:key]; |
| 317 [layer addAnimation:reversedAnimation forKey:key]; |
| 318 } |
| 319 } |
| OLD | NEW |