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 377e9aac6dbbe6..7562b4a396e7ae 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 @@ -24,8 +24,12 @@ struct Color { Color(int32_t color); Color(const DynamicColor& dynamicColor); Color(const ColorComponents& components); - Color(std::shared_ptr uiColor); + Color() : uiColor_(nullptr){}; int32_t getColor() const; + int32_t getUIColorHash() const; + + static Color createSemanticColor(std::vector& semanticItems); + std::shared_ptr getUIColor() const { return uiColor_; } @@ -48,6 +52,7 @@ struct Color { } private: + Color(std::shared_ptr uiColor); std::shared_ptr uiColor_; }; @@ -59,7 +64,7 @@ namespace HostPlatformColor { #define NO_DESTROY #endif -NO_DESTROY static const facebook::react::Color UndefinedColor = Color(nullptr); +NO_DESTROY static const facebook::react::Color UndefinedColor = Color(); } // namespace HostPlatformColor inline Color @@ -103,8 +108,6 @@ inline float blueFromHostPlatformColor(Color color) { template <> struct std::hash { size_t operator()(const facebook::react::Color& color) const { - auto seed = size_t{0}; - facebook::react::hash_combine(seed, color.getColor()); - return seed; + return color.getUIColorHash(); } }; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index 2a80746fa0b5a6..fbfeba1b1b00ba 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -6,9 +6,11 @@ */ #import "HostPlatformColor.h" +#import "UIColor+Graphics.h" #import #import +#import #import #import @@ -19,13 +21,31 @@ namespace facebook::react { namespace { + +bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) +{ + UIColor *color = unwrapManagedObject(uiColor); + CGColorSpaceRef colorSpace = CGColorGetColorSpace(color.CGColor); + + if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB) { + CFStringRef name = CGColorSpaceGetName(colorSpace); + if (name != NULL && CFEqual(name, kCGColorSpaceDisplayP3)) { + return true; + } + } + return false; +} + UIColor *_Nullable UIColorFromInt32(int32_t intColor) { CGFloat a = CGFloat((intColor >> 24) & 0xFF) / 255.0; CGFloat r = CGFloat((intColor >> 16) & 0xFF) / 255.0; CGFloat g = CGFloat((intColor >> 8) & 0xFF) / 255.0; CGFloat b = CGFloat(intColor & 0xFF) / 255.0; - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + + UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a]; + color.reactHash = facebook::react::hash_combine(intColor, 0); + return color; } UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor) @@ -56,6 +76,7 @@ } } }]; + color.reactHash = facebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0); return color; } else { return nil; @@ -64,37 +85,95 @@ return nil; } -int32_t ColorFromUIColor(UIColor *color) +int32_t ColorFromColorComponents(const facebook::react::ColorComponents &components) { float ratio = 255; + auto color = ((int32_t)round((float)components.alpha * ratio) & 0xff) << 24 | + ((int)round((float)components.red * ratio) & 0xff) << 16 | + ((int)round((float)components.green * ratio) & 0xff) << 8 | ((int)round((float)components.blue * ratio) & 0xff); + return color; +} + +int32_t ColorFromUIColor(UIColor *color) +{ CGFloat rgba[4]; [color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]]; - return ((int32_t)round((float)rgba[3] * ratio) & 0xff) << 24 | ((int)round((float)rgba[0] * ratio) & 0xff) << 16 | - ((int)round((float)rgba[1] * ratio) & 0xff) << 8 | ((int)round((float)rgba[2] * ratio) & 0xff); + return ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]}); } -int32_t ColorFromUIColor(const std::shared_ptr &uiColor) +int32_t ColorFromUIColorForSpecificTraitCollection( + const std::shared_ptr &uiColor, + UITraitCollection *traitCollection) { UIColor *color = (UIColor *)unwrapManagedObject(uiColor); if (color) { - UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; - color = [color resolvedColorWithTraitCollection:currentTraitCollection]; + color = [color resolvedColorWithTraitCollection:traitCollection]; return ColorFromUIColor(color); } return 0; } +int32_t ColorFromUIColor(const std::shared_ptr &uiColor) +{ + return ColorFromUIColorForSpecificTraitCollection(uiColor, [UITraitCollection currentTraitCollection]); +} + UIColor *_Nullable UIColorFromComponentsColor(const facebook::react::ColorComponents &components) { + UIColor *uiColor = nil; if (components.colorSpace == ColorSpace::DisplayP3) { - return [UIColor colorWithDisplayP3Red:components.red - green:components.green - blue:components.blue - alpha:components.alpha]; + uiColor = [UIColor colorWithDisplayP3Red:components.red + green:components.green + blue:components.blue + alpha:components.alpha]; + } else { + uiColor = [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + } + + auto color = ColorFromColorComponents(components); + uiColor.reactHash = facebook::react::hash_combine(color, components.colorSpace == ColorSpace::DisplayP3); + + return uiColor; +} + +int32_t hashFromUIColor(const std::shared_ptr &uiColor) +{ + if (uiColor == nullptr) { + return 0; } - return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + + static UITraitCollection *darkModeTraitCollection = + [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]; + auto darkColor = ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeTraitCollection); + + static UITraitCollection *lightModeTraitCollection = + [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]; + auto lightColor = ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeTraitCollection); + + static UITraitCollection *darkModeAccessibilityContrastTraitCollection = + [UITraitCollection traitCollectionWithTraitsFromCollections:@[ + darkModeTraitCollection, + [UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh] + ]]; + auto darkAccessibilityContrastColor = + ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeAccessibilityContrastTraitCollection); + + static UITraitCollection *lightModeAccessibilityContrastTraitCollection = + [UITraitCollection traitCollectionWithTraitsFromCollections:@[ + lightModeTraitCollection, + [UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh] + ]]; + auto lightAccessibilityContrastColor = + ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeAccessibilityContrastTraitCollection); + return facebook::react::hash_combine( + darkColor, + lightColor, + darkAccessibilityContrastColor, + lightAccessibilityContrastColor, + UIColorIsP3ColorSpace(uiColor)); } + } // anonymous namespace Color::Color(int32_t color) @@ -114,6 +193,11 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) Color::Color(std::shared_ptr uiColor) { + UIColor *color = ((UIColor *)unwrapManagedObject(uiColor)); + if (color && color.reactHash == 0) { + auto colorHash = hashFromUIColor(uiColor); + color.reactHash = colorHash; + } uiColor_ = std::move(uiColor); } @@ -121,7 +205,8 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) { return (!uiColor_ && !other.uiColor_) || (uiColor_ && other.uiColor_ && - [unwrapManagedObject(getUIColor()) isEqual:unwrapManagedObject(other.getUIColor())]); + ((UIColor *)unwrapManagedObject(getUIColor())).reactHash == + ((UIColor *)unwrapManagedObject(other.getUIColor())).reactHash); } bool Color::operator!=(const Color &other) const @@ -142,6 +227,17 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) return static_cast(rgba[channelId]); } +int32_t Color::getUIColorHash() const +{ + return [(UIColor *)unwrapManagedObject(uiColor_) reactHash]; +} + +Color Color::createSemanticColor(std::vector &semanticItems) +{ + auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems); + return Color(wrapManagedObject(semanticColor)); +} + } // namespace facebook::react NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm index 7b32e6ee915b95..7cb04798066724 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm @@ -56,7 +56,7 @@ SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t auto items = (std::unordered_map)value; if (items.find("semantic") != items.end() && items.at("semantic").hasType>()) { auto semanticItems = (std::vector)items.at("semantic"); - return {wrapManagedObject(RCTPlatformColorFromSemanticItems(semanticItems))}; + return SharedColor(Color::createSemanticColor(semanticItems)); } else if ( items.find("dynamic") != items.end() && items.at("dynamic").hasType>()) { diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h index 6d2689e8918c76..a45743a234907c 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h @@ -8,9 +8,15 @@ #pragma once #import -#import #import +namespace facebook { +namespace react { +struct ColorComponents; +struct Color; +} // namespace react +} // namespace facebook + facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems( std::vector& semanticItems); UIColor* RCTPlatformColorFromSemanticItems( diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm index 7f12a3ab00c9b4..3e037710add58a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm @@ -9,6 +9,7 @@ #import #import +#import #import #include diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h new file mode 100644 index 00000000000000..da2038a1f6906a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +#pragma once + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIColor (Graphics) +@property (nonatomic, assign) int32_t reactHash; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm new file mode 100644 index 00000000000000..4a60e985943aa0 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import "UIColor+Graphics.h" + +@implementation UIColor (Graphics) + +- (int32_t)reactHash +{ + return [objc_getAssociatedObject(self, _cmd) intValue]; +} + +- (void)setReactHash:(int32_t)reactHash +{ + objc_setAssociatedObject(self, @selector(reactHash), @(reactHash), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end