blob: d0e783d442e026ff6f5f9a5709f2143e92910934 [file] [log] [blame] [edit]
// Copyright 2023-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "M3CAnimationActions.h"
NS_ASSUME_NONNULL_BEGIN
NSString *_Nonnull const M3CAnySourceLayerAnimationKey = @"*";
@implementation M3CPendingBasicAnimationAction
#pragma mark - CAAction
- (void)runActionForKey:(nonnull NSString *)key
object:(nonnull id)object
arguments:(nullable NSDictionary<id, id> *)dict {
if (![object isKindOfClass:[CALayer class]]) {
return;
}
CALayer *layer = (CALayer *)object;
CALayer *sourceLayer = _sourceLayer ?: layer;
NSArray<NSString *> *sourceLayerAnimationKeys = sourceLayer.animationKeys;
NSArray<NSString *> *sourceAnimationKeys;
if (_sourceAnimationKeys.count > 0) {
sourceAnimationKeys = _sourceAnimationKeys;
} else {
sourceAnimationKeys = @[ key ];
}
CAAnimation *sourceAnimation = nil;
for (NSString *sourceAnimationKey in sourceAnimationKeys) {
if ([sourceAnimationKey isEqualToString:M3CAnySourceLayerAnimationKey]) {
sourceAnimation = [sourceLayer animationForKey:sourceLayerAnimationKeys.firstObject];
break;
}
sourceAnimation = [sourceLayer animationForKey:sourceAnimationKey];
if (!sourceAnimation) {
// Check for an animation decomposition (e.g., bounds -> bounds.size/bounds.position).
for (NSString *animationKey in sourceLayerAnimationKeys) {
if ([animationKey hasPrefix:sourceAnimationKey]) {
sourceAnimation = [sourceLayer animationForKey:animationKey];
break;
}
}
}
if (sourceAnimation) {
break;
}
}
id toValue = _toValueBlock ? _toValueBlock() : nil;
// If a source layer or animation key is explicitly given, a source animation must be found for
// this action to create and add an animation.
BOOL shouldHaveSourceAnimation = (_sourceLayer || _sourceAnimationKeys.firstObject);
// `fromValue` and `toValue` default to the current property value when nil.
BOOL areEqualOrBothNil = _fromValue == toValue || (_fromValue == nil && toValue == nil);
if (areEqualOrBothNil || (shouldHaveSourceAnimation && !sourceAnimation)) {
// If the value isn't changing or the given source animation couldn't be found, remove any old
// one and do nothing.
[layer removeAnimationForKey:key];
return;
}
CABasicAnimation *basicAnimation = [CABasicAnimation animation];
basicAnimation.keyPath = key;
basicAnimation.fromValue = _fromValue;
basicAnimation.toValue = toValue;
if (_duration > 0.0) {
basicAnimation.duration = _duration;
} else if (sourceAnimation) {
basicAnimation.duration = sourceAnimation.duration;
}
if (_timingFunction) {
basicAnimation.timingFunction = _timingFunction;
} else if (sourceAnimation) {
basicAnimation.timingFunction = sourceAnimation.timingFunction;
}
[layer addAnimation:basicAnimation forKey:key];
}
@end
/** The CALayer keys for bounds, cornerRadius, and shadowPath. */
static NSString *const kBoundsAnimationKey = @"bounds";
static NSString *const kCornerRadiusAnimationKey = @"cornerRadius";
static NSString *const kShadowPathKey = @"shadowPath";
BOOL M3CIsMDCShadowPathKey(NSString *_Nullable key) { return [key isEqualToString:kShadowPathKey]; }
M3CPendingBasicAnimationAction *_Nonnull M3CShadowPathActionForLayer(CALayer *_Nonnull layer) {
M3CPendingBasicAnimationAction *action = [[M3CPendingBasicAnimationAction alloc] init];
action.key = kShadowPathKey;
action.fromValue = [layer.presentationLayer valueForKey:kShadowPathKey];
action.toValueBlock = ^{
return [layer valueForKey:kShadowPathKey];
};
// Link the shadow path animation to animations of properties that affect it.
action.sourceAnimationKeys = @[ kBoundsAnimationKey, kCornerRadiusAnimationKey ];
return action;
}
NS_ASSUME_NONNULL_END