diff --git a/packages/normalize-color/__tests__/normalizeColor-test.js b/packages/normalize-color/__tests__/normalizeColor-test.js index 56d47df5e15977..94ee1731583c74 100644 --- a/packages/normalize-color/__tests__/normalizeColor-test.js +++ b/packages/normalize-color/__tests__/normalizeColor-test.js @@ -150,6 +150,37 @@ it('handles number colors properly', () => { expect(normalizeColor(0x01234567)).toBe(0x01234567); }); +it('handles color function properly', () => { + expect(normalizeColor('color(display-p3 1 0 0)')).toEqual({ + space: 'display-p3', + r: 1, + g: 0, + b: 0, + a: 1, + }); + expect(normalizeColor('color(display-p3 1 0 0 / 0.5)')).toEqual({ + space: 'display-p3', + r: 1, + g: 0, + b: 0, + a: 0.5, + }); + expect(normalizeColor('color(srgb 1 0 0)')).toEqual({ + space: 'srgb', + r: 1, + g: 0, + b: 0, + a: 1, + }); + expect(normalizeColor('color(srgb 1 0 0 / 0.5)')).toEqual({ + space: 'srgb', + r: 1, + g: 0, + b: 0, + a: 0.5, + }); +}); + it('returns the same color when it is already normalized', () => { const normalizedColor = normalizeColor('red') || 0; expect(normalizeColor(normalizedColor)).toBe(normalizedColor); diff --git a/packages/normalize-color/index.js b/packages/normalize-color/index.js index 611baaffc13891..402efced8e7462 100644 --- a/packages/normalize-color/index.js +++ b/packages/normalize-color/index.js @@ -155,6 +155,24 @@ function normalizeColor(color) { ); } + if ((match = matchers.color.exec(color))) { + return match[2] + ? { + space: match[2], + r: parseFloat(match[3]), + g: parseFloat(match[4]), + b: parseFloat(match[5]), + a: 1, + } + : { + space: match[6], + r: parseFloat(match[7]), + g: parseFloat(match[8]), + b: parseFloat(match[9]), + a: parseFloat(match[10]), + }; + } + return null; } @@ -209,6 +227,7 @@ function hwbToRgb(h, w, b) { ); } +const COLOR_SPACE = 'display-p3|srgb'; const NUMBER = '[-+]?\\d*\\.?\\d+'; const PERCENTAGE = NUMBER + '%'; @@ -235,6 +254,13 @@ let cachedMatchers; function getMatchers() { if (cachedMatchers === undefined) { cachedMatchers = { + color: new RegExp( + 'color(' + + call(COLOR_SPACE, NUMBER, NUMBER, NUMBER) + + '|' + + callWithSlashSeparator(COLOR_SPACE, NUMBER, NUMBER, NUMBER, NUMBER) + + ')', + ), rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)), rgba: new RegExp( 'rgba(' + diff --git a/packages/normalize-color/index.js.flow b/packages/normalize-color/index.js.flow index 4d24656d835e4f..66bad6e42f067a 100644 --- a/packages/normalize-color/index.js.flow +++ b/packages/normalize-color/index.js.flow @@ -7,4 +7,18 @@ * @flow strict */ -declare module.exports: (color: ?(string | number)) => null | number; +enum ColorSpace { + SRGB = 'srgb', + DisplayP3 = 'display-p3', +} + +export type RgbaValue = { + +space?: ColorSpace, + +r: number, + +g: number, + +b: number, + +a: number, + ... +}; + +declare module.exports: (color: ?(string | number)) => null | number | RgbaValue; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js b/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js index ebf986093b1dfd..a3852868694c32 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js @@ -109,6 +109,7 @@ function isRgbaAnimatedValue(value: any): boolean { } export default class AnimatedColor extends AnimatedWithChildren { + space: ?string; r: AnimatedValue; g: AnimatedValue; b: AnimatedValue; @@ -142,6 +143,7 @@ export default class AnimatedColor extends AnimatedWithChildren { this.nativeColor = (processedColor: NativeColorValue); } + this.space = initColor.space; this.r = new AnimatedValue(initColor.r); this.g = new AnimatedValue(initColor.g); this.b = new AnimatedValue(initColor.b); @@ -266,6 +268,10 @@ export default class AnimatedColor extends AnimatedWithChildren { __getValue(): ColorValue { if (this.nativeColor != null) { return this.nativeColor; + } else if (this.space) { + return `color(${ + this.space + } ${this.r.__getValue()} ${this.g.__getValue()} ${this.b.__getValue()} / ${this.a.__getValue()})`; } else { return `rgba(${this.r.__getValue()}, ${this.g.__getValue()}, ${this.b.__getValue()}, ${this.a.__getValue()})`; } @@ -310,6 +316,7 @@ export default class AnimatedColor extends AnimatedWithChildren { __getNativeConfig(): {...} { return { type: 'color', + space: this.space, r: this.r.__getNativeTag(), g: this.g.__getNativeTag(), b: this.b.__getNativeTag(), diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js index 8f504c5388faae..994456c30eb8b7 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js @@ -157,7 +157,9 @@ function mapStringToNumericComponents( | {isColor: false, components: $ReadOnlyArray} { let normalizedColor = normalizeColor(input); invariant( - normalizedColor == null || typeof normalizedColor !== 'object', + normalizedColor == null || + typeof normalizedColor !== 'object' || + normalizedColor.hasOwnProperty('space'), 'PlatformColors are not supported', ); @@ -395,6 +397,13 @@ export default class AnimatedInterpolation< if (typeof processedColor === 'number') { outputType = 'color'; return processedColor; + } else if (processedColor?.hasOwnProperty('space')) { + const {r, g, b, a} = processedColor; + const reprocessedColor = processColor( + `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${a})`, + ); + outputType = 'color'; + return reprocessedColor; } else { return NativeAnimatedHelper.transformDataType(value); } diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.h b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.h index ae1d132573445a..5652f626498e45 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.h +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.h @@ -9,6 +9,7 @@ @interface RCTColorAnimatedNode : RCTAnimatedNode -@property (nonatomic, assign) int32_t color; +// @property (nonatomic, strong) UIColor *color; +@property (nonatomic, strong) NSDictionary *color; @end diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.mm index d35d6388651f3b..bd5f1d7b9d9c7a 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTColorAnimatedNode.mm @@ -9,6 +9,7 @@ #import #import +#import @implementation RCTColorAnimatedNode @@ -16,12 +17,22 @@ - (void)performUpdate { [super performUpdate]; + CGFloat divisor = self.config[@"space"] ? 1.0 : 255.0; + RCTColorSpace space = [RCTConvert colorSpaceFromString:self.config[@"space"]]; RCTValueAnimatedNode *rNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"r"]]; RCTValueAnimatedNode *gNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"g"]]; 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); + +// TODO: why can't we just use UIColor here? +// _color = [RCTConvert createColorFrom:rNode.value green:gNode.value blue:bNode.value alpha:aNode.value andColorSpace:space]; + _color = @{ + @"space": space == RCTColorSpaceDisplayP3 ? @"display-p3" : @"srgb", + @"r": @(rNode.value / divisor), + @"g": @(gNode.value / divisor), + @"b": @(bNode.value / divisor), + @"a": @(aNode.value), + }; // TODO (T111179606): Support platform colors for color animations } diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm index 78ee190dee0cd4..19188d1561a0d0 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm @@ -150,7 +150,7 @@ - (void)performUpdate CGFloat inputValue = _parentNode.value; switch (_outputType) { case RCTInterpolationOutputColor: - _outputvalue = @(RCTInterpolateColorInRange(inputValue, _inputRange, _outputRange)); + _outputvalue = RCTInterpolateColorInRange(inputValue, _inputRange, _outputRange); break; case RCTInterpolationOutputString: _outputvalue = RCTInterpolateString( diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.mm index 2d8c6494c16139..e2d7cfd0611a6e 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.mm @@ -130,7 +130,7 @@ - (void)performUpdate } else if ([parentNode isKindOfClass:[RCTColorAnimatedNode class]]) { RCTColorAnimatedNode *colorAnimatedNode = (RCTColorAnimatedNode *)parentNode; NSString *property = [self propertyNameForParentTag:parentTag]; - _propsDictionary[property] = @(colorAnimatedNode.color); + _propsDictionary[property] = colorAnimatedNode.color; } else if ([parentNode isKindOfClass:[RCTObjectAnimatedNode class]]) { RCTObjectAnimatedNode *objectAnimatedNode = (RCTObjectAnimatedNode *)parentNode; NSString *property = [self propertyNameForParentTag:parentTag]; diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm index 7673db6323e41a..89c66a51fcdd4d 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm @@ -50,7 +50,7 @@ - (void)performUpdate [_propsDictionary addEntriesFromDictionary:transformAnimatedNode.propsDictionary]; } else if ([node isKindOfClass:[RCTColorAnimatedNode class]]) { RCTColorAnimatedNode *colorAnimatedNode = (RCTColorAnimatedNode *)node; - _propsDictionary[property] = @(colorAnimatedNode.color); + _propsDictionary[property] = colorAnimatedNode.color; } else if ([node isKindOfClass:[RCTObjectAnimatedNode class]]) { RCTObjectAnimatedNode *objectAnimatedNode = (RCTObjectAnimatedNode *)node; _propsDictionary[property] = objectAnimatedNode.value; diff --git a/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.h b/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.h index 77152f4a5773de..0edfd86a349720 100644 --- a/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.h +++ b/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.h @@ -32,7 +32,8 @@ RCT_EXTERN CGFloat RCTInterpolateValueInRange( NSString *extrapolateLeft, NSString *extrapolateRight); -RCT_EXTERN uint32_t +// RCT_EXTERN UIColor* +RCT_EXTERN NSDictionary* 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 diff --git a/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.mm b/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.mm index 90cfe304d6f68a..ed8eb0d110079d 100644 --- a/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.mm +++ b/packages/react-native/Libraries/NativeAnimation/RCTAnimationUtils.mm @@ -84,7 +84,8 @@ CGFloat RCTInterpolateValueInRange( return RCTInterpolateValue(value, inputMin, inputMax, outputMin, outputMax, extrapolateLeft, extrapolateRight); } -uint32_t RCTInterpolateColorInRange(CGFloat value, NSArray *inputRange, NSArray *outputRange) +//UIColor* RCTInterpolateColorInRange(CGFloat value, NSArray *inputRange, NSArray *outputRange) +NSDictionary* RCTInterpolateColorInRange(CGFloat value, NSArray *inputRange, NSArray *outputRange) { NSUInteger rangeIndex = RCTFindIndexOfNearestValue(value, inputRange); CGFloat inputMin = inputRange[rangeIndex].doubleValue; @@ -94,12 +95,20 @@ uint32_t RCTInterpolateColorInRange(CGFloat value, NSArray *inputRan [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)); + + CGFloat r = redMin + (value - inputMin) * (redMax - redMin) / (inputMax - inputMin); + CGFloat g = greenMin + (value - inputMin) * (greenMax - greenMin) / (inputMax - inputMin); + CGFloat b = blueMin + (value - inputMin) * (blueMax - blueMin) / (inputMax - inputMin); + CGFloat a = alphaMin + (value - inputMin) * (alphaMax - alphaMin) / (inputMax - inputMin); + +// return [UIColor colorWithRed:r green:g blue:b alpha:a]; + return @{ + @"space": @"srgb", + @"r":@(r), + @"g":@(g), + @"b":@(b), + @"a":@(a), + }; } uint32_t RCTColorFromComponents(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/processColor-test.js b/packages/react-native/Libraries/StyleSheet/__tests__/processColor-test.js index 8761fc0d156505..dd135c02f11375 100644 --- a/packages/react-native/Libraries/StyleSheet/__tests__/processColor-test.js +++ b/packages/react-native/Libraries/StyleSheet/__tests__/processColor-test.js @@ -51,6 +51,24 @@ describe('processColor', () => { }); }); + describe('color() strings', () => { + it('should convert color(s r g b)', () => { + const colorFromString = processColor('color(srgb 1 0 0)'); + expect(colorFromString).toEqual({space: 'srgb', r: 1, g: 0, b: 0, a: 1}); + }); + + it('should convert color(s r g b / a)', () => { + const colorFromString = processColor('color(display-p3 1 0 0 / 0.5)'); + expect(colorFromString).toEqual({ + space: 'display-p3', + r: 1, + g: 0, + b: 0, + a: 0.5, + }); + }); + }); + describe('RGB strings', () => { it('should convert rgb(x, y, z)', () => { const colorFromString = processColor('rgb(10, 20, 30)'); diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/processColorArray-test.js b/packages/react-native/Libraries/StyleSheet/__tests__/processColorArray-test.js index 0385dd7db757da..2c1d8f31bf8f7e 100644 --- a/packages/react-native/Libraries/StyleSheet/__tests__/processColorArray-test.js +++ b/packages/react-native/Libraries/StyleSheet/__tests__/processColorArray-test.js @@ -34,6 +34,19 @@ describe('processColorArray', () => { expect(colorFromStringArray).toEqual(expectedIntArray); }); + it('should convert array of color type color(display-p3 x y z)', () => { + const colorFromDisplayP3Array = processColorArray([ + 'color(display-p3 0.1 0.2 0.3)', + 'color(display-p3 0.2 0.3 0.4)', + 'color(display-p3 0.3 0.4 0.5)', + ]); + expect(colorFromDisplayP3Array).toEqual([ + {space: 'display-p3', r: 0.1, g: 0.2, b: 0.3, a: 1}, + {space: 'display-p3', r: 0.2, g: 0.3, b: 0.4, a: 1}, + {space: 'display-p3', r: 0.3, g: 0.4, b: 0.5, a: 1}, + ]); + }); + it('should convert array of color type rgb(x, y, z)', () => { const colorFromRGBArray = processColorArray([ 'rgb(10, 20, 30)', diff --git a/packages/react-native/React/Base/RCTConvert.h b/packages/react-native/React/Base/RCTConvert.h index 2d55e7c99acf12..b4c310da347c93 100644 --- a/packages/react-native/React/Base/RCTConvert.h +++ b/packages/react-native/React/Base/RCTConvert.h @@ -17,6 +17,16 @@ #import #import +typedef NS_ENUM(NSInteger, RCTColorSpace) { + RCTColorSpaceSRGB, + RCTColorSpaceDisplayP3, +}; + +// Change the default color space +RCTColorSpace RCTGetDefaultColorSpace(void); +RCT_EXTERN void RCTSetDefaultColorSpace(RCTColorSpace colorSpace); + + /** * This class provides a collection of conversion functions for mapping * JSON objects to native types and classes. These are useful when writing @@ -91,6 +101,13 @@ typedef NSURL RCTFileURL; + (CGAffineTransform)CGAffineTransform:(id)json; ++ (UIColor *)createColorFrom:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; ++ (UIColor *)createColorFrom:(CGFloat)red + green:(CGFloat)green + blue:(CGFloat)blue + alpha:(CGFloat)alpha + andColorSpace:(RCTColorSpace)colorSpace; ++ (RCTColorSpace)colorSpaceFromString:(NSString *)colorSpace; + (UIColor *)UIColor:(id)json; + (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED; diff --git a/packages/react-native/React/Base/RCTConvert.m b/packages/react-native/React/Base/RCTConvert.mm similarity index 95% rename from packages/react-native/React/Base/RCTConvert.m rename to packages/react-native/React/Base/RCTConvert.mm index 55f42ebf49e9e3..010eb0f2a8ad38 100644 --- a/packages/react-native/React/Base/RCTConvert.m +++ b/packages/react-native/React/Base/RCTConvert.mm @@ -16,6 +16,8 @@ #import "RCTParserUtils.h" #import "RCTUtils.h" +#import + @implementation RCTConvert RCT_CONVERTER(id, id, self) @@ -439,7 +441,7 @@ + (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC mapping = temporaryMapping; }); - UIKeyboardType type = RCTConvertEnumValue("UIKeyboardType", mapping, @(UIKeyboardTypeDefault), json).integerValue; + UIKeyboardType type = (UIKeyboardType)RCTConvertEnumValue("UIKeyboardType", mapping, @(UIKeyboardTypeDefault), json).integerValue; return type; } @@ -844,7 +846,7 @@ + (UIEdgeInsets)UIEdgeInsets:(id)json RCTAssert([UIColor respondsToSelector:selector], @"RCTUIColor does not respond to a semantic color selector."); Class klass = [UIColor class]; IMP imp = [klass methodForSelector:selector]; - id (*getSemanticColorObject)(id, SEL) = (void *)imp; + id (*getSemanticColorObject)(id, SEL) = (id (*)(id, SEL))imp; id colorObject = getSemanticColorObject(klass, selector); if ([colorObject isKindOfClass:[UIColor class]]) { color = colorObject; @@ -878,6 +880,38 @@ + (UIEdgeInsets)UIEdgeInsets:(id)json return names; } +static RCTColorSpace defaultColorSpace = (RCTColorSpace)facebook::react::getDefaultColorSpace(); +RCTColorSpace RCTGetDefaultColorSpace(void) +{ + return (RCTColorSpace)facebook::react::getDefaultColorSpace(); +} +void RCTSetDefaultColorSpace(RCTColorSpace colorSpace) +{ + facebook::react::setDefaultColorSpace((facebook::react::ColorSpace)colorSpace); +} + ++ (UIColor *)createColorFrom:(CGFloat)r green:(CGFloat)g blue:(CGFloat)b alpha:(CGFloat)a +{ + RCTColorSpace space = RCTGetDefaultColorSpace(); + return [self createColorFrom:r green:g blue:b alpha:a andColorSpace:space]; +} ++ (UIColor *)createColorFrom:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha andColorSpace:(RCTColorSpace)colorSpace +{ + if (colorSpace == RCTColorSpaceDisplayP3) { + return [UIColor colorWithDisplayP3Red:red green:green blue:blue alpha:alpha]; + } + return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; +} + ++ (RCTColorSpace)colorSpaceFromString:(NSString *)colorSpace { + if ([colorSpace isEqualToString:@"display-p3"]) { + return RCTColorSpaceDisplayP3; + } else if ([colorSpace isEqualToString:@"srgb"]) { + return RCTColorSpaceSRGB; + } + return RCTGetDefaultColorSpace(); +} + + (UIColor *)UIColor:(id)json { if (!json) { @@ -886,7 +920,7 @@ + (UIColor *)UIColor:(id)json if ([json isKindOfClass:[NSArray class]]) { NSArray *components = [self NSNumberArray:json]; CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0; - return [UIColor colorWithRed:[self CGFloat:components[0]] + return [self createColorFrom:[self CGFloat:components[0]] green:[self CGFloat:components[1]] blue:[self CGFloat:components[2]] alpha:alpha]; @@ -896,11 +930,19 @@ + (UIColor *)UIColor:(id)json CGFloat r = ((argb >> 16) & 0xFF) / 255.0; CGFloat g = ((argb >> 8) & 0xFF) / 255.0; CGFloat b = (argb & 0xFF) / 255.0; - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + return [self createColorFrom:r green:g blue:b alpha:a]; } else if ([json isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = json; id value = nil; - if ((value = [dictionary objectForKey:@"semantic"])) { + NSString *rawColorSpace = [dictionary objectForKey: @"space"]; + if ([@[@"display-p3", @"srgb"] containsObject:rawColorSpace]) { + CGFloat r = [[dictionary objectForKey:@"r"] floatValue]; + CGFloat g = [[dictionary objectForKey:@"g"] floatValue]; + CGFloat b = [[dictionary objectForKey:@"b"] floatValue]; + CGFloat a = [[dictionary objectForKey:@"a"] floatValue]; + RCTColorSpace colorSpace = [self colorSpaceFromString: rawColorSpace]; + return [self createColorFrom:r green:g blue:b alpha:a andColorSpace:colorSpace]; + } else if ((value = [dictionary objectForKey:@"semantic"])) { if ([value isKindOfClass:[NSString class]]) { NSString *semanticName = value; UIColor *color = [UIColor colorNamed:semanticName]; diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index d8ed955da44bc6..2edb3223b87ab6 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +#import #import #import @@ -53,7 +54,11 @@ inline UIColor *_Nullable RCTUIColorFromSharedColor(const facebook::react::Share } auto components = facebook::react::colorComponentsFromColor(sharedColor); - return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + return [RCTConvert createColorFrom:components.red + green:components.green + blue:components.blue + alpha:components.alpha + andColorSpace:(RCTColorSpace)components.colorSpace]; } inline CF_RETURNS_RETAINED CGColorRef _Nullable RCTCreateCGColorRefFromSharedColor( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index d9346e6b9ad85b..e99c38fa4e5d1e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -8,9 +8,11 @@ package com.facebook.react; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.Bundle; import android.view.KeyEvent; +import android.view.Window; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; @@ -44,6 +46,8 @@ protected ReactActivityDelegate createReactActivityDelegate() { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // TODO: this should be opt-in behavior + getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT); mDelegate.onCreate(savedInstanceState); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java index 7c55feb8f32095..2167b09068f484 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java @@ -21,6 +21,7 @@ private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; private final ReactApplicationContext mReactApplicationContext; + private String mSpace; private int mRNodeId; private int mGNodeId; private int mBNodeId; @@ -45,15 +46,18 @@ public int getColor() { ValueAnimatedNode bNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mBNodeId); ValueAnimatedNode aNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mANodeId); - double r = rNode.getValue(); - double g = gNode.getValue(); - double b = bNode.getValue(); + int multiplier = mSpace != null ? 255 : 1; + + double r = rNode.getValue() * multiplier; + double g = gNode.getValue() * multiplier; + double b = bNode.getValue() * multiplier; double a = aNode.getValue(); return ColorUtil.normalize(r, g, b, a); } public void onUpdateConfig(ReadableMap config) { + mSpace = config.getString("space"); mRNodeId = config.getInt("r"); mGNodeId = config.getInt("g"); mBNodeId = config.getInt("b"); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java index afc690dedaabbf..22dbe9b95e4cae 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java @@ -9,7 +9,10 @@ import android.content.Context; import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.ColorSpace; import android.util.TypedValue; +import androidx.annotation.ColorLong; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; import com.facebook.common.logging.FLog; @@ -24,13 +27,13 @@ public class ColorPropConverter { private static final String ATTR = "attr"; private static final String ATTR_SEGMENT = "attr/"; - public static Integer getColor(Object value, Context context) { + public static Color getColorInstance(Object value, Context context) { if (value == null) { return null; } if (value instanceof Double) { - return ((Double) value).intValue(); + return Color.valueOf(((Double) value).intValue()); } if (context == null) { @@ -39,8 +42,23 @@ public static Integer getColor(Object value, Context context) { if (value instanceof ReadableMap) { ReadableMap map = (ReadableMap) value; - ReadableArray resourcePaths = map.getArray(JSON_KEY); + // handle color(space r g b a) value + if (map.hasKey("space")) { + String rawColorSpace = map.getString("space"); + boolean isDisplayP3 = rawColorSpace.equals("display-p3"); + ColorSpace space = ColorSpace.get(isDisplayP3 ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB); + float r = (float) map.getDouble("r"); + float g = (float) map.getDouble("g"); + float b = (float) map.getDouble("b"); + float a = (float) map.getDouble("a"); + + @ColorLong + long color = Color.pack(r, g, b, a, space); + return Color.valueOf(color); + } + + ReadableArray resourcePaths = map.getArray(JSON_KEY); if (resourcePaths == null) { throw new JSApplicationCausedNativeException( "ColorValue: The `" + JSON_KEY + "` must be an array of color resource path strings."); @@ -49,7 +67,7 @@ public static Integer getColor(Object value, Context context) { for (int i = 0; i < resourcePaths.size(); i++) { Integer result = resolveResourcePath(context, resourcePaths.getString(i)); if (result != null) { - return result; + return Color.valueOf(result); } } @@ -63,6 +81,14 @@ public static Integer getColor(Object value, Context context) { "ColorValue: the value must be a number or Object."); } + public static Integer getColor(Object value, Context context) { + Color color = getColorInstance(value, context); + if (color == null) { + return null; + } + return color.toArgb(); + } + public static Integer getColor(Object value, Context context, int defaultInt) { try { return getColor(value, context); @@ -89,8 +115,9 @@ public static Integer resolveResourcePath(Context context, @Nullable String reso return resolveThemeAttribute(context, resourcePath); } } catch (Resources.NotFoundException exception) { - // The resource could not be found so do nothing to allow the for loop to continue and - // try the next fallback resource in the array. If none of the fallbacks are + // The resource could not be found so do nothing to allow the for loop to + // continue and + // try the next fallback resource in the array. If none of the fallbacks are // found then the exception immediately after the for loop will be thrown. } return null; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.java index 6e336ba1a88993..213908ce2a687e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.java @@ -39,7 +39,7 @@ public static ReadableMap fromJSONObject(JSONObject obj) throws JSONException { } else if (val instanceof Double) { result.putDouble(key, (Double) val); } else if (val instanceof Long) { - result.putInt(key, ((Long) val).intValue()); + result.putLong(key, (Long) val); } else if (obj.isNull(key)) { result.putNull(key); } else { @@ -86,7 +86,7 @@ public static ReadableArray fromJSONArray(JSONArray arr) throws JSONException { } else if (val instanceof Double) { result.pushDouble((Double) val); } else if (val instanceof Long) { - result.pushInt(((Long) val).intValue()); + result.pushLong((Long) val); } else if (arr.isNull(i)) { result.pushNull(); } else { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java index f81766a4c0243d..5a1ed1d7a92ec3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java @@ -96,6 +96,11 @@ public int getInt(int index) { return ((Number) mBackingList.get(index)).intValue(); } + @Override + public long getLong(int index) { + return ((Number) mBackingList.get(index)).longValue(); + } + @Override public @Nullable String getString(int index) { return (String) mBackingList.get(index); @@ -129,7 +134,7 @@ public ReadableMap getMap(int index) { return ReadableType.Null; } else if (object instanceof Boolean) { return ReadableType.Boolean; - } else if (object instanceof Double || object instanceof Float || object instanceof Integer) { + } else if (object instanceof Double || object instanceof Float || object instanceof Integer || object instanceof Long) { return ReadableType.Number; } else if (object instanceof String) { return ReadableType.String; @@ -156,6 +161,11 @@ public void pushInt(int value) { mBackingList.add(new Double(value)); } + @Override + public void pushLong(long value) { + mBackingList.add(new Double(value)); + } + @Override public void pushString(@Nullable String value) { mBackingList.add(value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java index ccce30a7d60e36..e72bdfff421170 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java @@ -110,6 +110,11 @@ public int getInt(@NonNull String name) { return ((Number) mBackingMap.get(name)).intValue(); } + @Override + public long getLong(@NonNull String name) { + return ((Number) mBackingMap.get(name)).longValue(); + } + @Override public String getString(@NonNull String name) { return (String) mBackingMap.get(name); @@ -190,6 +195,11 @@ public void putInt(@NonNull String key, int value) { mBackingMap.put(key, new Double(value)); } + @Override + public void putLong(@NonNull String key, long value) { + mBackingMap.put(key, new Double(value)); + } + @Override public void putString(@NonNull String key, @Nullable String value) { mBackingMap.put(key, value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java index 26a88911b30cae..9a5e84aaeae848 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java @@ -26,6 +26,8 @@ public interface ReadableArray { int getInt(int index); + long getLong(int index); + @NonNull String getString(int index); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java index a3bae65f7224c4..3dcc141a8cc232 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java @@ -29,6 +29,8 @@ public interface ReadableMap { int getInt(@NonNull String name); + long getLong(@NonNull String name); + @Nullable String getString(@NonNull String name); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java index 1a117a6b46928c..19543b173b3f5d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java @@ -97,6 +97,11 @@ public int getInt(int index) { return ((Double) getLocalArray()[index]).intValue(); } + @Override + public long getLong(int index) { + return ((Double) getLocalArray()[index]).longValue(); + } + @Override public @NonNull String getString(int index) { return (String) getLocalArray()[index]; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java index 0ade85c10090f1..4519c8b33e0902 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java @@ -160,6 +160,12 @@ public int getInt(@NonNull String name) { return getValue(name, Double.class).intValue(); } + @Override + public long getLong(@NonNull String name) { + // All numbers coming out of native are doubles, so cast here then truncate + return getValue(name, Double.class).longValue(); + } + @Override public @Nullable String getString(@NonNull String name) { return getNullableValue(name, String.class); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableArray.java index 8ec699c3970ed9..0b7cb17f69487c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableArray.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableArray.java @@ -20,6 +20,8 @@ public interface WritableArray extends ReadableArray { void pushInt(int value); + void pushLong(long value); + void pushString(@Nullable String value); void pushArray(@Nullable ReadableArray array); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableMap.java index 1f6ec6500970f1..a7ef7818f51bb5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableMap.java @@ -21,6 +21,8 @@ public interface WritableMap extends ReadableMap { void putInt(@NonNull String key, int value); + void putLong(@NonNull String key, long value); + void putString(@NonNull String key, @Nullable String value); void putArray(@NonNull String key, @Nullable ReadableArray value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java index 46a2c8d9e67e34..06ad5650e11c45 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeArray.java @@ -38,6 +38,9 @@ public WritableNativeArray() { @Override public native void pushInt(int value); + @Override + public native void pushLong(long value); + @Override public native void pushString(@Nullable String value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java index 809cc9127f555e..228b3065de7a39 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java @@ -32,6 +32,9 @@ public class WritableNativeMap extends ReadableNativeMap implements WritableMap @Override public native void putInt(@NonNull String key, int value); + @Override + public native void putLong(@NonNull String key, long value); + @Override public native void putNull(@NonNull String key); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt index 4530302021c1dc..f5bb2bcad2b4dc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt @@ -44,7 +44,8 @@ interface MapBuffer : Iterable { INT, DOUBLE, STRING, - MAP + MAP, + LONG } /** @@ -109,6 +110,16 @@ interface MapBuffer : Iterable { */ fun getInt(key: Int): Int + /** + * Provides parsed [Long] value if the entry for given key exists with [DataType.LONG] type + * + * @param key key to lookup [Long] value for + * @return value associated with the requested key + * @throws IllegalArgumentException if the key doesn't exist + * @throws IllegalStateException if the data type doesn't match + */ + fun getLong(key: Int): Long + /** * Provides parsed [Double] value if the entry for given key exists with [DataType.DOUBLE] type * @@ -175,6 +186,13 @@ interface MapBuffer : Iterable { */ val intValue: Int + /** + * Entry value represented as [Long] + * + * @throws IllegalStateException if the data type doesn't match [DataType.LONG] + */ + val longValue: Long + /** * Entry value represented as [Double] * diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt index b70fe1d2384112..f97c14d6c27516 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt @@ -115,6 +115,10 @@ class ReadableMapBuffer : MapBuffer { return buffer.getInt(bufferPosition) } + private fun readLongValue(bufferPosition: Int): Long { + return buffer.getLong(bufferPosition) + } + private fun readBooleanValue(bufferPosition: Int): Boolean { return readIntValue(bufferPosition) == 1 } @@ -180,6 +184,9 @@ class ReadableMapBuffer : MapBuffer { override fun getInt(key: Int): Int = readIntValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT)) + override fun getLong(key: Int): Long = + readLongValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.LONG)) + override fun getDouble(key: Int): Double = readDoubleValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.DOUBLE)) @@ -223,6 +230,7 @@ class ReadableMapBuffer : MapBuffer { when (entry.type) { MapBuffer.DataType.BOOL -> builder.append(entry.booleanValue) MapBuffer.DataType.INT -> builder.append(entry.intValue) + MapBuffer.DataType.LONG -> builder.append(entry.longValue) MapBuffer.DataType.DOUBLE -> builder.append(entry.doubleValue) MapBuffer.DataType.STRING -> builder.append(entry.stringValue) MapBuffer.DataType.MAP -> builder.append(entry.mapBufferValue.toString()) @@ -251,14 +259,16 @@ class ReadableMapBuffer : MapBuffer { private inner class MapBufferEntry(private val bucketOffset: Int) : MapBuffer.Entry { private fun assertType(expected: MapBuffer.DataType) { val dataType = type + val stackTrace = Throwable().stackTrace + val trace = stackTrace.map { "${it.className}.${it.methodName}\n" } check(!(expected !== dataType)) { - ("Expected " + + ("assertType: Expected " + expected + " for key: " + key + " found " + dataType.toString() + - " instead.") + " instead. Trace:\n" + trace) } } @@ -280,6 +290,12 @@ class ReadableMapBuffer : MapBuffer { return readIntValue(bucketOffset + VALUE_OFFSET) } + override val longValue: Long + get() { + assertType(MapBuffer.DataType.LONG) + return readLongValue(bucketOffset + VALUE_OFFSET) + } + override val booleanValue: Boolean get() { assertType(MapBuffer.DataType.BOOL) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt index b687f7a77bc975..ec835690178a87 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt @@ -48,6 +48,15 @@ class WritableMapBuffer : MapBuffer { */ fun put(key: Int, value: Int): WritableMapBuffer = putInternal(key, value) + /** + * Adds a long value for given key to the MapBuffer. + * + * @param key entry key + * @param value entry value + * @throws IllegalArgumentException if key is out of [UShort] range + */ + fun put(key: Int, value: Long): WritableMapBuffer = putInternal(key, value) + /** * Adds a double value for given key to the MapBuffer. * @@ -107,6 +116,8 @@ class WritableMapBuffer : MapBuffer { override fun getInt(key: Int): Int = verifyValue(key, values.get(key)) + override fun getLong(key: Int): Long = verifyValue(key, values.get(key)) + override fun getDouble(key: Int): Double = verifyValue(key, values.get(key)) override fun getString(key: Int): String = verifyValue(key, values.get(key)) @@ -128,6 +139,7 @@ class WritableMapBuffer : MapBuffer { return when (val value = this) { is Boolean -> DataType.BOOL is Int -> DataType.INT + is Long -> DataType.LONG is Double -> DataType.DOUBLE is String -> DataType.STRING is MapBuffer -> DataType.MAP @@ -153,6 +165,9 @@ class WritableMapBuffer : MapBuffer { override val intValue: Int get() = verifyValue(key, values.valueAt(index)) + override val longValue: Long + get() = verifyValue(key, values.valueAt(index)) + override val doubleValue: Double get() = verifyValue(key, values.valueAt(index)) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 3e3d86e87dcf0c..b68d537c0688ff 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -30,6 +30,8 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.PointerEventHelper; import com.facebook.react.uimanager.util.ReactFindViewUtil; +import com.facebook.react.views.view.ReactViewGroup; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -164,14 +166,38 @@ public void onLayoutChange( } @Override - @ReactProp( - name = ViewProps.BACKGROUND_COLOR, - defaultInt = Color.TRANSPARENT, - customType = "Color") public void setBackgroundColor(@NonNull T view, int backgroundColor) { view.setBackgroundColor(backgroundColor); } + @ReactProp( + name = ViewProps.BACKGROUND_COLOR, + defaultInt = Color.TRANSPARENT, + customType = "Color") + public void setBackgroundColor(@NonNull T view, long backgroundColor) { + // FLog.e("RYAN", "long BaseViewManager.setBackgroundColor: " + backgroundColor); + + // Check if the view class has a setBackgroundColor(long) method + try { + Method setBackgroundColorMethod = view.getClass().getMethod("setBackgroundColor", long.class); + // FLog.e("RYAN", "setBackgroundColor(long) method was found in the view class"); + + // If the method is found, invoke it + setBackgroundColorMethod.invoke(view, backgroundColor); + + } catch (NoSuchMethodException e) { + // Log or handle the case where the method doesn't exist + // FLog.e("RYAN", "setBackgroundColor(long) method not found in the view class"); + + // Fallback to the existing code with casting to int + view.setBackgroundColor((int) backgroundColor); + + } catch (Exception e) { + // Handle other reflection-related exceptions + // FLog.e("RYAN", "Error invoking setBackgroundColor(long): " + e.getMessage()); + } + } + @Override @ReactProp(name = ViewProps.TRANSFORM) public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) { @@ -204,7 +230,6 @@ public void setElevation(@NonNull T view, float elevation) { } @Override - @ReactProp(name = ViewProps.SHADOW_COLOR, defaultInt = Color.BLACK, customType = "Color") public void setShadowColor(@NonNull T view, int shadowColor) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { view.setOutlineAmbientShadowColor(shadowColor); @@ -212,6 +237,14 @@ public void setShadowColor(@NonNull T view, int shadowColor) { } } + @ReactProp(name = ViewProps.SHADOW_COLOR, defaultInt = Color.BLACK, customType = "Color") + public void setShadowColor(@NonNull T view, long shadowColor) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + view.setOutlineAmbientShadowColor(Color.toArgb(shadowColor)); + view.setOutlineSpotShadowColor(Color.toArgb(shadowColor)); + } + } + @Override @ReactProp(name = ViewProps.Z_INDEX) public void setZIndex(@NonNull T view, float zIndex) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java index 6ef232813dbc3f..2f8208a4012e31 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java @@ -35,6 +35,7 @@ public interface BaseViewManagerInterface { void setViewState(T view, @Nullable ReadableMap accessibilityState); void setBackgroundColor(T view, int backgroundColor); + void setBackgroundColor(T view, long backgroundColor); void setBorderRadius(T view, float borderRadius); @@ -49,6 +50,7 @@ public interface BaseViewManagerInterface { void setElevation(T view, float elevation); void setShadowColor(T view, int shadowColor); + void setShadowColor(T view, long shadowColor); void setImportantForAccessibility(T view, @Nullable String importantForAccessibility); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BorderColor.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BorderColor.java new file mode 100644 index 00000000000000..9605d4cf5613b0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BorderColor.java @@ -0,0 +1,178 @@ +/* + * 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. + */ + +package com.facebook.react.uimanager; + +import android.graphics.Color; +import java.util.Arrays; + +/** + * Class representing CSS spacing border colors. Modified from {@link Spacing} to support long values. + */ +public class BorderColor { + + /** Spacing type that represents the left direction. E.g. {@code marginLeft}. */ + public static final int LEFT = 0; + /** Spacing type that represents the top direction. E.g. {@code marginTop}. */ + public static final int TOP = 1; + /** Spacing type that represents the right direction. E.g. {@code marginRight}. */ + public static final int RIGHT = 2; + /** Spacing type that represents the bottom direction. E.g. {@code marginBottom}. */ + public static final int BOTTOM = 3; + /** + * Spacing type that represents start direction e.g. left in left-to-right, right in + * right-to-left. + */ + public static final int START = 4; + /** + * Spacing type that represents end direction e.g. right in left-to-right, left in right-to-left. + */ + public static final int END = 5; + /** + * Spacing type that represents horizontal direction (left and right). E.g. {@code + * marginHorizontal}. + */ + public static final int HORIZONTAL = 6; + /** + * Spacing type that represents vertical direction (top and bottom). E.g. {@code marginVertical}. + */ + public static final int VERTICAL = 7; + /** + * Spacing type that represents all directions (left, top, right, bottom). E.g. {@code margin}. + */ + public static final int ALL = 8; + /** Spacing type that represents block directions (top, bottom). E.g. {@code marginBlock}. */ + public static final int BLOCK = 9; + /** Spacing type that represents the block end direction (bottom). E.g. {@code marginBlockEnd}. */ + public static final int BLOCK_END = 10; + /** + * Spacing type that represents the block start direction (top). E.g. {@code marginBlockStart}. + */ + public static final int BLOCK_START = 11; + + private static final int[] sFlagsMap = { + 1, /*LEFT*/ 2, /*TOP*/ 4, /*RIGHT*/ 8, /*BOTTOM*/ 16, /*START*/ 32, /*END*/ 64, /*HORIZONTAL*/ + 128, /*VERTICAL*/ 256, /*ALL*/ 512, /*BLOCK*/ 1024, /*BLOCK_END*/ 2048, /*BLOCK_START*/ + }; + + private final long[] mSpacing; + private int mValueFlags = 0; + private final long mDefaultValue; + private boolean mHasAliasesSet; + + public BorderColor() { + this(0); + } + + public BorderColor(long defaultValue) { + mDefaultValue = defaultValue; + mSpacing = newFullBorderColorArray(); + } + + public BorderColor(BorderColor original) { + mDefaultValue = original.mDefaultValue; + mSpacing = Arrays.copyOf(original.mSpacing, original.mSpacing.length); + mValueFlags = original.mValueFlags; + mHasAliasesSet = original.mHasAliasesSet; + } + + /** + * Set a spacing value. + * + * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link + * #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL} + * @param value the value for this direction + * @return {@code true} if the spacing has changed, or {@code false} if the same value was already + * set + */ + public boolean set(int spacingType, long value) { + if (mSpacing[spacingType] != value) { + mSpacing[spacingType] = value; + + mValueFlags |= sFlagsMap[spacingType]; + + mHasAliasesSet = + (mValueFlags & sFlagsMap[ALL]) != 0 + || (mValueFlags & sFlagsMap[VERTICAL]) != 0 + || (mValueFlags & sFlagsMap[HORIZONTAL]) != 0 + || (mValueFlags & sFlagsMap[BLOCK]) != 0; + + return true; + } + + return false; + } + + /** + * Get the spacing for a direction. This takes into account any default values that have been set. + * + * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM} + */ + public long get(int spacingType) { + long defaultValue = + (spacingType == START + || spacingType == END + || spacingType == BLOCK + || spacingType == BLOCK_END + || spacingType == BLOCK_START + ? 0 + : mDefaultValue); + + if (mValueFlags == 0) { + return defaultValue; + } + + if ((mValueFlags & sFlagsMap[spacingType]) != 0) { + return mSpacing[spacingType]; + } + + if (mHasAliasesSet) { + int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL; + if ((mValueFlags & sFlagsMap[secondType]) != 0) { + return mSpacing[secondType]; + } else if ((mValueFlags & sFlagsMap[ALL]) != 0) { + return mSpacing[ALL]; + } + } + + return defaultValue; + } + + /** + * Get the raw value (that was set using {@link #set(int, float)}), without taking into account + * any default values. + * + * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link + * #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL} + */ + public long getRaw(int spacingType) { + return mSpacing[spacingType]; + } + + /** + * Resets the spacing instance to its default state. This method is meant to be used when + * recycling {@link Spacing} instances. + */ + public void reset() { + Arrays.fill(mSpacing, 0); + mHasAliasesSet = false; + mValueFlags = 0; + } + + /** + * Try to get start value and fallback to given type if not defined. This is used privately by the + * layout engine as a more efficient way to fetch direction-aware values by avoid extra method + * invocations. + */ + float getWithFallback(int spacingType, int fallbackType) { + return (mValueFlags & sFlagsMap[spacingType]) != 0 ? mSpacing[spacingType] : get(fallbackType); + } + + private static long[] newFullBorderColorArray() { + return new long[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java index 504bedbfe220da..49ecdddacb3acb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java @@ -9,6 +9,7 @@ import android.view.View; import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -70,6 +71,11 @@ public int getInt(String name, int restoreNullToDefaultValue) { return mBackingMap.isNull(name) ? restoreNullToDefaultValue : mBackingMap.getInt(name); } + public long getLong(String name, long restoreNullToDefaultValue) { + // FLog.e("RYAN", "getLong: " + name + " " + mBackingMap.getLong(name)); + return mBackingMap.isNull(name) ? restoreNullToDefaultValue : mBackingMap.getLong(name); + } + @Nullable public String getString(String name) { return mBackingMap.getString(name); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index 09ae7a272ad0e6..cc5ef32fa0014a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -8,6 +8,7 @@ package com.facebook.react.uimanager; import android.content.Context; +import android.graphics.Color; import android.view.View; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; @@ -194,6 +195,7 @@ protected Object getValueOrDefault(Object value, Context context) { private static class ColorPropSetter extends PropSetter { private final int mDefaultValue; + private ReactProp mProp = null; public ColorPropSetter(ReactProp prop, Method setter) { this(prop, setter, 0); @@ -202,6 +204,7 @@ public ColorPropSetter(ReactProp prop, Method setter) { public ColorPropSetter(ReactProp prop, Method setter, int defaultValue) { super(prop, "mixed", setter); mDefaultValue = defaultValue; + mProp = prop; } public ColorPropSetter(ReactPropGroup prop, Method setter, int index, int defaultValue) { @@ -215,7 +218,8 @@ protected Object getValueOrDefault(Object value, Context context) { return mDefaultValue; } - return ColorPropConverter.getColor(value, context); + Color color = ColorPropConverter.getColorInstance(value, context); + return color.pack(); } } @@ -331,9 +335,11 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) { } private static class BoxedColorPropSetter extends PropSetter { + private ReactProp mProp = null; public BoxedColorPropSetter(ReactProp prop, Method setter) { super(prop, "mixed", setter); + mProp = prop; } public BoxedColorPropSetter(ReactPropGroup prop, Method setter, int index) { @@ -343,7 +349,8 @@ public BoxedColorPropSetter(ReactPropGroup prop, Method setter, int index) { @Override protected @Nullable Object getValueOrDefault(Object value, Context context) { if (value != null) { - return ColorPropConverter.getColor(value, context); + Color color = ColorPropConverter.getColorInstance(value, context); + return color.pack(); } return null; } @@ -437,6 +444,8 @@ private static PropSetter createPropSetter( return new ColorPropSetter(annotation, method, annotation.defaultInt()); } return new IntPropSetter(annotation, method, annotation.defaultInt()); + } else if (propTypeClass == long.class && "Color".equals(annotation.customType())) { + return new ColorPropSetter(annotation, method, annotation.defaultInt()); } else if (propTypeClass == float.class) { return new FloatPropSetter(annotation, method, annotation.defaultFloat()); } else if (propTypeClass == double.class) { @@ -450,6 +459,8 @@ private static PropSetter createPropSetter( return new BoxedColorPropSetter(annotation, method); } return new BoxedIntPropSetter(annotation, method); + } else if (propTypeClass == Long.class && "Color".equals(annotation.customType())) { + return new BoxedColorPropSetter(annotation, method); } else if (propTypeClass == ReadableArray.class) { return new ArrayPropSetter(annotation, method); } else if (propTypeClass == ReadableMap.class) { @@ -483,6 +494,10 @@ private static void createPropSetters( props.put(names[i], new IntPropSetter(annotation, method, i, annotation.defaultInt())); } } + } else if (propTypeClass == long.class && "Color".equals(annotation.customType())) { + for (int i = 0; i < names.length; i++) { + props.put(names[i], new ColorPropSetter(annotation, method, i, annotation.defaultInt())); + } } else if (propTypeClass == float.class) { for (int i = 0; i < names.length; i++) { props.put(names[i], new FloatPropSetter(annotation, method, i, annotation.defaultFloat())); @@ -500,6 +515,10 @@ private static void createPropSetters( props.put(names[i], new BoxedIntPropSetter(annotation, method, i)); } } + } else if (propTypeClass == Long.class && "Color".equals(annotation.customType())) { + for (int i = 0; i < names.length; i++) { + props.put(names[i], new BoxedColorPropSetter(annotation, method, i)); + } } else { throw new RuntimeException( "Unrecognized type: " diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java index 7e24f0d7345574..ab75a8fdbdd159 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java @@ -165,13 +165,17 @@ public void closeDrawer(ReactDrawerLayout view) { public void setKeyboardDismissMode(ReactDrawerLayout view, @Nullable String value) {} @Override - @ReactProp(name = "drawerBackgroundColor", customType = "Color") public void setDrawerBackgroundColor(ReactDrawerLayout view, @Nullable Integer value) {} + @ReactProp(name = "drawerBackgroundColor", customType = "Color") + public void setDrawerBackgroundColor(ReactDrawerLayout view, @Nullable Long value) {} + @Override - @ReactProp(name = "statusBarBackgroundColor", customType = "Color") public void setStatusBarBackgroundColor(ReactDrawerLayout view, @Nullable Integer value) {} + @ReactProp(name = "statusBarBackgroundColor", customType = "Color") + public void setStatusBarBackgroundColor(ReactDrawerLayout view, @Nullable Long value) {} + @Override public void setElevation(@NonNull ReactDrawerLayout view, float elevation) { view.setDrawerElevation(PixelUtil.toPixelFromDIP(elevation)); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 67345805224b87..d9ce1ed1c90a1d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -153,20 +153,20 @@ public void setLoadingIndicatorSource(ReactImageView view, @Nullable String sour } @ReactProp(name = "borderColor", customType = "Color") - public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { + public void setBorderColor(ReactImageView view, @Nullable Long borderColor) { if (borderColor == null) { - view.setBorderColor(Color.TRANSPARENT); + view.setBorderColor(Color.pack(Color.TRANSPARENT)); } else { view.setBorderColor(borderColor); } } @ReactProp(name = "overlayColor", customType = "Color") - public void setOverlayColor(ReactImageView view, @Nullable Integer overlayColor) { + public void setOverlayColor(ReactImageView view, @Nullable Long overlayColor) { if (overlayColor == null) { view.setOverlayColor(Color.TRANSPARENT); } else { - view.setOverlayColor(overlayColor); + view.setOverlayColor(Color.toArgb(overlayColor)); } } @@ -217,11 +217,11 @@ public void setResizeMethod(ReactImageView view, @Nullable String resizeMethod) } @ReactProp(name = "tintColor", customType = "Color") - public void setTintColor(ReactImageView view, @Nullable Integer tintColor) { + public void setTintColor(ReactImageView view, @Nullable Long tintColor) { if (tintColor == null) { view.clearColorFilter(); } else { - view.setColorFilter(tintColor, Mode.SRC_IN); + view.setColorFilter(Color.toArgb(tintColor), Mode.SRC_IN); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index da4fad4b4c7c97..1a1f079a999223 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -239,9 +239,17 @@ public void setBackgroundColor(int backgroundColor) { } } - public void setBorderColor(int borderColor) { + public void setBackgroundColor(long backgroundColor) { + if (mBackgroundColor != backgroundColor) { + mBackgroundColor = (int)backgroundColor; + mBackgroundImageDrawable = new RoundedColorDrawable((int)backgroundColor); + mIsDirty = true; + } + } + + public void setBorderColor(long borderColor) { if (mBorderColor != borderColor) { - mBorderColor = borderColor; + mBorderColor = (int)borderColor; mIsDirty = true; } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java index eb4cbfd779f858..fc5345eef98316 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java @@ -8,6 +8,7 @@ package com.facebook.react.views.progressbar; import android.content.Context; +import android.graphics.Color; import android.util.Pair; import android.view.View; import android.view.ViewGroup; @@ -88,11 +89,15 @@ public void setStyleAttr(ProgressBarContainerView view, @Nullable String styleNa } @Override - @ReactProp(name = ViewProps.COLOR, customType = "Color") public void setColor(ProgressBarContainerView view, @Nullable Integer color) { view.setColor(color); } + @ReactProp(name = ViewProps.COLOR, customType = "Color") + public void setColor(ProgressBarContainerView view, @Nullable Long color) { + view.setColor(Color.toArgb(color)); + } + @Override @ReactProp(name = PROP_INDETERMINATE) public void setIndeterminate(ProgressBarContainerView view, boolean indeterminate) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 7dbcb788c3ac32..7e4ce485c647b5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -17,9 +17,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.view.FocusFinder; import android.view.KeyEvent; @@ -95,8 +94,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private boolean mSendMomentumEvents; private @Nullable FpsListener mFpsListener = null; private @Nullable String mScrollPerfTag; - private @Nullable Drawable mEndBackground; - private int mEndFillColor = Color.TRANSPARENT; + private long mEndFillColor = Color.pack(Color.TRANSPARENT); private boolean mDisableIntervalMomentum = false; private int mSnapInterval = 0; private @Nullable List mSnapOffsets; @@ -779,10 +777,9 @@ private View getContentView() { return getChildAt(0); } - public void setEndFillColor(int color) { + public void setEndFillColor(long color) { if (color != mEndFillColor) { mEndFillColor = color; - mEndBackground = new ColorDrawable(mEndFillColor); } } @@ -856,9 +853,10 @@ private boolean isScrollPerfLoggingEnabled() { public void draw(Canvas canvas) { if (mEndFillColor != Color.TRANSPARENT) { final View content = getContentView(); - if (mEndBackground != null && content != null && content.getRight() < getWidth()) { - mEndBackground.setBounds(content.getRight(), 0, getWidth(), getHeight()); - mEndBackground.draw(canvas); + if (content != null && content.getRight() < getWidth()) { + Paint paint = new Paint(); + paint.setColor(mEndFillColor); + canvas.drawRect(content.getRight(), 0, getWidth(), getHeight(), paint); } } super.draw(canvas); @@ -1272,12 +1270,16 @@ public void setBackgroundColor(int color) { mReactBackgroundManager.setBackgroundColor(color); } + public void setBackgroundColor(long color) { + mReactBackgroundManager.setBackgroundColor(color); + } + public void setBorderWidth(int position, float width) { mReactBackgroundManager.setBorderWidth(position, width); } - public void setBorderColor(int position, float color, float alpha) { - mReactBackgroundManager.setBorderColor(position, color, alpha); + public void setBorderColor(int position, long color) { + mReactBackgroundManager.setBorderColor(position, color); } public void setBorderRadius(float borderRadius) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java index d311a236ed96f7..ef20e2adcdd165 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java @@ -236,7 +236,7 @@ public void scrollToEnd( * @param color */ @ReactProp(name = "endFillColor", defaultInt = Color.TRANSPARENT, customType = "Color") - public void setBottomFillColor(ReactHorizontalScrollView view, int color) { + public void setBottomFillColor(ReactHorizontalScrollView view, long color) { view.setEndFillColor(color); } @@ -291,11 +291,9 @@ public void setBorderWidth(ReactHorizontalScrollView view, int index, float widt "borderBottomColor" }, customType = "Color") - public void setBorderColor(ReactHorizontalScrollView view, int index, Integer color) { - float rgbComponent = - color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF); - float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24); - view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + public void setBorderColor(ReactHorizontalScrollView view, int index, Long color) { + long borderColor = color == null ? 0 : color; + view.setBorderColor(SPACING_TYPES[index], borderColor); } @ReactProp(name = "overflow") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index f66ed102018ab9..a4423b7ffc9194 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -17,9 +17,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.view.KeyEvent; import android.view.MotionEvent; @@ -95,8 +94,7 @@ public class ReactScrollView extends ScrollView private boolean mSendMomentumEvents; private @Nullable FpsListener mFpsListener = null; private @Nullable String mScrollPerfTag; - private @Nullable Drawable mEndBackground; - private int mEndFillColor = Color.TRANSPARENT; + private long mEndFillColor = Color.pack(Color.TRANSPARENT); private boolean mDisableIntervalMomentum = false; private int mSnapInterval = 0; private @Nullable List mSnapOffsets; @@ -620,9 +618,10 @@ public void setStateWrapper(StateWrapper stateWrapper) { public void draw(Canvas canvas) { if (mEndFillColor != Color.TRANSPARENT) { final View content = getChildAt(0); - if (mEndBackground != null && content != null && content.getBottom() < getHeight()) { - mEndBackground.setBounds(0, content.getBottom(), getWidth(), getHeight()); - mEndBackground.draw(canvas); + if (content != null && content.getBottom() < getHeight()) { + Paint paint = new Paint(); + paint.setColor(mEndFillColor); + canvas.drawRect(0, content.getBottom(), getWidth(), getHeight(), paint); } } getDrawingRect(mRect); @@ -1005,10 +1004,9 @@ private int getSnapInterval() { return getHeight(); } - public void setEndFillColor(int color) { + public void setEndFillColor(long color) { if (color != mEndFillColor) { mEndFillColor = color; - mEndBackground = new ColorDrawable(mEndFillColor); } } @@ -1151,12 +1149,16 @@ public void setBackgroundColor(int color) { mReactBackgroundManager.setBackgroundColor(color); } + public void setBackgroundColor(long color) { + mReactBackgroundManager.setBackgroundColor(color); + } + public void setBorderWidth(int position, float width) { mReactBackgroundManager.setBorderWidth(position, width); } - public void setBorderColor(int position, float color, float alpha) { - mReactBackgroundManager.setBorderColor(position, color, alpha); + public void setBorderColor(int position, long color) { + mReactBackgroundManager.setBorderColor(position, color); } public void setBorderRadius(float borderRadius) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java index 1b42aaefefcf92..2f7fc635c150e3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -173,7 +173,7 @@ public void setPagingEnabled(ReactScrollView view, boolean pagingEnabled) { * @param color */ @ReactProp(name = "endFillColor", defaultInt = Color.TRANSPARENT, customType = "Color") - public void setBottomFillColor(ReactScrollView view, int color) { + public void setBottomFillColor(ReactScrollView view, long color) { view.setEndFillColor(color); } @@ -272,10 +272,9 @@ public void setBorderWidth(ReactScrollView view, int index, float width) { "borderBottomColor" }, customType = "Color") - public void setBorderColor(ReactScrollView view, int index, Integer color) { - float rgbComponent = color == null ? YogaConstants.UNDEFINED : (float) (color & 0x00FFFFFF); - float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) (color >>> 24); - view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + public void setBorderColor(ReactScrollView view, int index, Long color) { + long borderColor = color == null ? 0 : color; + view.setBorderColor(SPACING_TYPES[index], borderColor); } @ReactProp(name = "overflow") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java index 255855f3cab4d2..3109fae600ea55 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -83,11 +83,15 @@ public void setColors(ReactSwipeRefreshLayout view, @Nullable ReadableArray colo } @Override - @ReactProp(name = "progressBackgroundColor", customType = "Color") public void setProgressBackgroundColor(ReactSwipeRefreshLayout view, Integer color) { view.setProgressBackgroundColorSchemeColor(color == null ? Color.TRANSPARENT : color); } + @ReactProp(name = "progressBackgroundColor", customType = "Color") + public void setProgressBackgroundColor(ReactSwipeRefreshLayout view, Long color) { + view.setProgressBackgroundColorSchemeColor(color == null ? Color.TRANSPARENT : Color.toArgb(color)); + } + // TODO(T46143833): Remove this method once the 'size' prop has been migrated to String in JS. public void setSize(ReactSwipeRefreshLayout view, int value) { view.setSize(value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java index 2bdd48ecc406b6..c0240bb3dc9b39 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -55,6 +56,12 @@ public void setBackgroundColor(int color) { createRippleDrawableColorStateList(color), new ColorDrawable(color), null)); } + public void setBackgroundColor(long color) { + setBackground( + new RippleDrawable( + createRippleDrawableColorStateList(Color.toArgb(color)), new ColorDrawable(Color.toArgb(color)), null)); + } + void setColor(Drawable drawable, @Nullable Integer color) { if (color == null) { drawable.clearColorFilter(); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index 9fc2d32315eab5..8319278b6f26b9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -9,6 +9,7 @@ package com.facebook.react.views.switchview; import android.content.Context; +import android.graphics.Color; import android.view.View; import android.widget.CompoundButton; import androidx.annotation.NonNull; @@ -142,35 +143,55 @@ public void setValue(ReactSwitch view, boolean value) { } @Override - @ReactProp(name = "thumbTintColor", customType = "Color") public void setThumbTintColor(ReactSwitch view, @Nullable Integer color) { this.setThumbColor(view, color); } + @ReactProp(name = "thumbTintColor", customType = "Color") + public void setThumbTintColor(ReactSwitch view, @Nullable Long color) { + this.setThumbColor(view, Color.toArgb(color)); + } + @Override - @ReactProp(name = "thumbColor", customType = "Color") public void setThumbColor(ReactSwitch view, @Nullable Integer color) { view.setThumbColor(color); } + @ReactProp(name = "thumbColor", customType = "Color") + public void setThumbColor(ReactSwitch view, @Nullable Long color) { + view.setThumbColor(Color.toArgb(color)); + } + @Override - @ReactProp(name = "trackColorForFalse", customType = "Color") public void setTrackColorForFalse(ReactSwitch view, @Nullable Integer color) { view.setTrackColorForFalse(color); } + @ReactProp(name = "trackColorForFalse", customType = "Color") + public void setTrackColorForFalse(ReactSwitch view, @Nullable Long color) { + view.setTrackColorForFalse(Color.toArgb(color)); + } + @Override - @ReactProp(name = "trackColorForTrue", customType = "Color") public void setTrackColorForTrue(ReactSwitch view, @Nullable Integer color) { view.setTrackColorForTrue(color); } + @ReactProp(name = "trackColorForTrue", customType = "Color") + public void setTrackColorForTrue(ReactSwitch view, @Nullable Long color) { + view.setTrackColorForTrue(Color.toArgb(color)); + } + @Override - @ReactProp(name = "trackTintColor", customType = "Color") public void setTrackTintColor(ReactSwitch view, @Nullable Integer color) { view.setTrackColor(color); } + @ReactProp(name = "trackTintColor", customType = "Color") + public void setTrackTintColor(ReactSwitch view, @Nullable Long color) { + view.setTrackColor(Color.toArgb(color)); + } + @Override public void setNativeValue(ReactSwitch view, boolean value) { setValueInternal(view, value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBackgroundColorSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBackgroundColorSpan.java index 0954a0f8bc8c97..2bcf7f27887714 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBackgroundColorSpan.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBackgroundColorSpan.java @@ -7,6 +7,7 @@ package com.facebook.react.views.text; +import android.graphics.Color; import android.text.style.BackgroundColorSpan; /* @@ -16,4 +17,8 @@ public class ReactBackgroundColorSpan extends BackgroundColorSpan implements Rea public ReactBackgroundColorSpan(int color) { super(color); } + + public ReactBackgroundColorSpan(long color) { + super(Color.toArgb(color)); + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index cb02e705da57ae..39f37b169b54a8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -10,6 +10,7 @@ import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; +import android.os.Parcel; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -300,9 +301,9 @@ protected Spannable spannedFromShadowNode( protected TextAttributes mTextAttributes; protected boolean mIsColorSet = false; - protected int mColor; + protected long mColor; protected boolean mIsBackgroundColorSet = false; - protected int mBackgroundColor; + protected long mBackgroundColor; protected @Nullable AccessibilityRole mAccessibilityRole = null; protected @Nullable Role mRole = null; @@ -317,7 +318,7 @@ protected Spannable spannedFromShadowNode( protected float mTextShadowOffsetDx = 0; protected float mTextShadowOffsetDy = 0; protected float mTextShadowRadius = 0; - protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; + protected long mTextShadowColor = Color.pack(DEFAULT_TEXT_SHADOW_COLOR); protected boolean mIsUnderlineTextDecorationSet = false; protected boolean mIsLineThroughTextDecorationSet = false; @@ -453,7 +454,7 @@ public void setFontSize(float fontSize) { } @ReactProp(name = ViewProps.COLOR, customType = "Color") - public void setColor(@Nullable Integer color) { + public void setColor(@Nullable Long color) { mIsColorSet = (color != null); if (mIsColorSet) { mColor = color; @@ -462,7 +463,7 @@ public void setColor(@Nullable Integer color) { } @ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color") - public void setBackgroundColor(@Nullable Integer color) { + public void setBackgroundColor(@Nullable Long color) { // Background color needs to be handled here for virtual nodes so it can be incorporated into // the span. However, it doesn't need to be applied to non-virtual nodes because non-virtual // nodes get mapped to native views and native views get their background colors get set via @@ -593,7 +594,7 @@ public void setTextShadowRadius(float textShadowRadius) { } @ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color") - public void setTextShadowColor(int textShadowColor) { + public void setTextShadowColor(long textShadowColor) { if (textShadowColor != mTextShadowColor) { mTextShadowColor = textShadowColor; markUpdated(); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactForegroundColorSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactForegroundColorSpan.java index 3aebde60799b97..722d9828c7317a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactForegroundColorSpan.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactForegroundColorSpan.java @@ -7,13 +7,31 @@ package com.facebook.react.views.text; +import android.graphics.Color; import android.text.style.ForegroundColorSpan; +import android.text.TextPaint; +import androidx.annotation.NonNull; /* * Wraps {@link ForegroundColorSpan} as a {@link ReactSpan}. */ public class ReactForegroundColorSpan extends ForegroundColorSpan implements ReactSpan { + private long mColor = 0; + public ReactForegroundColorSpan(int color) { super(color); } + + public ReactForegroundColorSpan(long color) { + super(Color.toArgb(color)); + this.mColor = color; + } + + @Override + public void updateDrawState(@NonNull TextPaint tp) { + super.updateDrawState(tp); + if (mColor != 0) { + tp.setColor(mColor); + } + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java index 867aac0d2f530b..c788d70a4e8bb8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java @@ -7,6 +7,7 @@ package com.facebook.react.views.text; +import android.graphics.Color; import android.text.Layout; import android.text.Spannable; import android.text.TextUtils; @@ -106,12 +107,12 @@ public void setSelectable(ReactTextView view, boolean isSelectable) { } @ReactProp(name = "selectionColor", customType = "Color") - public void setSelectionColor(ReactTextView view, @Nullable Integer color) { + public void setSelectionColor(ReactTextView view, @Nullable Long color) { if (color == null) { view.setHighlightColor( DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.getContext())); } else { - view.setHighlightColor(color); + view.setHighlightColor(Color.toArgb(color)); } } @@ -180,11 +181,9 @@ public void setBorderWidth(ReactTextView view, int index, float width) { "borderBottomColor" }, customType = "Color") - public void setBorderColor(ReactTextView view, int index, Integer color) { - float rgbComponent = - color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF); - float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24); - view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + public void setBorderColor(ReactTextView view, int index, Long color) { + long borderColor = color == null ? 0 : color; + view.setBorderColor(SPACING_TYPES[index], borderColor); } @ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 83bacaafb9ea60..8de14044b0b40d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -625,12 +625,16 @@ public void setBackgroundColor(int color) { mReactBackgroundManager.setBackgroundColor(color); } + public void setBackgroundColor(long color) { + mReactBackgroundManager.setBackgroundColor(color); + } + public void setBorderWidth(int position, float width) { mReactBackgroundManager.setBorderWidth(position, width); } - public void setBorderColor(int position, float color, float alpha) { - mReactBackgroundManager.setBorderColor(position, color, alpha); + public void setBorderColor(int position, long color) { + mReactBackgroundManager.setBorderColor(position, color); } public void setBorderRadius(float borderRadius) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java index 4fe4bf958201e7..d674c95f971f48 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java @@ -7,14 +7,23 @@ package com.facebook.react.views.text; +import android.graphics.Color; +import android.graphics.ColorSpace; import android.text.TextPaint; import android.text.style.CharacterStyle; public class ShadowStyleSpan extends CharacterStyle implements ReactSpan { private final float mDx, mDy, mRadius; - private final int mColor; + private final long mColor; public ShadowStyleSpan(float dx, float dy, float radius, int color) { + mDx = dx; + mDy = dy; + mRadius = radius; + mColor = Color.pack(color); + } + + public ShadowStyleSpan(float dx, float dy, float radius, long color) { mDx = dx; mDy = dy; mRadius = radius; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index aa450d79fd2111..13ca81e7477cef 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -7,6 +7,7 @@ package com.facebook.react.views.text; +import android.graphics.Color; import android.os.Build; import android.text.Layout; import android.text.TextUtils; @@ -71,7 +72,7 @@ public class TextAttributeProps { private static final String PROP_TEXT_TRANSFORM = "textTransform"; - private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000; + private static final long DEFAULT_TEXT_SHADOW_COLOR = Color.pack(0x55000000); private static final int DEFAULT_JUSTIFICATION_MODE = (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE; private static final int DEFAULT_BREAK_STRATEGY = Layout.BREAK_STRATEGY_HIGH_QUALITY; @@ -80,9 +81,9 @@ public class TextAttributeProps { protected float mLineHeight = Float.NaN; protected boolean mIsColorSet = false; protected boolean mAllowFontScaling = true; - protected int mColor; + protected long mColor; protected boolean mIsBackgroundColorSet = false; - protected int mBackgroundColor; + protected long mBackgroundColor; protected int mNumberOfLines = UNSET; protected int mFontSize = UNSET; @@ -99,7 +100,7 @@ public class TextAttributeProps { protected float mTextShadowOffsetDx = 0; protected float mTextShadowOffsetDy = 0; protected float mTextShadowRadius = 0; - protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; + protected long mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; protected boolean mIsUnderlineTextDecorationSet = false; protected boolean mIsLineThroughTextDecorationSet = false; @@ -151,10 +152,10 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { MapBuffer.Entry entry = iterator.next(); switch (entry.getKey()) { case TA_KEY_FOREGROUND_COLOR: - result.setColor(entry.getIntValue()); + result.setColor(entry.getLongValue()); break; case TA_KEY_BACKGROUND_COLOR: - result.setBackgroundColor(entry.getIntValue()); + result.setBackgroundColor(entry.getLongValue()); break; case TA_KEY_OPACITY: break; @@ -199,7 +200,7 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { result.setTextShadowRadius((float) entry.getDoubleValue()); break; case TA_KEY_TEXT_SHADOW_COLOR: - result.setTextShadowColor(entry.getIntValue()); + result.setTextShadowColor(entry.getLongValue()); break; case TA_KEY_TEXT_SHADOW_OFFSET_DX: result.setTextShadowOffsetDx((float) entry.getDoubleValue()); @@ -238,14 +239,14 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) { result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN)); result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true)); result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, UNSET)); - result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null); + result.setColor(getLongProp(props, ViewProps.COLOR, 0)); result.setColor( props.hasKey(ViewProps.FOREGROUND_COLOR) - ? props.getInt(ViewProps.FOREGROUND_COLOR, 0) + ? props.getLong(ViewProps.FOREGROUND_COLOR, 0) : null); result.setBackgroundColor( props.hasKey(ViewProps.BACKGROUND_COLOR) - ? props.getInt(ViewProps.BACKGROUND_COLOR, 0) + ? props.getLong(ViewProps.BACKGROUND_COLOR, 0) : null); result.setFontFamily(getStringProp(props, ViewProps.FONT_FAMILY)); result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT)); @@ -256,7 +257,7 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) { result.setTextShadowOffset( props.hasKey(PROP_SHADOW_OFFSET) ? props.getMap(PROP_SHADOW_OFFSET) : null); result.setTextShadowRadius(getFloatProp(props, PROP_SHADOW_RADIUS, 1)); - result.setTextShadowColor(getIntProp(props, PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR)); + result.setTextShadowColor(getLongProp(props, PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR)); result.setTextTransform(getStringProp(props, PROP_TEXT_TRANSFORM)); result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION)); result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE)); @@ -325,6 +326,14 @@ private static int getIntProp(ReactStylesDiffMap mProps, String name, int defaul } } + private static long getLongProp(ReactStylesDiffMap mProps, String name, long defaultvalue) { + if (mProps.hasKey(name)) { + return mProps.getLong(name, defaultvalue); + } else { + return defaultvalue; + } + } + private static float getFloatProp(ReactStylesDiffMap mProps, String name, float defaultvalue) { if (mProps.hasKey(name)) { return mProps.getFloat(name, defaultvalue); @@ -406,14 +415,14 @@ private void setFontSize(float fontSize) { mFontSize = (int) fontSize; } - private void setColor(@Nullable Integer color) { + private void setColor(@Nullable Long color) { mIsColorSet = (color != null); if (mIsColorSet) { mColor = color; } } - private void setBackgroundColor(Integer color) { + private void setBackgroundColor(Long color) { // TODO: Don't apply background color to anchor TextView since it will be applied on the View // directly // if (!isVirtualAnchor()) { @@ -603,7 +612,7 @@ private void setTextShadowRadius(float textShadowRadius) { } } - private void setTextShadowColor(int textShadowColor) { + private void setTextShadowColor(long textShadowColor) { if (textShadowColor != mTextShadowColor) { mTextShadowColor = textShadowColor; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java index 7891aee93a030c..33bb4cb1d2ee0e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.res.Resources; +import android.graphics.Color; import android.net.Uri; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; @@ -75,8 +76,8 @@ public void setHeaders(ReadableMap headers) { } @ReactProp(name = "tintColor", customType = "Color") - public void setTintColor(int tintColor) { - mTintColor = tintColor; + public void setTintColor(long tintColor) { + mTintColor = Color.toArgb(tintColor); } /** Besides width/height, all other layout props on inline images are ignored */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 1c7950e4b3c006..cc0a74509907a3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -1062,15 +1062,19 @@ public void setBackgroundColor(int color) { mReactBackgroundManager.setBackgroundColor(color); } + public void setBackgroundColor(long color) { + mReactBackgroundManager.setBackgroundColor(color); + } + public void setBorderWidth(int position, float width) { mReactBackgroundManager.setBorderWidth(position, width); } - public void setBorderColor(int position, float color, float alpha) { - mReactBackgroundManager.setBorderColor(position, color, alpha); + public void setBorderColor(int position, long color) { + mReactBackgroundManager.setBorderColor(position, color); } - public int getBorderColor(int position) { + public long getBorderColor(int position) { return mReactBackgroundManager.getBorderColor(position); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 376af6a81e6a10..5b6cbdcf7914d0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -13,6 +13,7 @@ import android.content.res.ColorStateList; import android.graphics.BlendMode; import android.graphics.BlendModeColorFilter; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; @@ -504,32 +505,32 @@ public void setPlaceholder(ReactEditText view, String placeholder) { } @ReactProp(name = "placeholderTextColor", customType = "Color") - public void setPlaceholderTextColor(ReactEditText view, @Nullable Integer color) { + public void setPlaceholderTextColor(ReactEditText view, @Nullable Long color) { if (color == null) { view.setHintTextColor(DefaultStyleValuesUtil.getDefaultTextColorHint(view.getContext())); } else { - view.setHintTextColor(color); + view.setHintTextColor(Color.toArgb(color)); } } @ReactProp(name = "selectionColor", customType = "Color") - public void setSelectionColor(ReactEditText view, @Nullable Integer color) { + public void setSelectionColor(ReactEditText view, @Nullable Long color) { if (color == null) { view.setHighlightColor( DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.getContext())); } else { - view.setHighlightColor(color); + view.setHighlightColor(Color.toArgb(color)); } } @ReactProp(name = "selectionHandleColor", customType = "Color") - public void setSelectionHandleColor(ReactEditText view, @Nullable Integer color) { + public void setSelectionHandleColor(ReactEditText view, @Nullable Long color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Drawable drawableCenter = view.getTextSelectHandle().mutate(); Drawable drawableLeft = view.getTextSelectHandleLeft().mutate(); Drawable drawableRight = view.getTextSelectHandleRight().mutate(); if (color != null) { - BlendModeColorFilter filter = new BlendModeColorFilter(color, BlendMode.SRC_IN); + BlendModeColorFilter filter = new BlendModeColorFilter(Color.toArgb(color), BlendMode.SRC_IN); drawableCenter.setColorFilter(filter); drawableLeft.setColorFilter(filter); drawableRight.setColorFilter(filter); @@ -564,7 +565,7 @@ public void setSelectionHandleColor(ReactEditText view, @Nullable Integer color) Drawable drawable = ContextCompat.getDrawable(view.getContext(), resourceId).mutate(); if (color != null) { - drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + drawable.setColorFilter(Color.toArgb(color), PorterDuff.Mode.SRC_IN); } else { drawable.clearColorFilter(); } @@ -583,12 +584,12 @@ public void setSelectionHandleColor(ReactEditText view, @Nullable Integer color) } @ReactProp(name = "cursorColor", customType = "Color") - public void setCursorColor(ReactEditText view, @Nullable Integer color) { + public void setCursorColor(ReactEditText view, @Nullable Long color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Drawable cursorDrawable = view.getTextCursorDrawable(); if (cursorDrawable != null) { if (color != null) { - cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN)); + cursorDrawable.setColorFilter(new BlendModeColorFilter(Color.toArgb(color), BlendMode.SRC_IN)); } else { cursorDrawable.clearColorFilter(); } @@ -618,7 +619,7 @@ public void setCursorColor(ReactEditText view, @Nullable Integer color) { Drawable drawable = ContextCompat.getDrawable(view.getContext(), resourceId).mutate(); if (color != null) { - drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + drawable.setColorFilter(Color.toArgb(color), PorterDuff.Mode.SRC_IN); } else { drawable.clearColorFilter(); } @@ -669,7 +670,7 @@ public void setSelectTextOnFocus(ReactEditText view, boolean selectTextOnFocus) } @ReactProp(name = ViewProps.COLOR, customType = "Color") - public void setColor(ReactEditText view, @Nullable Integer color) { + public void setColor(ReactEditText view, @Nullable Long color) { if (color == null) { ColorStateList defaultContextTextColor = DefaultStyleValuesUtil.getDefaultTextColor(view.getContext()); @@ -685,12 +686,12 @@ public void setColor(ReactEditText view, @Nullable Integer color) { + (c != null ? c.getClass().getCanonicalName() : "null"))); } } else { - view.setTextColor(color); + view.getPaint().setColor(color); } } @ReactProp(name = "underlineColorAndroid", customType = "Color") - public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineColor) { + public void setUnderlineColor(ReactEditText view, @Nullable Long underlineColor) { // Drawable.mutate() can sometimes crash due to an AOSP bug: // See https://code.google.com/p/android/issues/detail?id=191754 for more info Drawable background = view.getBackground(); @@ -714,12 +715,12 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol // fixes underlineColor transparent not working on API 21 // re-sets the TextInput underlineColor https://bit.ly/3M4alr6 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { - int bottomBorderColor = view.getBorderColor(Spacing.BOTTOM); + long bottomBorderColor = view.getBorderColor(Spacing.BOTTOM); setBorderColor(view, Spacing.START, underlineColor); - drawableToMutate.setColorFilter(underlineColor, PorterDuff.Mode.SRC_IN); + drawableToMutate.setColorFilter(Color.toArgb(underlineColor), PorterDuff.Mode.SRC_IN); setBorderColor(view, Spacing.START, bottomBorderColor); } else { - drawableToMutate.setColorFilter(underlineColor, PorterDuff.Mode.SRC_IN); + drawableToMutate.setColorFilter(Color.toArgb(underlineColor), PorterDuff.Mode.SRC_IN); } } } @@ -1033,11 +1034,9 @@ public void setBorderWidth(ReactEditText view, int index, float width) { "borderBottomColor" }, customType = "Color") - public void setBorderColor(ReactEditText view, int index, Integer color) { - float rgbComponent = - color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF); - float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24); - view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + public void setBorderColor(ReactEditText view, int index, Long color) { + long borderColor = color == null ? 0 : color; + view.setBorderColor(SPACING_TYPES[index], borderColor); } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt index ef65868eca5855..d8318956863235 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt @@ -114,7 +114,7 @@ object ReactMapBufferPropSetter { private const val NATIVE_DRAWABLE_BORDERLESS = 3 private const val NATIVE_DRAWABLE_RIPPLE_RADIUS = 4 - private const val UNDEF_COLOR = Int.MAX_VALUE + private const val UNDEF_COLOR = Long.MAX_VALUE fun setProps(view: ReactViewGroup, viewManager: ReactViewManager, props: MapBuffer) { for (entry in props) { @@ -151,7 +151,7 @@ object ReactMapBufferPropSetter { } VP_BG_COLOR -> { // TODO: color for some reason can be object in Java but not in C++ - viewManager.backgroundColor(view, entry.intValue) + viewManager.backgroundColor(view, entry.longValue) } VP_FG_COLOR -> { // Prop not used on Android? @@ -242,7 +242,7 @@ object ReactMapBufferPropSetter { } VP_SHADOW_COLOR -> { // TODO: color for some reason can be object in Java but not in C++ - viewManager.shadowColor(view, entry.intValue) + viewManager.shadowColor(view, entry.longValue) } VP_TEST_ID -> { viewManager.setTestId(view, entry.stringValue.takeIf { it.isNotEmpty() }) @@ -342,8 +342,8 @@ object ReactMapBufferPropSetter { setBackfaceVisibility(view, stringName) } - private fun ReactViewManager.backgroundColor(view: ReactViewGroup, value: Int) { - val color = value.takeIf { it != UNDEF_COLOR } ?: Color.TRANSPARENT + private fun ReactViewManager.backgroundColor(view: ReactViewGroup, value: Long) { + val color = value.takeIf { it != UNDEF_COLOR } ?: Color.pack(Color.TRANSPARENT) setBackgroundColor(view, color) } @@ -364,7 +364,7 @@ object ReactMapBufferPropSetter { else -> throw IllegalArgumentException("Unknown key for border color: $key") } val colorValue = entry.intValue - setBorderColor(view, index, colorValue.takeIf { it != -1 }) + setBorderColor(view, index, (colorValue.takeIf { it != -1 })?.toLong()) } } @@ -484,8 +484,8 @@ object ReactMapBufferPropSetter { setOverflow(view, stringValue) } - private fun ReactViewManager.shadowColor(view: ReactViewGroup, value: Int) { - val color = value.takeIf { it != UNDEF_COLOR } ?: Color.BLACK + private fun ReactViewManager.shadowColor(view: ReactViewGroup, value: Long) { + val color = value.takeIf { it != UNDEF_COLOR } ?: Color.pack(Color.BLACK) setShadowColor(view, color) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java index c93128a89aa270..0d696b7a84083f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java @@ -10,21 +10,25 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorSpace; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathEffect; +import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.view.View; +import androidx.annotation.ColorLong; import androidx.annotation.Nullable; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.modules.i18nmanager.I18nUtil; +import com.facebook.react.uimanager.BorderColor; import com.facebook.react.uimanager.FloatUtil; import com.facebook.react.uimanager.Spacing; import com.facebook.yoga.YogaConstants; @@ -44,9 +48,7 @@ */ public class ReactViewBackgroundDrawable extends Drawable { - private static final int DEFAULT_BORDER_COLOR = Color.BLACK; - private static final int DEFAULT_BORDER_RGB = 0x00FFFFFF & DEFAULT_BORDER_COLOR; - private static final int DEFAULT_BORDER_ALPHA = (0xFF000000 & DEFAULT_BORDER_COLOR) >>> 24; + private static final long DEFAULT_BORDER_COLOR = Color.pack(Color.BLACK); // ~0 == 0xFFFFFFFF, all bits set to 1. private static final int ALL_BITS_SET = ~0; // 0 == 0x00000000, all bits set to 0. @@ -78,8 +80,7 @@ private enum BorderStyle { /* Value at Spacing.ALL index used for rounded borders, whole array used by rectangular borders */ private @Nullable Spacing mBorderWidth; - private @Nullable Spacing mBorderRGB; - private @Nullable Spacing mBorderAlpha; + private @Nullable BorderColor mBorderColor; private @Nullable BorderStyle mBorderStyle; private @Nullable Path mInnerClipPathForBorderRadius; @@ -102,7 +103,7 @@ private enum BorderStyle { /* Used by all types of background and for drawing borders */ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private int mColor = Color.TRANSPARENT; + private long mColor = Color.pack(Color.TRANSPARENT); private int mAlpha = 255; // There is a small gap between the edges of adjacent paths @@ -179,6 +180,13 @@ public int getAlpha() { return mAlpha; } + public long useColor() { + Color color = Color.valueOf(mColor); + float colorAlpha = color.alpha(); + float combinedAlpha = colorAlpha * (mAlpha / 255.0f); + return Color.pack(color.red(), color.green(), color.blue(), combinedAlpha, color.getColorSpace()); + } + @Override public void setColorFilter(ColorFilter cf) { // do nothing @@ -186,7 +194,14 @@ public void setColorFilter(ColorFilter cf) { @Override public int getOpacity() { - return ColorUtil.getOpacityFromColor(ColorUtil.multiplyColorAlpha(mColor, mAlpha)); + float alpha = Color.valueOf(mColor).alpha() * (mAlpha / 255.0f); + if (alpha == 1.0f) { + return PixelFormat.OPAQUE; + } else if (alpha == 0.0f) { + return PixelFormat.TRANSPARENT; + } else { + return PixelFormat.TRANSLUCENT; + } } /* Android's elevation implementation requires this to be implemented to know where to draw the shadow. */ @@ -204,7 +219,7 @@ public void getOutline(Outline outline) { public void setBorderWidth(int position, float width) { if (mBorderWidth == null) { - mBorderWidth = new Spacing(); + mBorderWidth = new Spacing(YogaConstants.UNDEFINED); } if (!FloatUtil.floatsEqual(mBorderWidth.getRaw(position), width)) { mBorderWidth.set(position, width); @@ -222,32 +237,15 @@ public void setBorderWidth(int position, float width) { } } - public void setBorderColor(int position, float rgb, float alpha) { - this.setBorderRGB(position, rgb); - this.setBorderAlpha(position, alpha); - mNeedUpdatePathForBorderRadius = true; - } - - private void setBorderRGB(int position, float rgb) { - // set RGB component - if (mBorderRGB == null) { - mBorderRGB = new Spacing(DEFAULT_BORDER_RGB); - } - if (!FloatUtil.floatsEqual(mBorderRGB.getRaw(position), rgb)) { - mBorderRGB.set(position, rgb); - invalidateSelf(); - } - } - - private void setBorderAlpha(int position, float alpha) { - // set Alpha component - if (mBorderAlpha == null) { - mBorderAlpha = new Spacing(DEFAULT_BORDER_ALPHA); + public void setBorderColor(int position, long color) { + if (mBorderColor == null) { + mBorderColor = new BorderColor(DEFAULT_BORDER_COLOR); } - if (!FloatUtil.floatsEqual(mBorderAlpha.getRaw(position), alpha)) { - mBorderAlpha.set(position, alpha); + if (mBorderColor.getRaw(position) != color) { + mBorderColor.set(position, color); invalidateSelf(); } + mNeedUpdatePathForBorderRadius = true; } public void setBorderStyle(@Nullable String style) { @@ -305,6 +303,11 @@ public float getBorderRadiusOrDefaultTo( } public void setColor(int color) { + mColor = Color.pack(color); + invalidateSelf(); + } + + public void setColor(long color) { mColor = color; invalidateSelf(); } @@ -330,7 +333,7 @@ public boolean onResolvedLayoutDirectionChanged(int layoutDirection) { @VisibleForTesting public int getColor() { - return mColor; + return Color.toArgb(mColor); } private void drawRoundedBackgroundWithBorders(Canvas canvas) { @@ -341,22 +344,22 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) { canvas.clipPath(mOuterClipPathForBorderRadius, Region.Op.INTERSECT); // Draws the View without its border first (with background color fill) - int useColor = ColorUtil.multiplyColorAlpha(mColor, mAlpha); - if (Color.alpha(useColor) != 0) { // color is not transparent - mPaint.setColor(useColor); + long color = useColor(); + if (Color.alpha(color) != 0.0f) { // color is not transparent + mPaint.setColor(color); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mBackgroundColorRenderPath, mPaint); } final RectF borderWidth = getDirectionAwareBorderInsets(); - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); + long colorLeft = getBorderColor(Spacing.LEFT); + long colorTop = getBorderColor(Spacing.TOP); + long colorRight = getBorderColor(Spacing.RIGHT); + long colorBottom = getBorderColor(Spacing.BOTTOM); - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); + long colorBlock = getBorderColor(Spacing.BLOCK); + long colorBlockStart = getBorderColor(Spacing.BLOCK_START); + long colorBlockEnd = getBorderColor(Spacing.BLOCK_END); if (isBorderColorDefined(Spacing.BLOCK)) { colorBottom = colorBlock; @@ -376,7 +379,7 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) { // If it's a full and even border draw inner rect path with stroke final float fullBorderWidth = getFullBorderWidth(); - int borderColor = getBorderColor(Spacing.ALL); + long borderColor = getBorderColor(Spacing.ALL); if (borderWidth.top == fullBorderWidth && borderWidth.bottom == fullBorderWidth && borderWidth.left == fullBorderWidth @@ -386,7 +389,14 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) { && colorRight == borderColor && colorBottom == borderColor) { if (fullBorderWidth > 0) { - mPaint.setColor(ColorUtil.multiplyColorAlpha(borderColor, mAlpha)); + long alphaAdjustedBorderColor = + Color.pack( + Color.red(borderColor), + Color.green(borderColor), + Color.blue(borderColor), + Color.alpha(borderColor) * (mAlpha / 255.0f), + Color.colorSpace(borderColor)); + mPaint.setColor(alphaAdjustedBorderColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(fullBorderWidth); canvas.drawPath(mCenterDrawPath, mPaint); @@ -400,8 +410,8 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) { canvas.clipPath(mInnerClipPathForBorderRadius, Region.Op.DIFFERENCE); final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - int colorStart = getBorderColor(Spacing.START); - int colorEnd = getBorderColor(Spacing.END); + long colorStart = getBorderColor(Spacing.START); + long colorEnd = getBorderColor(Spacing.END); if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { if (!isBorderColorDefined(Spacing.START)) { @@ -412,14 +422,14 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) { colorEnd = colorRight; } - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; + final long directionAwareColorLeft = isRTL ? colorEnd : colorStart; + final long directionAwareColorRight = isRTL ? colorStart : colorEnd; colorLeft = directionAwareColorLeft; colorRight = directionAwareColorRight; } else { - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; + final long directionAwareColorLeft = isRTL ? colorEnd : colorStart; + final long directionAwareColorRight = isRTL ? colorStart : colorEnd; final boolean isColorStartDefined = isBorderColorDefined(Spacing.START); final boolean isColorEndDefined = isBorderColorDefined(Spacing.END); @@ -557,15 +567,15 @@ private void updatePath() { final RectF borderWidth = getDirectionAwareBorderInsets(); - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); - int borderColor = getBorderColor(Spacing.ALL); + long colorLeft = getBorderColor(Spacing.LEFT); + long colorTop = getBorderColor(Spacing.TOP); + long colorRight = getBorderColor(Spacing.RIGHT); + long colorBottom = getBorderColor(Spacing.BOTTOM); + long borderColor = getBorderColor(Spacing.ALL); - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); + long colorBlock = getBorderColor(Spacing.BLOCK); + long colorBlockStart = getBorderColor(Spacing.BLOCK_START); + long colorBlockEnd = getBorderColor(Spacing.BLOCK_END); if (isBorderColorDefined(Spacing.BLOCK)) { colorBottom = colorBlock; @@ -1106,21 +1116,21 @@ public float getFullBorderWidth() { * * @return A compatible border color, or zero if the border colors are not compatible. */ - private static int fastBorderCompatibleColorOrZero( + private static long fastBorderCompatibleColorOrZero( int borderLeft, int borderTop, int borderRight, int borderBottom, - int colorLeft, - int colorTop, - int colorRight, - int colorBottom) { - int andSmear = + long colorLeft, + long colorTop, + long colorRight, + long colorBottom) { + long andSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_SET) & (borderTop > 0 ? colorTop : ALL_BITS_SET) & (borderRight > 0 ? colorRight : ALL_BITS_SET) & (borderBottom > 0 ? colorBottom : ALL_BITS_SET); - int orSmear = + long orSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_UNSET) | (borderTop > 0 ? colorTop : ALL_BITS_UNSET) | (borderRight > 0 ? colorRight : ALL_BITS_UNSET) @@ -1131,9 +1141,9 @@ private static int fastBorderCompatibleColorOrZero( private void drawRectangularBackgroundWithBorders(Canvas canvas) { mPaint.setStyle(Paint.Style.FILL); - int useColor = ColorUtil.multiplyColorAlpha(mColor, mAlpha); - if (Color.alpha(useColor) != 0) { // color is not transparent - mPaint.setColor(useColor); + long color = useColor(); + if (Color.alpha(color) != 0.0f) { // color is not transparent + mPaint.setColor(color); canvas.drawRect(getBounds(), mPaint); } @@ -1148,14 +1158,14 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) { if (borderLeft > 0 || borderRight > 0 || borderTop > 0 || borderBottom > 0) { Rect bounds = getBounds(); - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); + long colorLeft = getBorderColor(Spacing.LEFT); + long colorTop = getBorderColor(Spacing.TOP); + long colorRight = getBorderColor(Spacing.RIGHT); + long colorBottom = getBorderColor(Spacing.BOTTOM); - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); + long colorBlock = getBorderColor(Spacing.BLOCK); + long colorBlockStart = getBorderColor(Spacing.BLOCK_START); + long colorBlockEnd = getBorderColor(Spacing.BLOCK_END); if (isBorderColorDefined(Spacing.BLOCK)) { colorBottom = colorBlock; @@ -1169,8 +1179,8 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) { } final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - int colorStart = getBorderColor(Spacing.START); - int colorEnd = getBorderColor(Spacing.END); + long colorStart = getBorderColor(Spacing.START); + long colorEnd = getBorderColor(Spacing.END); if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { if (!isBorderColorDefined(Spacing.START)) { @@ -1181,14 +1191,14 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) { colorEnd = colorRight; } - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; + final long directionAwareColorLeft = isRTL ? colorEnd : colorStart; + final long directionAwareColorRight = isRTL ? colorStart : colorEnd; colorLeft = directionAwareColorLeft; colorRight = directionAwareColorRight; } else { - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; + final long directionAwareColorLeft = isRTL ? colorEnd : colorStart; + final long directionAwareColorRight = isRTL ? colorStart : colorEnd; final boolean isColorStartDefined = isBorderColorDefined(Spacing.START); final boolean isColorEndDefined = isBorderColorDefined(Spacing.END); @@ -1210,7 +1220,7 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) { int top = bounds.top; // Check for fast path to border drawing. - int fastBorderColor = + long fastBorderColor = fastBorderCompatibleColorOrZero( borderLeft, borderTop, @@ -1338,7 +1348,7 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) { private void drawQuadrilateral( Canvas canvas, - int fillColor, + long fillColor, float x1, float y1, float x2, @@ -1374,24 +1384,13 @@ private int getBorderWidth(int position) { return YogaConstants.isUndefined(width) ? -1 : Math.round(width); } - private static int colorFromAlphaAndRGBComponents(float alpha, float rgb) { - int rgbComponent = 0x00FFFFFF & (int) rgb; - int alphaComponent = 0xFF000000 & ((int) alpha) << 24; - - return rgbComponent | alphaComponent; - } - private boolean isBorderColorDefined(int position) { - final float rgb = mBorderRGB != null ? mBorderRGB.get(position) : YogaConstants.UNDEFINED; - final float alpha = mBorderAlpha != null ? mBorderAlpha.get(position) : YogaConstants.UNDEFINED; - return !YogaConstants.isUndefined(rgb) && !YogaConstants.isUndefined(alpha); + long color = mBorderColor != null ? mBorderColor.get(position) : 0; + return color != 0; } - public int getBorderColor(int position) { - float rgb = mBorderRGB != null ? mBorderRGB.get(position) : DEFAULT_BORDER_RGB; - float alpha = mBorderAlpha != null ? mBorderAlpha.get(position) : DEFAULT_BORDER_ALPHA; - - return ReactViewBackgroundDrawable.colorFromAlphaAndRGBComponents(alpha, rgb); + public long getBorderColor(int position) { + return mBorderColor != null ? mBorderColor.get(position) : DEFAULT_BORDER_COLOR; } public RectF getDirectionAwareBorderInsets() { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java index 9b479fa42214be..12bf4fbf64c591 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java @@ -57,6 +57,14 @@ public void setBackgroundColor(int color) { } } + public void setBackgroundColor(long color) { + if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) { + // don't do anything, no need to allocate ReactBackgroundDrawable for transparent background + } else { + getOrCreateReactViewBackground().setColor(color); + } + } + public int getBackgroundColor() { return mColor; } @@ -65,11 +73,11 @@ public void setBorderWidth(int position, float width) { getOrCreateReactViewBackground().setBorderWidth(position, width); } - public void setBorderColor(int position, float color, float alpha) { - getOrCreateReactViewBackground().setBorderColor(position, color, alpha); + public void setBorderColor(int position, long color) { + getOrCreateReactViewBackground().setBorderColor(position, color); } - public int getBorderColor(int position) { + public long getBorderColor(int position) { return getOrCreateReactViewBackground().getBorderColor(position); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 6bd724ecb3424d..9bce1ea1b01256 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -229,6 +229,14 @@ public void setBackgroundColor(int color) { } } + public void setBackgroundColor(long color) { + if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) { + // don't do anything, no need to allocate ReactBackgroundDrawable for transparent background + } else { + getOrCreateReactViewBackground().setColor(color); + } + } + @Override public void setBackground(Drawable drawable) { throw new UnsupportedOperationException( @@ -310,8 +318,8 @@ public void setBorderWidth(int position, float width) { getOrCreateReactViewBackground().setBorderWidth(position, width); } - public void setBorderColor(int position, float rgb, float alpha) { - getOrCreateReactViewBackground().setBorderColor(position, rgb, alpha); + public void setBorderColor(int position, long color) { + getOrCreateReactViewBackground().setBorderColor(position, color); } public void setBorderRadius(float borderRadius) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index dde735192aafa6..423f0e0ecac312 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -252,11 +252,9 @@ public void setBorderWidth(ReactViewGroup view, int index, float width) { ViewProps.BORDER_BLOCK_START_COLOR }, customType = "Color") - public void setBorderColor(ReactViewGroup view, int index, Integer color) { - float rgbComponent = - color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF); - float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24); - view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); + public void setBorderColor(ReactViewGroup view, int index, Long color) { + long borderColor = color == null ? 0 : color; + view.setBorderColor(SPACING_TYPES[index], borderColor); } @ReactProp(name = ViewProps.COLLAPSABLE) diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index 445e452e1f4d98..cafcc24f12196e 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -1025,11 +1025,11 @@ inline MapBuffer toMapBuffer(const FontVariant& fontVariant) { inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { auto builder = MapBufferBuilder(); if (textAttributes.foregroundColor) { - builder.putInt( + builder.putLong( TA_KEY_FOREGROUND_COLOR, toAndroidRepr(textAttributes.foregroundColor)); } if (textAttributes.backgroundColor) { - builder.putInt( + builder.putLong( TA_KEY_BACKGROUND_COLOR, toAndroidRepr(textAttributes.backgroundColor)); } if (!std::isnan(textAttributes.opacity)) { @@ -1085,7 +1085,7 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { // Decoration if (textAttributes.textDecorationColor) { - builder.putInt( + builder.putLong( TA_KEY_TEXT_DECORATION_COLOR, toAndroidRepr(textAttributes.textDecorationColor)); } @@ -1106,7 +1106,7 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { TA_KEY_TEXT_SHADOW_RADIUS, textAttributes.textShadowRadius); } if (textAttributes.textShadowColor) { - builder.putInt( + builder.putLong( TA_KEY_TEXT_SHADOW_COLOR, toAndroidRepr(textAttributes.textShadowColor)); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/ViewPropsMapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/ViewPropsMapBuffer.cpp index 1fd158c20357af..b2a0091d9ae1ec 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/ViewPropsMapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/ViewPropsMapBuffer.cpp @@ -49,7 +49,7 @@ void ViewProps::propsDiffMapBuffer( } if (oldProps.backgroundColor != newProps.backgroundColor) { - builder.putInt(VP_BG_COLOR, toAndroidRepr(newProps.backgroundColor)); + builder.putLong(VP_BG_COLOR, toAndroidRepr(newProps.backgroundColor)); } if (oldProps.borderCurves != newProps.borderCurves) { @@ -172,7 +172,7 @@ void ViewProps::propsDiffMapBuffer( } if (oldProps.shadowColor != newProps.shadowColor) { - builder.putInt(VP_SHADOW_COLOR, toAndroidRepr(newProps.shadowColor)); + builder.putLong(VP_SHADOW_COLOR, toAndroidRepr(newProps.shadowColor)); } if (oldProps.testId != newProps.testId) { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/viewPropConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/viewPropConversions.h index 0d99ea87f9d93c..3eff6baed2c855 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/viewPropConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/viewPropConversions.h @@ -92,7 +92,7 @@ inline void putOptionalColor( MapBufferBuilder& builder, MapBuffer::Key key, const std::optional& color) { - builder.putInt(key, color.has_value() ? toAndroidRepr(color.value()) : -1); + builder.putLong(key, color.has_value() ? toAndroidRepr(color.value()) : -1); } inline MapBuffer convertBorderColors(const CascadedBorderColors& colors) { diff --git a/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h b/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h index 04c8930e919872..f6b36cf7d2f2b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/core/graphicsConversions.h @@ -47,6 +47,24 @@ inline void fromRawValue( colorComponents.blue = items.at(2); colorComponents.alpha = length == 4 ? items.at(3) : 1.0f; } else { + if (value.hasType>()) { + auto items = (std::unordered_map)value; + if (items.find("space") != items.end()) { + colorComponents.red = (float)items.at("r"); + colorComponents.green = (float)items.at("g"); + colorComponents.blue = (float)items.at("b"); + colorComponents.alpha = (float)items.at("a"); + colorComponents.colorSpace = getDefaultColorSpace(); + std::string space = (std::string)items.at("space"); + if (space == "display-p3") { + colorComponents.colorSpace = ColorSpace::DisplayP3; + } else if (space == "srgb") { + colorComponents.colorSpace = ColorSpace::sRGB; + } + result = colorFromComponents(colorComponents); + return; + } + } result = parsePlatformColor(context, value); return; } @@ -55,11 +73,36 @@ inline void fromRawValue( } #ifdef ANDROID -inline int toAndroidRepr(const SharedColor& color) { - return *color; +inline int64_t toAndroidRepr(const SharedColor& color) { + ColorComponents components = colorComponentsFromColor(color); + if (components.colorSpace == ColorSpace::DisplayP3) { + int ratio = 15360; + int red = static_cast(round(components.red * ratio)) & 0xffff; + int green = static_cast(round(components.green * ratio)) & 0xffff; + int blue = static_cast(round(components.blue * ratio)) & 0xffff; + int alpha = static_cast(round(components.alpha * 0x3ff)) & 0x3ff; + int colorSpace = 7; + int64_t androidColor = (static_cast(red) << 48) | + (static_cast(green) << 32) | + (static_cast(blue) << 16) | + (static_cast(alpha) << 6) | + static_cast(colorSpace); + return androidColor; + } else { + int ratio = 255; + int alpha = static_cast(round(components.alpha * ratio)) & 0xff; + int red = static_cast(round(components.red * ratio)) & 0xff; + int green = static_cast(round(components.green * ratio)) & 0xff; + int blue = static_cast(round(components.blue * ratio)) & 0xff; + int64_t androidColor = (static_cast(alpha) << 56) | + (static_cast(red) << 48) | + (static_cast(green) << 40) | + (static_cast(blue) << 32); + return androidColor; + } } inline folly::dynamic toDynamic(const SharedColor& color) { - return *color; + return toAndroidRepr(color); } #endif diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/Color.h b/packages/react-native/ReactCommon/react/renderer/graphics/Color.h index c3ae69eaa28ed5..7362731338d98f 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/Color.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/Color.h @@ -29,6 +29,8 @@ class SharedColor { SharedColor(Color color) : color_(color) {} + SharedColor(int color) : color_({color, getDefaultColorSpace()}) {} + Color operator*() const { return color_; } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp new file mode 100644 index 00000000000000..e0c6d7fbc569d4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.cpp @@ -0,0 +1,15 @@ +#include "ColorComponents.h" + +namespace facebook::react { + +static ColorSpace defaultColorSpace = ColorSpace::sRGB; + +ColorSpace getDefaultColorSpace() { + return defaultColorSpace; +} + +void setDefaultColorSpace(ColorSpace newColorSpace) { + defaultColorSpace = newColorSpace; +} + +} // namespace facebook::react \ No newline at end of file diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h index 0a3887a3caeb7e..382045ecaa868b 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/ColorComponents.h @@ -9,11 +9,18 @@ namespace facebook::react { +enum class ColorSpace { sRGB, DisplayP3 }; + +// Declare the functions without providing definitions +ColorSpace getDefaultColorSpace(); +void setDefaultColorSpace(ColorSpace newColorSpace); + struct ColorComponents { float red{0}; float green{0}; float blue{0}; float alpha{0}; + ColorSpace colorSpace{getDefaultColorSpace()}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h index 0dd7abe08f05d0..b9fb96424413b4 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h @@ -12,7 +12,18 @@ namespace facebook::react { -using Color = int32_t; +struct Color { + int32_t value; + ColorSpace colorSpace; + + bool operator==(const Color& otherColor) const { + return colorSpace == otherColor.colorSpace && value == otherColor.value; + } + + bool operator!=(const Color& otherColor) const { + return colorSpace != otherColor.colorSpace || value != otherColor.value; + } +}; namespace HostPlatformColor { static const facebook::react::Color UndefinedColor = @@ -21,19 +32,38 @@ static const facebook::react::Color UndefinedColor = inline Color hostPlatformColorFromComponents(ColorComponents components) { float ratio = 255; - return ((int)round(components.alpha * ratio) & 0xff) << 24 | - ((int)round(components.red * ratio) & 0xff) << 16 | - ((int)round(components.green * ratio) & 0xff) << 8 | - ((int)round(components.blue * ratio) & 0xff); + return Color{ + ((int)round(components.alpha * ratio) & 0xff) << 24 | + ((int)round(components.red * ratio) & 0xff) << 16 | + ((int)round(components.green * ratio) & 0xff) << 8 | + ((int)round(components.blue * ratio) & 0xff), + components.colorSpace}; } inline ColorComponents colorComponentsFromHostPlatformColor(Color color) { float ratio = 255; return ColorComponents{ - (float)((color >> 16) & 0xff) / ratio, - (float)((color >> 8) & 0xff) / ratio, - (float)((color >> 0) & 0xff) / ratio, - (float)((color >> 24) & 0xff) / ratio}; + (float)((color.value >> 16) & 0xff) / ratio, + (float)((color.value >> 8) & 0xff) / ratio, + (float)((color.value >> 0) & 0xff) / ratio, + (float)((color.value >> 24) & 0xff) / ratio, + color.colorSpace}; } } // namespace facebook::react + +namespace std { +template <> +struct std::hash { + size_t operator()(const facebook::react::Color& color) const { + size_t h = color.value; + + int colorSpaces[] = {0, 1}; + for (auto s : colorSpaces) { + h ^= hash{}(s); + } + + return h; + }; +}; +} // namespace std diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h index 0dd7abe08f05d0..b9fb96424413b4 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h @@ -12,7 +12,18 @@ namespace facebook::react { -using Color = int32_t; +struct Color { + int32_t value; + ColorSpace colorSpace; + + bool operator==(const Color& otherColor) const { + return colorSpace == otherColor.colorSpace && value == otherColor.value; + } + + bool operator!=(const Color& otherColor) const { + return colorSpace != otherColor.colorSpace || value != otherColor.value; + } +}; namespace HostPlatformColor { static const facebook::react::Color UndefinedColor = @@ -21,19 +32,38 @@ static const facebook::react::Color UndefinedColor = inline Color hostPlatformColorFromComponents(ColorComponents components) { float ratio = 255; - return ((int)round(components.alpha * ratio) & 0xff) << 24 | - ((int)round(components.red * ratio) & 0xff) << 16 | - ((int)round(components.green * ratio) & 0xff) << 8 | - ((int)round(components.blue * ratio) & 0xff); + return Color{ + ((int)round(components.alpha * ratio) & 0xff) << 24 | + ((int)round(components.red * ratio) & 0xff) << 16 | + ((int)round(components.green * ratio) & 0xff) << 8 | + ((int)round(components.blue * ratio) & 0xff), + components.colorSpace}; } inline ColorComponents colorComponentsFromHostPlatformColor(Color color) { float ratio = 255; return ColorComponents{ - (float)((color >> 16) & 0xff) / ratio, - (float)((color >> 8) & 0xff) / ratio, - (float)((color >> 0) & 0xff) / ratio, - (float)((color >> 24) & 0xff) / ratio}; + (float)((color.value >> 16) & 0xff) / ratio, + (float)((color.value >> 8) & 0xff) / ratio, + (float)((color.value >> 0) & 0xff) / ratio, + (float)((color.value >> 24) & 0xff) / ratio, + color.colorSpace}; } } // namespace facebook::react + +namespace std { +template <> +struct std::hash { + size_t operator()(const facebook::react::Color& color) const { + size_t h = color.value; + + int colorSpaces[] = {0, 1}; + for (auto s : colorSpaces) { + h ^= hash{}(s); + } + + return h; + }; +}; +} // namespace std diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp index 8a46d17fa3b0da..5d9c0aa56ef912 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp @@ -61,6 +61,14 @@ int32_t MapBuffer::getInt(Key key) const { bytes_.data() + valueOffset(bucketIndex)); } +int64_t MapBuffer::getLong(Key key) const { + auto bucketIndex = getKeyBucket(key); + react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer"); + + return *reinterpret_cast( + bytes_.data() + valueOffset(bucketIndex)); +} + bool MapBuffer::getBool(Key key) const { return getInt(key) != 0; } diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index 1efe45cf063114..968747ee64fb71 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -104,6 +104,7 @@ class MapBuffer { Double = 2, String = 3, Map = 4, + Long = 5, }; explicit MapBuffer(std::vector data); @@ -118,6 +119,8 @@ class MapBuffer { int32_t getInt(MapBuffer::Key key) const; + int64_t getLong(MapBuffer::Key key) const; + bool getBool(MapBuffer::Key key) const; double getDouble(MapBuffer::Key key) const; diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index e2ca2d57f43f96..42d87a41a6f25a 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -15,6 +15,7 @@ namespace facebook::react { constexpr uint32_t INT_SIZE = sizeof(uint32_t); constexpr uint32_t DOUBLE_SIZE = sizeof(double); constexpr uint32_t MAX_BUCKET_VALUE_SIZE = sizeof(uint64_t); +constexpr uint64_t LONG_SIZE = sizeof(uint64_t); MapBuffer MapBufferBuilder::EMPTY() { return MapBufferBuilder(0).build(); @@ -76,6 +77,14 @@ void MapBufferBuilder::putInt(MapBuffer::Key key, int32_t value) { INT_SIZE); } +void MapBufferBuilder::putLong(MapBuffer::Key key, int64_t value) { + storeKeyValue( + key, + MapBuffer::DataType::Long, + reinterpret_cast(&value), + LONG_SIZE); +} + void MapBufferBuilder::putString(MapBuffer::Key key, const std::string& value) { auto strSize = value.size(); const char* strData = value.data(); diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h index 0b26e915aeac35..2260e690a2d785 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h @@ -27,6 +27,8 @@ class MapBufferBuilder { void putInt(MapBuffer::Key key, int32_t value); + void putLong(MapBuffer::Key key, int64_t value); + void putBool(MapBuffer::Key key, bool value); void putDouble(MapBuffer::Key key, double value); diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextPrimitivesConversions.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextPrimitivesConversions.h index 1921d1ca1cffed..2ce773330bae1b 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextPrimitivesConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextPrimitivesConversions.h @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +#import #import #include @@ -113,5 +114,9 @@ inline static UIColor *RCTUIColorFromSharedColor(const facebook::react::SharedCo } auto components = facebook::react::colorComponentsFromColor(sharedColor); - return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + return [RCTConvert createColorFrom:components.red + green:components.green + blue:components.blue + alpha:components.alpha + andColorSpace:(RCTColorSpace)components.colorSpace]; } diff --git a/packages/rn-tester/js/examples/Animated/ColorStylesExample.js b/packages/rn-tester/js/examples/Animated/ColorStylesExample.js index 548e0fd7dc234a..d21c0ec7fc68ea 100644 --- a/packages/rn-tester/js/examples/Animated/ColorStylesExample.js +++ b/packages/rn-tester/js/examples/Animated/ColorStylesExample.js @@ -20,12 +20,14 @@ function AnimatedView({useNativeDriver}: {useNativeDriver: boolean}) { const animations = []; const animatedViewStyle = { - backgroundColor: new Animated.Color('blue'), + // backgroundColor: new Animated.Color('blue'), + backgroundColor: new Animated.Color('color(display-p3 0 0 1)'), borderColor: new Animated.Color('orange'), }; animations.push( Animated.timing(animatedViewStyle.backgroundColor, { - toValue: new Animated.Color('red'), + // toValue: new Animated.Color('red'), + toValue: new Animated.Color('color(display-p3 1 0 0)'), duration: 1000, useNativeDriver, }), @@ -42,7 +44,8 @@ function AnimatedView({useNativeDriver}: {useNativeDriver: boolean}) { const interpolationAnimatedStyle = { backgroundColor: animatedBaseValue.interpolate({ inputRange: [0, 1], - outputRange: ['blue', 'red'], + // outputRange: ['blue', 'red'], + outputRange: ['color(display-p3 0 0 1)', 'color(display-p3 1 0 0)'], }), borderColor: animatedBaseValue.interpolate({ inputRange: [0, 1], @@ -58,11 +61,13 @@ function AnimatedView({useNativeDriver}: {useNativeDriver: boolean}) { ); const animatedFirstSpanTextStyle = { - color: new Animated.Color('blue'), + // color: new Animated.Color('blue'), + color: new Animated.Color('color(display-p3 0 0 1)'), }; animations.push( Animated.timing(animatedFirstSpanTextStyle.color, { - toValue: new Animated.Color('red'), + // toValue: new Animated.Color('red'), + toValue: new Animated.Color('color(display-p3 1 0 0)'), duration: 1000, useNativeDriver, }), @@ -80,11 +85,13 @@ function AnimatedView({useNativeDriver}: {useNativeDriver: boolean}) { ); const animatedImageStyle = { - tintColor: new Animated.Color('blue'), + // tintColor: new Animated.Color('blue'), + tintColor: new Animated.Color('color(display-p3 0 0 1)'), }; animations.push( Animated.timing(animatedImageStyle.tintColor, { - toValue: new Animated.Color('red'), + // toValue: new Animated.Color('red'), + toValue: new Animated.Color('color(display-p3 1 0 0)'), duration: 1000, useNativeDriver, }), @@ -114,16 +121,18 @@ function AnimatedView({useNativeDriver}: {useNativeDriver: boolean}) { fox jumps over the lazy dog - + + + ); } function AnimatedColorStyleExample(): React.Node { - const [useNativeDriver, setUseNativeDriver] = React.useState(false); + const [useNativeDriver, setUseNativeDriver] = React.useState(true); return ( diff --git a/packages/rn-tester/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index 274937e2ade16b..07bd8cc72c417a 100644 --- a/packages/rn-tester/js/examples/View/ViewExample.js +++ b/packages/rn-tester/js/examples/View/ViewExample.js @@ -10,8 +10,6 @@ 'use strict'; -import type {RNTesterModule} from '../../types/RNTesterTypes'; - import * as React from 'react'; import {Platform, Pressable, StyleSheet, Text, View} from 'react-native'; @@ -393,11 +391,35 @@ export default ({ name: 'background-color', render(): React.Node { return ( - - Blue background - + <> + + Blue background + + + + sRGB Red background + + + + + displayP3 Red background + + + ); }, },