RCTInterpolationAnimatedNode.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTInterpolationAnimatedNode.h>
  8. #import <React/RCTAnimationUtils.h>
  9. static NSRegularExpression *regex;
  10. @implementation RCTInterpolationAnimatedNode
  11. {
  12. __weak RCTValueAnimatedNode *_parentNode;
  13. NSArray<NSNumber *> *_inputRange;
  14. NSArray<NSNumber *> *_outputRange;
  15. NSArray<NSArray<NSNumber *> *> *_outputs;
  16. NSArray<NSString *> *_soutputRange;
  17. NSString *_extrapolateLeft;
  18. NSString *_extrapolateRight;
  19. NSUInteger _numVals;
  20. bool _hasStringOutput;
  21. bool _shouldRound;
  22. NSArray<NSTextCheckingResult*> *_matches;
  23. }
  24. - (instancetype)initWithTag:(NSNumber *)tag
  25. config:(NSDictionary<NSString *, id> *)config
  26. {
  27. static dispatch_once_t onceToken;
  28. dispatch_once(&onceToken, ^{
  29. NSString *fpRegex = @"[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?";
  30. regex = [NSRegularExpression regularExpressionWithPattern:fpRegex options:NSRegularExpressionCaseInsensitive error:nil];
  31. });
  32. if ((self = [super initWithTag:tag config:config])) {
  33. _inputRange = [config[@"inputRange"] copy];
  34. NSMutableArray *outputRange = [NSMutableArray array];
  35. NSMutableArray *soutputRange = [NSMutableArray array];
  36. NSMutableArray<NSMutableArray<NSNumber *> *> *_outputRanges = [NSMutableArray array];
  37. _hasStringOutput = NO;
  38. for (id value in config[@"outputRange"]) {
  39. if ([value isKindOfClass:[NSNumber class]]) {
  40. [outputRange addObject:value];
  41. } else if ([value isKindOfClass:[NSString class]]) {
  42. /**
  43. * Supports string shapes by extracting numbers so new values can be computed,
  44. * and recombines those values into new strings of the same shape. Supports
  45. * things like:
  46. *
  47. * rgba(123, 42, 99, 0.36) // colors
  48. * -45deg // values with units
  49. */
  50. NSMutableArray *output = [NSMutableArray array];
  51. [_outputRanges addObject:output];
  52. [soutputRange addObject:value];
  53. _matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
  54. for (NSTextCheckingResult *match in _matches) {
  55. NSString* strNumber = [value substringWithRange:match.range];
  56. [output addObject:[NSNumber numberWithDouble:strNumber.doubleValue]];
  57. }
  58. _hasStringOutput = YES;
  59. [outputRange addObject:[output objectAtIndex:0]];
  60. }
  61. }
  62. if (_hasStringOutput) {
  63. // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
  64. // ->
  65. // [
  66. // [0, 50],
  67. // [100, 150],
  68. // [200, 250],
  69. // [0, 0.5],
  70. // ]
  71. _numVals = [_matches count];
  72. NSString *value = [soutputRange objectAtIndex:0];
  73. _shouldRound = [value containsString:@"rgb"];
  74. _matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
  75. NSMutableArray<NSMutableArray<NSNumber *> *> *outputs = [NSMutableArray arrayWithCapacity:_numVals];
  76. NSUInteger size = [soutputRange count];
  77. for (NSUInteger j = 0; j < _numVals; j++) {
  78. NSMutableArray *output = [NSMutableArray arrayWithCapacity:size];
  79. [outputs addObject:output];
  80. for (int i = 0; i < size; i++) {
  81. [output addObject:[[_outputRanges objectAtIndex:i] objectAtIndex:j]];
  82. }
  83. }
  84. _outputs = [outputs copy];
  85. }
  86. _outputRange = [outputRange copy];
  87. _soutputRange = [soutputRange copy];
  88. _extrapolateLeft = config[@"extrapolateLeft"];
  89. _extrapolateRight = config[@"extrapolateRight"];
  90. }
  91. return self;
  92. }
  93. - (void)onAttachedToNode:(RCTAnimatedNode *)parent
  94. {
  95. [super onAttachedToNode:parent];
  96. if ([parent isKindOfClass:[RCTValueAnimatedNode class]]) {
  97. _parentNode = (RCTValueAnimatedNode *)parent;
  98. }
  99. }
  100. - (void)onDetachedFromNode:(RCTAnimatedNode *)parent
  101. {
  102. [super onDetachedFromNode:parent];
  103. if (_parentNode == parent) {
  104. _parentNode = nil;
  105. }
  106. }
  107. - (void)performUpdate
  108. {
  109. [super performUpdate];
  110. if (!_parentNode) {
  111. return;
  112. }
  113. CGFloat inputValue = _parentNode.value;
  114. CGFloat interpolated = RCTInterpolateValueInRange(inputValue,
  115. _inputRange,
  116. _outputRange,
  117. _extrapolateLeft,
  118. _extrapolateRight);
  119. self.value = interpolated;
  120. if (_hasStringOutput) {
  121. // 'rgba(0, 100, 200, 0)'
  122. // ->
  123. // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
  124. if (_numVals > 1) {
  125. NSString *text = _soutputRange[0];
  126. NSMutableString *formattedText = [NSMutableString stringWithString:text];
  127. NSUInteger i = _numVals;
  128. for (NSTextCheckingResult *match in [_matches reverseObjectEnumerator]) {
  129. CGFloat val = RCTInterpolateValueInRange(inputValue,
  130. _inputRange,
  131. _outputs[--i],
  132. _extrapolateLeft,
  133. _extrapolateRight);
  134. NSString *str;
  135. if (_shouldRound) {
  136. // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
  137. // round the opacity (4th column).
  138. bool isAlpha = i == 3;
  139. CGFloat rounded = isAlpha ? round(val * 1000) / 1000 : round(val);
  140. str = isAlpha ? [NSString stringWithFormat:@"%1.3f", rounded] : [NSString stringWithFormat:@"%1.0f", rounded];
  141. } else {
  142. NSNumber *numberValue = [NSNumber numberWithDouble:val];
  143. str = [numberValue stringValue];
  144. }
  145. [formattedText replaceCharactersInRange:[match range] withString:str];
  146. }
  147. self.animatedObject = formattedText;
  148. } else {
  149. self.animatedObject = [regex stringByReplacingMatchesInString:_soutputRange[0]
  150. options:0
  151. range:NSMakeRange(0, _soutputRange[0].length)
  152. withTemplate:[NSString stringWithFormat:@"%1f", interpolated]];
  153. }
  154. }
  155. }
  156. @end