Skip to content

Commit

Permalink
[iOS] Add Fabric dynamic color support
Browse files Browse the repository at this point in the history
  • Loading branch information
zhongwuzw committed Jan 2, 2024
1 parent d45a01d commit cdf65db
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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];
Expand All @@ -640,7 +652,7 @@ - (void)invalidateLayer
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
borderColors,
_backgroundColor.CGColor,
backgroundColor,
self.clipsToBounds);

RCTReleaseRCTBorderColors(borderColors);
Expand Down
48 changes: 48 additions & 0 deletions packages/react-native/React/Fabric/RCTConversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

#import <UIKit/UIKit.h>

#import <React/RCTConvert.h>
#import <react/renderer/components/view/AccessibilityPrimitives.h>
#import <react/renderer/components/view/primitives.h>
#import <react/renderer/core/LayoutPrimitives.h>
#import <react/renderer/graphics/Color.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/renderer/graphics/Transform.h>

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -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) {
Expand All @@ -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];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,108 @@
#pragma once

#include <react/renderer/graphics/ColorComponents.h>
#include <react/utils/hash_combine.h>
#include <cmath>

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<facebook::react::Color>::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<facebook::react::Color> {
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<facebook::react::DynamicColor> {
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;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ inline SharedColor parsePlatformColor(
auto semanticItems = (std::vector<std::string>)items.at("semantic");
return {colorFromComponents(
RCTPlatformColorComponentsFromSemanticItems(semanticItems))};
} else if (
items.find("dynamic") != items.end() &&
items.at("dynamic")
.hasType<std::unordered_map<std::string, RawValue>>()) {
auto dynamicItems =
(std::unordered_map<std::string, RawValue>)items.at("dynamic");
return RCTPlatformColorComponentsFromDynamicItems(context, dynamicItems);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/

#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/ColorComponents.h>
#include <vector>

facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(
std::vector<std::string>& semanticItems);

facebook::react::SharedColor RCTPlatformColorComponentsFromDynamicItems(
const facebook::react::PropsParserContext& context,
std::unordered_map<std::string, facebook::react::RawValue>& dynamicItems);
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <react/renderer/core/graphicsConversions.h>

#include <string>

using namespace facebook::react;

NS_ASSUME_NONNULL_BEGIN

static NSString *const kColorSuffix = @"Color";
Expand Down Expand Up @@ -200,4 +203,33 @@
return {0, 0, 0, 0};
}

facebook::react::SharedColor RCTPlatformColorComponentsFromDynamicItems(
const facebook::react::PropsParserContext &context,
std::unordered_map<std::string, facebook::react::RawValue> &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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import <UIKit/UIKit.h>

#import <React/RCTConvert.h>
#include <react/renderer/textlayoutmanager/RCTFontProperties.h>
#include <react/renderer/textlayoutmanager/RCTFontUtils.h>

Expand Down Expand Up @@ -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) {
Expand All @@ -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];
}

0 comments on commit cdf65db

Please sign in to comment.