diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 6751018c889162..6596bdcee1f997 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -91,6 +91,15 @@ - (void)setBackgroundColor:(UIColor *)backgroundColor _backgroundColor = backgroundColor; } +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection +{ + [super traitCollectionDidChange:previousTraitCollection]; + + if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) { + [self invalidateLayer]; + } +} + #pragma mark - RCTComponentViewProtocol + (ComponentDescriptorProvider)componentDescriptorProvider @@ -602,6 +611,9 @@ - (void)invalidateLayer borderMetrics.borderWidths.left == 0 || colorComponentsFromColor(borderMetrics.borderColors.left).alpha == 0 || self.clipsToBounds); + CGColorRef backgroundColor; + backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; + if (useCoreAnimationBorderRendering) { layer.mask = nil; if (_borderLayer) { @@ -617,7 +629,7 @@ - (void)invalidateLayer layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); - layer.backgroundColor = _backgroundColor.CGColor; + layer.backgroundColor = backgroundColor; } else { if (!_borderLayer) { _borderLayer = [CALayer new]; @@ -640,7 +652,7 @@ - (void)invalidateLayer RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - _backgroundColor.CGColor, + backgroundColor, self.clipsToBounds); RCTReleaseRCTBorderColors(borderColors); diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index d8ed955da44bc6..cb8718f700c7ea 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -7,10 +7,12 @@ #import +#import #import #import #import #import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -34,6 +36,43 @@ inline std::string RCTStringFromNSString(NSString *string) return std::string{string.UTF8String ?: ""}; } +inline UIColor *_Nullable RCTUIColorFromDynamicColor(const facebook::react::Color &color) +{ + auto dynamicColor = color.getDynamicColor(); + int32_t light = dynamicColor.lightColor; + int32_t dark = dynamicColor.darkColor; + int32_t highContrastLight = dynamicColor.highContrastLightColor; + int32_t highContrastDark = dynamicColor.highContrastDarkColor; + + UIColor *lightColor = [RCTConvert UIColor:@(light)]; + UIColor *darkColor = [RCTConvert UIColor:@(dark)]; + UIColor *highContrastLightColor = [RCTConvert UIColor:@(highContrastLight)]; + UIColor *highContrastDarkColor = [RCTConvert UIColor:@(highContrastDark)]; + + if (lightColor != nil && darkColor != nil) { + UIColor *color = [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull collection) { + if (collection.userInterfaceStyle == UIUserInterfaceStyleDark) { + if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastDarkColor != nil) { + return highContrastDarkColor; + } else { + return darkColor; + } + } else { + if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastLightColor != nil) { + return highContrastLightColor; + } else { + return lightColor; + } + } + }]; + return color; + } else { + return nil; + } + + return nil; +} + inline UIColor *_Nullable RCTUIColorFromSharedColor(const facebook::react::SharedColor &sharedColor) { if (!sharedColor) { @@ -52,6 +91,15 @@ inline UIColor *_Nullable RCTUIColorFromSharedColor(const facebook::react::Share return [UIColor whiteColor]; } + facebook::react::Color color = *sharedColor; + switch (color.getKind()) { + case facebook::react::DynamicKind: + return RCTUIColorFromDynamicColor(color); + case facebook::react::UndefinedKind: + case facebook::react::ComponentsKind: + break; + } + auto components = facebook::react::colorComponentsFromColor(sharedColor); return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; } 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..70eb09b980356c 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 @@ -8,32 +8,108 @@ #pragma once #include +#include #include namespace facebook::react { -using Color = int32_t; +enum ColorKind { UndefinedKind, ComponentsKind, DynamicKind }; + +struct DynamicColor { + int32_t lightColor = 0; + int32_t darkColor = 0; + int32_t highContrastLightColor = 0; + int32_t highContrastDarkColor = 0; + + bool operator==(const DynamicColor& other) const { + return lightColor == other.lightColor && darkColor == other.darkColor && + highContrastLightColor == other.highContrastLightColor && + highContrastDarkColor == other.highContrastDarkColor; + } +}; + +class Color { + public: + Color() : Color(UndefinedKind) {} + Color(DynamicColor dynamicColor) + : kind_(DynamicKind), + dynamicColor_(dynamicColor), + color_(dynamicColor.lightColor) {} + Color(ColorComponents components) { + float ratio = 255; + kind_ = ComponentsKind; + 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); + } + int32_t getColor() const { + return color_; + } + ColorKind getKind() const { + return kind_; + } + DynamicColor getDynamicColor() const { + return dynamicColor_; + } + bool operator==(const Color& other) const { + return kind_ == other.kind_ && color_ == other.color_ && + dynamicColor_ == other.dynamicColor_; + } + bool operator!=(const Color& other) const { + return kind_ != other.kind_ || color_ != other.color_ || + dynamicColor_ != other.dynamicColor_; + } + + private: + Color(ColorKind kind) : kind_(kind), color_(0) {} + ColorKind kind_; + int32_t color_ = 0; + DynamicColor dynamicColor_; +}; namespace HostPlatformColor { -static const facebook::react::Color UndefinedColor = - std::numeric_limits::max(); +static const facebook::react::Color UndefinedColor = Color(); } 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(components); } inline ColorComponents colorComponentsFromHostPlatformColor(Color color) { float ratio = 255; + int32_t primitiveColor = color.getColor(); return ColorComponents{ - (float)((color >> 16) & 0xff) / ratio, - (float)((color >> 8) & 0xff) / ratio, - (float)((color >> 0) & 0xff) / ratio, - (float)((color >> 24) & 0xff) / ratio}; + (float)((primitiveColor >> 16) & 0xff) / ratio, + (float)((primitiveColor >> 8) & 0xff) / ratio, + (float)((primitiveColor >> 0) & 0xff) / ratio, + (float)((primitiveColor >> 24) & 0xff) / ratio}; } } // namespace facebook::react + +template <> +struct std::hash { + size_t operator()(facebook::react::Color color) const { + auto seed = size_t{0}; + facebook::react::hash_combine(seed, color.getKind(), color.getColor()); + if (color.getKind() == facebook::react::DynamicKind) { + facebook::react::hash_combine(seed, color.getDynamicColor()); + } + return seed; + } +}; + +template <> +struct std::hash { + size_t operator()(facebook::react::DynamicColor dynamicColor) const { + auto seed = size_t{0}; + facebook::react::hash_combine( + seed, + dynamicColor.lightColor, + dynamicColor.darkColor, + dynamicColor.highContrastLightColor, + dynamicColor.highContrastDarkColor); + return seed; + } +}; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h index 32bcc883f468e9..3429d2d3648e0a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h @@ -25,6 +25,13 @@ inline SharedColor parsePlatformColor( auto semanticItems = (std::vector)items.at("semantic"); return {colorFromComponents( RCTPlatformColorComponentsFromSemanticItems(semanticItems))}; + } else if ( + items.find("dynamic") != items.end() && + items.at("dynamic") + .hasType>()) { + auto dynamicItems = + (std::unordered_map)items.at("dynamic"); + return RCTPlatformColorComponentsFromDynamicItems(context, dynamicItems); } } 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 397fcde6b59954..126907f99b0d7d 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 @@ -5,8 +5,15 @@ * LICENSE file in the root directory of this source tree. */ +#include +#include +#include #include #include facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems( std::vector& semanticItems); + +facebook::react::SharedColor RCTPlatformColorComponentsFromDynamicItems( + const facebook::react::PropsParserContext& context, + std::unordered_map& dynamicItems); 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 1d566ac8d6c440..5546fa12c5d9da 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,9 +9,12 @@ #import #import +#include #include +using namespace facebook::react; + NS_ASSUME_NONNULL_BEGIN static NSString *const kColorSuffix = @"Color"; @@ -200,4 +203,33 @@ return {0, 0, 0, 0}; } +facebook::react::SharedColor RCTPlatformColorComponentsFromDynamicItems( + const facebook::react::PropsParserContext &context, + std::unordered_map &dynamicItems) +{ + SharedColor lightSharedColor{}; + SharedColor darkSharedColor{}; + SharedColor highContrastLightSharedColor{}; + SharedColor highContrastDarkSharedColor{}; + if (dynamicItems.count("light")) { + fromRawValue(context, dynamicItems.at("light"), lightSharedColor); + } + if (dynamicItems.count("dark")) { + fromRawValue(context, dynamicItems.at("dark"), darkSharedColor); + } + if (dynamicItems.count("highContrastLight")) { + fromRawValue(context, dynamicItems.at("highContrastLight"), highContrastLightSharedColor); + } + if (dynamicItems.count("highContrastDark")) { + fromRawValue(context, dynamicItems.at("highContrastDark"), highContrastDarkSharedColor); + } + + Color color = Color(DynamicColor{ + (*lightSharedColor).getColor(), + (*darkSharedColor).getColor(), + (*highContrastLightSharedColor).getColor(), + (*highContrastDarkSharedColor).getColor()}); + return SharedColor(color); +} + NS_ASSUME_NONNULL_END 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..0e021cf272ac62 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 @@ -7,6 +7,7 @@ #import +#import #include #include @@ -94,6 +95,43 @@ inline static NSUnderlineStyle RCTNSUnderlineStyleFromTextDecorationStyle( } } +inline UIColor *_Nullable RCTUIColorFromDynamicColor(const facebook::react::Color &color) +{ + auto dynamicColor = color.getDynamicColor(); + int32_t light = dynamicColor.lightColor; + int32_t dark = dynamicColor.darkColor; + int32_t highContrastLight = dynamicColor.highContrastLightColor; + int32_t highContrastDark = dynamicColor.highContrastDarkColor; + + UIColor *lightColor = [RCTConvert UIColor:@(light)]; + UIColor *darkColor = [RCTConvert UIColor:@(dark)]; + UIColor *highContrastLightColor = [RCTConvert UIColor:@(highContrastLight)]; + UIColor *highContrastDarkColor = [RCTConvert UIColor:@(highContrastDark)]; + + if (lightColor != nil && darkColor != nil) { + UIColor *color = [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull collection) { + if (collection.userInterfaceStyle == UIUserInterfaceStyleDark) { + if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastDarkColor != nil) { + return highContrastDarkColor; + } else { + return darkColor; + } + } else { + if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastLightColor != nil) { + return highContrastLightColor; + } else { + return lightColor; + } + } + }]; + return color; + } else { + return nil; + } + + return nil; +} + inline static UIColor *RCTUIColorFromSharedColor(const facebook::react::SharedColor &sharedColor) { if (!sharedColor) { @@ -112,6 +150,15 @@ inline static UIColor *RCTUIColorFromSharedColor(const facebook::react::SharedCo return [UIColor whiteColor]; } + facebook::react::Color color = *sharedColor; + switch (color.getKind()) { + case facebook::react::DynamicKind: + return RCTUIColorFromDynamicColor(color); + case facebook::react::UndefinedKind: + case facebook::react::ComponentsKind: + break; + } + auto components = facebook::react::colorComponentsFromColor(sharedColor); return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; }