From de7a323f6be7108fe212bdff36eeff09cffc2e62 Mon Sep 17 00:00:00 2001 From: Di Chen Date: Wed, 30 Nov 2022 14:45:07 -0800 Subject: [PATCH] Revert D40632443: Support colors for AnimatedInterpolation on iOS Differential Revision: D40632443 (https://github.com/facebook/react-native/commit/6003e70e84c369d7dc2c6bea50ea41f0bac79595) Original commit changeset: 4dfb29edca4b Original Phabricator Diff: D40632443 (https://github.com/facebook/react-native/commit/6003e70e84c369d7dc2c6bea50ea41f0bac79595) fbshipit-source-id: 0718fbd18c3a7a06e116d9926d40d8d9a75ed4cd --- .../Nodes/RCTColorAnimatedNode.m | 5 +- .../Nodes/RCTInterpolationAnimatedNode.h | 10 - .../Nodes/RCTInterpolationAnimatedNode.m | 212 +++++++++--------- .../Nodes/RCTStyleAnimatedNode.m | 7 +- .../Nodes/RCTValueAnimatedNode.h | 2 +- .../Nodes/RCTValueAnimatedNode.m | 5 - Libraries/NativeAnimation/RCTAnimationUtils.h | 27 +-- Libraries/NativeAnimation/RCTAnimationUtils.m | 30 +-- .../RCTAnimationUtilsTests.m | 32 --- 9 files changed, 124 insertions(+), 206 deletions(-) diff --git a/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.m index d35d6388651..3a86978d87d 100644 --- a/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.m @@ -8,8 +8,6 @@ #import #import -#import - @implementation RCTColorAnimatedNode - (void)performUpdate @@ -21,7 +19,8 @@ RCTValueAnimatedNode *bNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"b"]]; RCTValueAnimatedNode *aNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"a"]]; - _color = RCTColorFromComponents(rNode.value, gNode.value, bNode.value, aNode.value); + _color = ((int)round(aNode.value * 255) & 0xff) << 24 | ((int)round(rNode.value) & 0xff) << 16 | + ((int)round(gNode.value) & 0xff) << 8 | ((int)round(bNode.value) & 0xff); // TODO (T111179606): Support platform colors for color animations } diff --git a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h index 903b49286a2..2ac165013dc 100644 --- a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h @@ -7,16 +7,6 @@ #import "RCTValueAnimatedNode.h" -#import - -RCT_EXTERN NSString *RCTInterpolateString( - NSString *pattern, - CGFloat inputValue, - NSArray *inputRange, - NSArray *> *outputRange, - NSString *extrapolateLeft, - NSString *extrapolateRight); - @interface RCTInterpolationAnimatedNode : RCTValueAnimatedNode @end diff --git a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m index a1110178185..3950d4f1565 100644 --- a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m @@ -8,19 +8,25 @@ #import #import -#import static NSRegularExpression *regex; -typedef enum { - kNumber, - kColor, - kString, -} RCTInterpolationOutputType; +@implementation RCTInterpolationAnimatedNode { + __weak RCTValueAnimatedNode *_parentNode; + NSArray *_inputRange; + NSArray *_outputRange; + NSArray *> *_outputs; + NSArray *_soutputRange; + NSString *_extrapolateLeft; + NSString *_extrapolateRight; + NSUInteger _numVals; + bool _hasStringOutput; + bool _shouldRound; + NSArray *_matches; +} -static NSRegularExpression *getNumericComponentRegex() +- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - static NSRegularExpression *regex; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *fpRegex = @"[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?"; @@ -28,96 +34,65 @@ static NSRegularExpression *getNumericComponentRegex() options:NSRegularExpressionCaseInsensitive error:nil]; }); - return regex; -} - -static NSArray *> *outputFromStringPattern(NSString *input) -{ - NSMutableArray *output = [NSMutableArray array]; - [getNumericComponentRegex() - enumerateMatchesInString:input - options:0 - range:NSMakeRange(0, input.length) - usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { - [output addObject:@([[input substringWithRange:result.range] doubleValue])]; - }]; - return output; -} - -NSString *RCTInterpolateString( - NSString *pattern, - CGFloat inputValue, - NSArray *inputRange, - NSArray *> *outputRange, - NSString *extrapolateLeft, - NSString *extrapolateRight) -{ - NSUInteger rangeIndex = RCTFindIndexOfNearestValue(inputValue, inputRange); - - NSMutableString *output = [NSMutableString stringWithString:pattern]; - NSArray *matches = - [getNumericComponentRegex() matchesInString:pattern options:0 range:NSMakeRange(0, pattern.length)]; - NSInteger matchIndex = matches.count - 1; - for (NSTextCheckingResult *match in [matches reverseObjectEnumerator]) { - CGFloat val = RCTInterpolateValue( - inputValue, - [inputRange[rangeIndex] doubleValue], - [inputRange[rangeIndex + 1] doubleValue], - [outputRange[rangeIndex][matchIndex] doubleValue], - [outputRange[rangeIndex + 1][matchIndex] doubleValue], - extrapolateLeft, - extrapolateRight); - [output replaceCharactersInRange:match.range withString:[@(val) stringValue]]; - matchIndex--; - } - return output; -} - -@implementation RCTInterpolationAnimatedNode { - __weak RCTValueAnimatedNode *_parentNode; - NSArray *_inputRange; - NSArray *_outputRange; - NSString *_extrapolateLeft; - NSString *_extrapolateRight; - RCTInterpolationOutputType _outputType; - id _Nullable _outputvalue; - NSString *_Nullable _outputPattern; - - NSArray *_matches; -} - -- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config -{ if ((self = [super initWithTag:tag config:config])) { - _inputRange = config[@"inputRange"]; + _inputRange = [config[@"inputRange"] copy]; + NSMutableArray *outputRange = [NSMutableArray array]; + NSMutableArray *soutputRange = [NSMutableArray array]; + NSMutableArray *> *_outputRanges = [NSMutableArray array]; - NSArray *outputRangeConfig = config[@"outputRange"]; - if ([config[@"outputType"] isEqual:@"color"]) { - _outputType = kColor; - } else if ([outputRangeConfig[0] isKindOfClass:[NSString class]]) { - _outputType = kString; - _outputPattern = outputRangeConfig[0]; - } else { - _outputType = kNumber; - } + _hasStringOutput = NO; + for (id value in config[@"outputRange"]) { + if ([value isKindOfClass:[NSNumber class]]) { + [outputRange addObject:value]; + } else if ([value isKindOfClass:[NSString class]]) { + /** + * Supports string shapes by extracting numbers so new values can be computed, + * and recombines those values into new strings of the same shape. Supports + * things like: + * + * rgba(123, 42, 99, 0.36) // colors + * -45deg // values with units + */ + NSMutableArray *output = [NSMutableArray array]; + [_outputRanges addObject:output]; + [soutputRange addObject:value]; - NSMutableArray *outputRange = [NSMutableArray arrayWithCapacity:outputRangeConfig.count]; - for (id value in outputRangeConfig) { - switch (_outputType) { - case kColor: { - UIColor *color = [RCTConvert UIColor:value]; - [outputRange addObject:color ? color : [UIColor whiteColor]]; - break; + _matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])]; + for (NSTextCheckingResult *match in _matches) { + NSString *strNumber = [value substringWithRange:match.range]; + [output addObject:[NSNumber numberWithDouble:strNumber.doubleValue]]; } - case kString: - [outputRange addObject:outputFromStringPattern(value)]; - break; - case kNumber: - [outputRange addObject:value]; - break; + + _hasStringOutput = YES; + [outputRange addObject:[output objectAtIndex:0]]; } } - _outputRange = outputRange; + if (_hasStringOutput) { + // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] + // -> + // [ + // [0, 50], + // [100, 150], + // [200, 250], + // [0, 0.5], + // ] + _numVals = [_matches count]; + NSString *value = [soutputRange objectAtIndex:0]; + _shouldRound = [value containsString:@"rgb"]; + _matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])]; + NSMutableArray *> *outputs = [NSMutableArray arrayWithCapacity:_numVals]; + NSUInteger size = [soutputRange count]; + for (NSUInteger j = 0; j < _numVals; j++) { + NSMutableArray *output = [NSMutableArray arrayWithCapacity:size]; + [outputs addObject:output]; + for (int i = 0; i < size; i++) { + [output addObject:[[_outputRanges objectAtIndex:i] objectAtIndex:j]]; + } + } + _outputs = [outputs copy]; + } + _outputRange = [outputRange copy]; + _soutputRange = [soutputRange copy]; _extrapolateLeft = config[@"extrapolateLeft"]; _extrapolateRight = config[@"extrapolateRight"]; } @@ -148,24 +123,43 @@ NSString *RCTInterpolateString( } CGFloat inputValue = _parentNode.value; - switch (_outputType) { - case kColor: - _outputvalue = @(RCTInterpolateColorInRange(inputValue, _inputRange, _outputRange)); - break; - case kString: - _outputvalue = RCTInterpolateString( - _outputPattern, inputValue, _inputRange, _outputRange, _extrapolateLeft, _extrapolateRight); - break; - case kNumber: - self.value = - RCTInterpolateValueInRange(inputValue, _inputRange, _outputRange, _extrapolateLeft, _extrapolateRight); - break; + + CGFloat interpolated = + RCTInterpolateValueInRange(inputValue, _inputRange, _outputRange, _extrapolateLeft, _extrapolateRight); + self.value = interpolated; + if (_hasStringOutput) { + // 'rgba(0, 100, 200, 0)' + // -> + // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' + if (_numVals > 1) { + NSString *text = _soutputRange[0]; + NSMutableString *formattedText = [NSMutableString stringWithString:text]; + NSUInteger i = _numVals; + for (NSTextCheckingResult *match in [_matches reverseObjectEnumerator]) { + CGFloat val = + RCTInterpolateValueInRange(inputValue, _inputRange, _outputs[--i], _extrapolateLeft, _extrapolateRight); + NSString *str; + if (_shouldRound) { + // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to + // round the opacity (4th column). + bool isAlpha = i == 3; + CGFloat rounded = isAlpha ? round(val * 1000) / 1000 : round(val); + str = isAlpha ? [NSString stringWithFormat:@"%1.3f", rounded] : [NSString stringWithFormat:@"%1.0f", rounded]; + } else { + NSNumber *numberValue = [NSNumber numberWithDouble:val]; + str = [numberValue stringValue]; + } + + [formattedText replaceCharactersInRange:[match range] withString:str]; + } + self.animatedObject = formattedText; + } else { + self.animatedObject = [regex stringByReplacingMatchesInString:_soutputRange[0] + options:0 + range:NSMakeRange(0, _soutputRange[0].length) + withTemplate:[NSString stringWithFormat:@"%1f", interpolated]]; + } } } -- (id)animatedObject -{ - return _outputvalue; -} - @end diff --git a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m index 180b2b1e784..e99992ee78d 100644 --- a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m @@ -38,12 +38,7 @@ if (node) { if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTValueAnimatedNode *valueAnimatedNode = (RCTValueAnimatedNode *)node; - id animatedObject = valueAnimatedNode.animatedObject; - if (animatedObject) { - _propsDictionary[property] = animatedObject; - } else { - _propsDictionary[property] = @(valueAnimatedNode.value); - } + _propsDictionary[property] = @(valueAnimatedNode.value); } else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) { RCTTransformAnimatedNode *transformAnimatedNode = (RCTTransformAnimatedNode *)node; [_propsDictionary addEntriesFromDictionary:transformAnimatedNode.propsDictionary]; diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h index 84d220bb7b0..ccc33462c0a 100644 --- a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h @@ -24,7 +24,7 @@ - (void)extractOffset; @property (nonatomic, assign) CGFloat value; -@property (nonatomic, strong, readonly) id animatedObject; +@property (nonatomic, strong) id animatedObject; @property (nonatomic, weak) id valueObserver; @end diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m index 60d118cb085..d128c9d92dd 100644 --- a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m @@ -43,11 +43,6 @@ return _value + _offset; } -- (id)animatedObject -{ - return nil; -} - - (void)setValue:(CGFloat)value { _value = value; diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.h b/Libraries/NativeAnimation/RCTAnimationUtils.h index 338a88b4832..97594f35d87 100644 --- a/Libraries/NativeAnimation/RCTAnimationUtils.h +++ b/Libraries/NativeAnimation/RCTAnimationUtils.h @@ -10,11 +10,16 @@ #import -RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_IDENTITY; -RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_CLAMP; -RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_EXTEND; +static NSString *const EXTRAPOLATE_TYPE_IDENTITY = @"identity"; +static NSString *const EXTRAPOLATE_TYPE_CLAMP = @"clamp"; +static NSString *const EXTRAPOLATE_TYPE_EXTEND = @"extend"; -RCT_EXTERN NSUInteger RCTFindIndexOfNearestValue(CGFloat value, NSArray *range); +RCT_EXTERN CGFloat RCTInterpolateValueInRange( + CGFloat value, + NSArray *inputRange, + NSArray *outputRange, + NSString *extrapolateLeft, + NSString *extrapolateRight); RCT_EXTERN CGFloat RCTInterpolateValue( CGFloat value, @@ -25,18 +30,8 @@ RCT_EXTERN CGFloat RCTInterpolateValue( NSString *extrapolateLeft, NSString *extrapolateRight); -RCT_EXTERN CGFloat RCTInterpolateValueInRange( - CGFloat value, - NSArray *inputRange, - NSArray *outputRange, - NSString *extrapolateLeft, - NSString *extrapolateRight); - -RCT_EXTERN int32_t -RCTInterpolateColorInRange(CGFloat value, NSArray *inputRange, NSArray *outputRange); - -// Represents a color as a int32_t. RGB components are assumed to be in [0-255] range and alpha in [0-1] range -RCT_EXTERN int32_t RCTColorFromComponents(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); +RCT_EXTERN CGFloat RCTRadiansToDegrees(CGFloat radians); +RCT_EXTERN CGFloat RCTDegreesToRadians(CGFloat degrees); /** * Coefficient to slow down animations, respects the ios diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.m b/Libraries/NativeAnimation/RCTAnimationUtils.m index 22ee2bc9080..1f97b22baf0 100644 --- a/Libraries/NativeAnimation/RCTAnimationUtils.m +++ b/Libraries/NativeAnimation/RCTAnimationUtils.m @@ -9,11 +9,7 @@ #import -NSString *const EXTRAPOLATE_TYPE_IDENTITY = @"identity"; -NSString *const EXTRAPOLATE_TYPE_CLAMP = @"clamp"; -NSString *const EXTRAPOLATE_TYPE_EXTEND = @"extend"; - -NSUInteger RCTFindIndexOfNearestValue(CGFloat value, NSArray *range) +static NSUInteger _RCTFindIndexOfNearestValue(CGFloat value, NSArray *range) { NSUInteger index; NSUInteger rangeCount = range.count; @@ -75,7 +71,7 @@ CGFloat RCTInterpolateValueInRange( NSString *extrapolateLeft, NSString *extrapolateRight) { - NSUInteger rangeIndex = RCTFindIndexOfNearestValue(value, inputRange); + NSUInteger rangeIndex = _RCTFindIndexOfNearestValue(value, inputRange); CGFloat inputMin = inputRange[rangeIndex].doubleValue; CGFloat inputMax = inputRange[rangeIndex + 1].doubleValue; CGFloat outputMin = outputRange[rangeIndex].doubleValue; @@ -84,28 +80,14 @@ CGFloat RCTInterpolateValueInRange( return RCTInterpolateValue(value, inputMin, inputMax, outputMin, outputMax, extrapolateLeft, extrapolateRight); } -int32_t RCTInterpolateColorInRange(CGFloat value, NSArray *inputRange, NSArray *outputRange) +CGFloat RCTRadiansToDegrees(CGFloat radians) { - NSUInteger rangeIndex = RCTFindIndexOfNearestValue(value, inputRange); - CGFloat inputMin = inputRange[rangeIndex].doubleValue; - CGFloat inputMax = inputRange[rangeIndex + 1].doubleValue; - - CGFloat redMin, greenMin, blueMin, alphaMin; - [outputRange[rangeIndex] getRed:&redMin green:&greenMin blue:&blueMin alpha:&alphaMin]; - CGFloat redMax, greenMax, blueMax, alphaMax; - [outputRange[rangeIndex + 1] getRed:&redMax green:&greenMax blue:&blueMax alpha:&alphaMax]; - - return RCTColorFromComponents( - 0xFF * (redMin + (value - inputMin) * (redMax - redMin) / (inputMax - inputMin)), - 0xFF * (greenMin + (value - inputMin) * (greenMax - greenMin) / (inputMax - inputMin)), - 0xFF * (blueMin + (value - inputMin) * (blueMax - blueMin) / (inputMax - inputMin)), - alphaMin + (value - inputMin) * (alphaMax - alphaMin) / (inputMax - inputMin)); + return radians * 180.0 / M_PI; } -int32_t RCTColorFromComponents(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) +CGFloat RCTDegreesToRadians(CGFloat degrees) { - return ((int)round(alpha * 255) & 0xFF) << 24 | ((int)round(red) & 0xFF) << 16 | ((int)round(green) & 0xFF) << 8 | - ((int)round(blue) & 0xFF); + return degrees / 180.0 * M_PI; } #if TARGET_IPHONE_SIMULATOR diff --git a/packages/rn-tester/RNTesterUnitTests/RCTAnimationUtilsTests.m b/packages/rn-tester/RNTesterUnitTests/RCTAnimationUtilsTests.m index 5926ffd3827..a9ae7df513d 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTAnimationUtilsTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTAnimationUtilsTests.m @@ -8,8 +8,6 @@ #import #import -#import -#import @interface RCTAnimationUtilsTests : XCTestCase @@ -95,34 +93,4 @@ static CGFloat RCTSimpleInterpolation(CGFloat value, NSArray *inputR XCTAssertEqual(value, 5); } -- (void)testColorInterpolation -{ - NSArray *input = @[ @0, @1 ]; - NSArray *output = @[ [UIColor redColor], [UIColor blueColor] ]; - uint32_t value; - value = RCTInterpolateColorInRange(0, input, output); - XCTAssertEqualObjects([RCTConvert UIColor:@(value)], [UIColor redColor]); - value = RCTInterpolateColorInRange(0.5, input, output); - XCTAssertEqualObjects( - [RCTConvert UIColor:@(value)], [UIColor colorWithRed:128. / 255 green:0 blue:128. / 255 alpha:1]); - value = RCTInterpolateColorInRange(1, input, output); - XCTAssertEqualObjects([RCTConvert UIColor:@(value)], [UIColor blueColor]); -} - -- (void)testStringInterpolation -{ - NSString *pattern = @"M20,20L20,80L80,80L80,20Z"; - NSArray *input = @[ @0, @1 ]; - NSArray *> *output = @[ - @[ @20, @20, @20, @80, @80, @80, @80, @20 ], - @[ @40, @40, @33, @60, @60, @60, @65, @40 ], - ]; - - NSString *value; - value = RCTInterpolateString(pattern, 0, input, output, EXTRAPOLATE_TYPE_IDENTITY, EXTRAPOLATE_TYPE_IDENTITY); - XCTAssertEqualObjects(value, @"M20,20L20,80L80,80L80,20Z"); - value = RCTInterpolateString(pattern, 0.5, input, output, EXTRAPOLATE_TYPE_IDENTITY, EXTRAPOLATE_TYPE_IDENTITY); - XCTAssertEqualObjects(value, @"M30,30L26.5,70L70,70L72.5,30Z"); -} - @end