/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import // We do some normalizing of the event names in RCTEventDispatcher#RCTNormalizeInputEventName. // To make things simpler just get rid of the parts we change in the event names we use here. // This is a lot easier than trying to denormalize because there would be multiple possible // denormalized forms for a single input. static NSString *RCTNormalizeAnimatedEventName(NSString *eventName) { if ([eventName hasPrefix:@"on"]) { return [eventName substringFromIndex:2]; } if ([eventName hasPrefix:@"top"]) { return [eventName substringFromIndex:3]; } return eventName; } @implementation RCTNativeAnimatedNodesManager { __weak RCTBridge *_bridge; __weak id _surfacePresenter; NSMutableDictionary *_animationNodes; // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time // there will be only one driver per mapping so all code code should be optimized around that. NSMutableDictionary *> *_eventDrivers; NSMutableSet> *_activeAnimations; CADisplayLink *_displayLink; } - (instancetype)initWithBridge:(nullable RCTBridge *)bridge surfacePresenter:(id)surfacePresenter; { if ((self = [super init])) { _bridge = bridge; _surfacePresenter = surfacePresenter; _animationNodes = [NSMutableDictionary new]; _eventDrivers = [NSMutableDictionary new]; _activeAnimations = [NSMutableSet new]; } return self; } - (BOOL)isNodeManagedByFabric:(NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; if (node) { return [node isManagedByFabric]; } return false; } #pragma mark-- Graph - (void)createAnimatedNode:(NSNumber *)tag config:(NSDictionary *)config { static NSDictionary *map; static dispatch_once_t mapToken; dispatch_once(&mapToken, ^{ map = @{ @"style" : [RCTStyleAnimatedNode class], @"value" : [RCTValueAnimatedNode class], @"color" : [RCTColorAnimatedNode class], @"props" : [RCTPropsAnimatedNode class], @"interpolation" : [RCTInterpolationAnimatedNode class], @"addition" : [RCTAdditionAnimatedNode class], @"diffclamp" : [RCTDiffClampAnimatedNode class], @"division" : [RCTDivisionAnimatedNode class], @"multiplication" : [RCTMultiplicationAnimatedNode class], @"modulus" : [RCTModuloAnimatedNode class], @"subtraction" : [RCTSubtractionAnimatedNode class], @"transform" : [RCTTransformAnimatedNode class], @"tracking" : [RCTTrackingAnimatedNode class] }; }); NSString *nodeType = [RCTConvert NSString:config[@"type"]]; Class nodeClass = map[nodeType]; if (!nodeClass) { RCTLogError(@"Animated node type %@ not supported natively", nodeType); return; } RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config]; node.manager = self; _animationNodes[tag] = node; [node setNeedsUpdate]; } - (void)connectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag { RCTAssertParam(parentTag); RCTAssertParam(childTag); RCTAnimatedNode *parentNode = _animationNodes[parentTag]; RCTAnimatedNode *childNode = _animationNodes[childTag]; RCTAssertParam(parentNode); RCTAssertParam(childNode); [parentNode addChild:childNode]; [childNode setNeedsUpdate]; } - (void)disconnectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag { RCTAssertParam(parentTag); RCTAssertParam(childTag); RCTAnimatedNode *parentNode = _animationNodes[parentTag]; RCTAnimatedNode *childNode = _animationNodes[childTag]; RCTAssertParam(parentNode); RCTAssertParam(childNode); [parentNode removeChild:childNode]; [childNode setNeedsUpdate]; } - (void)connectAnimatedNodeToView:(NSNumber *)nodeTag viewTag:(NSNumber *)viewTag viewName:(nullable NSString *)viewName { RCTAnimatedNode *node = _animationNodes[nodeTag]; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { // viewName is not used when node is managed by Fabric [(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName bridge:_bridge surfacePresenter:_surfacePresenter]; } [node setNeedsUpdate]; } - (void)disconnectAnimatedNodeFromView:(NSNumber *)nodeTag viewTag:(NSNumber *)viewTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node disconnectFromView:viewTag]; } } - (void)restoreDefaultValues:(NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; // Restoring default values needs to happen before UIManager operations so it is // possible the node hasn't been created yet if it is being connected and // disconnected in the same batch. In that case we don't need to restore // default values since it will never actually update the view. if (node == nil) { return; } if (![node isKindOfClass:[RCTPropsAnimatedNode class]]) { RCTLogError(@"Not a props node."); } [(RCTPropsAnimatedNode *)node restoreDefaultValues]; } - (void)dropAnimatedNode:(NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; if (node) { [node detachNode]; [_animationNodes removeObjectForKey:tag]; } } #pragma mark-- Mutations - (void)setAnimatedNodeValue:(NSNumber *)nodeTag value:(NSNumber *)value { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } [self stopAnimationsForNode:node]; RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; valueNode.value = value.floatValue; [valueNode setNeedsUpdate]; } - (void)setAnimatedNodeOffset:(NSNumber *)nodeTag offset:(NSNumber *)offset { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode setOffset:offset.floatValue]; [valueNode setNeedsUpdate]; } - (void)flattenAnimatedNodeOffset:(NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode flattenOffset]; } - (void)extractAnimatedNodeOffset:(NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode extractOffset]; } - (void)getValue:(NSNumber *)nodeTag saveCallback:(RCTResponseSenderBlock)saveCallback { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; ; saveCallback(@[ @(valueNode.value) ]); } - (void)updateAnimatedNodeConfig:(NSNumber *)tag config:(NSDictionary *)config { // TODO (T111179606): Support platform colors for color animations } #pragma mark-- Drivers - (void)startAnimatingNode:(NSNumber *)animationId nodeTag:(NSNumber *)nodeTag config:(NSDictionary *)config endCallback:(nullable RCTResponseSenderBlock)callBack { // check if the animation has already started for (id driver in _activeAnimations) { if ([driver.animationId isEqual:animationId]) { // if the animation is running, we restart it with an updated configuration [driver resetAnimationConfig:config]; return; } } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; NSString *type = config[@"type"]; id animationDriver; if ([type isEqual:@"frames"]) { animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else if ([type isEqual:@"spring"]) { animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else if ([type isEqual:@"decay"]) { animationDriver = [[RCTDecayAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else { RCTLogError(@"Unsupported animation type: %@", config[@"type"]); return; } [_activeAnimations addObject:animationDriver]; [animationDriver startAnimation]; [self startAnimationLoopIfNeeded]; } - (void)stopAnimation:(NSNumber *)animationId { for (id driver in _activeAnimations) { if ([driver.animationId isEqual:animationId]) { [driver stopAnimation]; [_activeAnimations removeObject:driver]; break; } } } - (void)stopAnimationsForNode:(RCTAnimatedNode *)node { NSMutableArray> *discarded = [NSMutableArray new]; for (id driver in _activeAnimations) { if ([driver.valueNode isEqual:node]) { [discarded addObject:driver]; } } for (id driver in discarded) { [driver stopAnimation]; [_activeAnimations removeObject:driver]; } } #pragma mark-- Events - (void)addAnimatedEventToView:(NSNumber *)viewTag eventName:(NSString *)eventName eventMapping:(NSDictionary *)eventMapping { NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]]; RCTAnimatedNode *node = _animationNodes[nodeTag]; if (!node) { RCTLogError(@"Animated node with tag %@ does not exists", nodeTag); return; } if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode"); return; } NSArray *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]]; RCTEventAnimation *driver = [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node]; NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, RCTNormalizeAnimatedEventName(eventName)]; if (_eventDrivers[key] != nil) { [_eventDrivers[key] addObject:driver]; } else { NSMutableArray *drivers = [NSMutableArray new]; [drivers addObject:driver]; _eventDrivers[key] = drivers; } } - (void)removeAnimatedEventFromView:(NSNumber *)viewTag eventName:(NSString *)eventName animatedNodeTag:(NSNumber *)animatedNodeTag { NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, RCTNormalizeAnimatedEventName(eventName)]; if (_eventDrivers[key] != nil) { if (_eventDrivers[key].count == 1) { [_eventDrivers removeObjectForKey:key]; } else { NSMutableArray *driversForKey = _eventDrivers[key]; for (NSUInteger i = 0; i < driversForKey.count; i++) { if (driversForKey[i].valueNode.nodeTag == animatedNodeTag) { [driversForKey removeObjectAtIndex:i]; break; } } } } } - (void)handleAnimatedEvent:(id)event { if (_eventDrivers.count == 0) { return; } NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, RCTNormalizeAnimatedEventName(event.eventName)]; NSMutableArray *driversForKey = _eventDrivers[key]; if (driversForKey) { for (RCTEventAnimation *driver in driversForKey) { [self stopAnimationsForNode:driver.valueNode]; [driver updateWithEvent:event]; } [self updateAnimations]; } } #pragma mark-- Listeners - (void)startListeningToAnimatedNodeValue:(NSNumber *)tag valueObserver:(id)valueObserver { RCTAnimatedNode *node = _animationNodes[tag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = valueObserver; } } - (void)stopListeningToAnimatedNodeValue:(NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = nil; } } #pragma mark-- Animation Loop - (void)startAnimationLoopIfNeeded { if (!_displayLink && _activeAnimations.count > 0) { _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } } - (void)stopAnimationLoopIfNeeded { if (_activeAnimations.count == 0) { [self stopAnimationLoop]; } } - (void)stopAnimationLoop { if (_displayLink) { [_displayLink invalidate]; _displayLink = nil; } } - (void)stepAnimations:(CADisplayLink *)displaylink { NSTimeInterval time = displaylink.timestamp; for (id animationDriver in _activeAnimations) { [animationDriver stepAnimationWithTime:time]; } [self updateAnimations]; for (id animationDriver in [_activeAnimations copy]) { if (animationDriver.animationHasFinished) { [animationDriver stopAnimation]; [_activeAnimations removeObject:animationDriver]; } } [self stopAnimationLoopIfNeeded]; } #pragma mark-- Updates - (void)updateAnimations { [_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) { if (node.needsUpdate) { [node updateNodeIfNecessary]; } }]; } @end