first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -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.
*/
#include "BackgroundImage.h"
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic(const BackgroundImage& backgroundImage) {
if (std::holds_alternative<LinearGradient>(backgroundImage)) {
return std::get<LinearGradient>(backgroundImage).toDynamic();
} else if (std::holds_alternative<RadialGradient>(backgroundImage)) {
return std::get<RadialGradient>(backgroundImage).toDynamic();
}
return folly::dynamic(nullptr);
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,45 @@
/*
* 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
#include <react/renderer/graphics/LinearGradient.h>
#include <react/renderer/graphics/RadialGradient.h>
namespace facebook::react {
using BackgroundImage = std::variant<LinearGradient, RadialGradient>;
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic(const BackgroundImage &backgroundImage);
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
inline std::string toString(std::vector<BackgroundImage> &value)
{
std::stringstream ss;
ss << "[";
for (size_t i = 0; i < value.size(); i++) {
if (i > 0) {
ss << ", ";
}
const auto &backgroundImage = value[i];
if (std::holds_alternative<LinearGradient>(backgroundImage)) {
std::get<LinearGradient>(backgroundImage).toString(ss);
} else if (std::holds_alternative<RadialGradient>(backgroundImage)) {
std::get<RadialGradient>(backgroundImage).toString(ss);
}
}
ss << "]";
return ss.str();
}
#endif
}; // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* 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
#include <react/renderer/graphics/ValueUnit.h>
#include <optional>
namespace facebook::react {
struct BackgroundPosition {
std::optional<ValueUnit> top;
std::optional<ValueUnit> left;
std::optional<ValueUnit> right;
std::optional<ValueUnit> bottom;
BackgroundPosition() : top(ValueUnit{0.0f, UnitType::Point}), left(ValueUnit{0.0f, UnitType::Point}) {}
bool operator==(const BackgroundPosition &other) const = default;
bool operator!=(const BackgroundPosition &other) const = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* 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
namespace facebook::react {
enum class BackgroundRepeatStyle {
Repeat,
Space,
Round,
NoRepeat,
};
struct BackgroundRepeat {
BackgroundRepeatStyle x{BackgroundRepeatStyle::Repeat};
BackgroundRepeatStyle y{BackgroundRepeatStyle::Repeat};
BackgroundRepeat() {}
bool operator==(const BackgroundRepeat &other) const = default;
bool operator!=(const BackgroundRepeat &other) const = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* 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
#include <react/renderer/graphics/ValueUnit.h>
#include <variant>
namespace facebook::react {
struct BackgroundSizeLengthPercentage {
std::variant<std::monostate, ValueUnit> x;
std::variant<std::monostate, ValueUnit> y;
BackgroundSizeLengthPercentage() : x(std::monostate{}), y(std::monostate{}) {}
bool isXAuto() const
{
return std::holds_alternative<std::monostate>(x);
}
bool isYAuto() const
{
return std::holds_alternative<std::monostate>(y);
}
bool operator==(const BackgroundSizeLengthPercentage &other) const = default;
bool operator!=(const BackgroundSizeLengthPercentage &other) const = default;
};
enum class BackgroundSizeKeyword { Cover, Contain };
// https://www.w3.org/TR/css-backgrounds-3/#background-size
using BackgroundSize = std::variant<BackgroundSizeKeyword, BackgroundSizeLengthPercentage>;
} // namespace facebook::react

View File

@@ -0,0 +1,110 @@
/*
* 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
#include <optional>
#include <string_view>
namespace facebook::react {
enum class BlendMode {
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
};
inline std::optional<BlendMode> blendModeFromString(std::string_view blendModeName)
{
if (blendModeName == "normal") {
return BlendMode::Normal;
} else if (blendModeName == "multiply") {
return BlendMode::Multiply;
} else if (blendModeName == "screen") {
return BlendMode::Screen;
} else if (blendModeName == "overlay") {
return BlendMode::Overlay;
} else if (blendModeName == "darken") {
return BlendMode::Darken;
} else if (blendModeName == "lighten") {
return BlendMode::Lighten;
} else if (blendModeName == "color-dodge") {
return BlendMode::ColorDodge;
} else if (blendModeName == "color-burn") {
return BlendMode::ColorBurn;
} else if (blendModeName == "hard-light") {
return BlendMode::HardLight;
} else if (blendModeName == "soft-light") {
return BlendMode::SoftLight;
} else if (blendModeName == "difference") {
return BlendMode::Difference;
} else if (blendModeName == "exclusion") {
return BlendMode::Exclusion;
} else if (blendModeName == "hue") {
return BlendMode::Hue;
} else if (blendModeName == "saturation") {
return BlendMode::Saturation;
} else if (blendModeName == "color") {
return BlendMode::Color;
} else if (blendModeName == "luminosity") {
return BlendMode::Luminosity;
} else {
return std::nullopt;
}
}
inline std::string toString(const BlendMode &blendMode)
{
switch (blendMode) {
case BlendMode::Normal:
return "normal";
case BlendMode::Multiply:
return "multiply";
case BlendMode::Screen:
return "screen";
case BlendMode::Overlay:
return "overlay";
case BlendMode::Darken:
return "darken";
case BlendMode::Lighten:
return "lighten";
case BlendMode::ColorDodge:
return "color-dodge";
case BlendMode::ColorBurn:
return "color-burn";
case BlendMode::HardLight:
return "hard-light";
case BlendMode::SoftLight:
return "soft-light";
case BlendMode::Difference:
return "difference";
case BlendMode::Exclusion:
return "exclusion";
case BlendMode::Hue:
return "hue";
case BlendMode::Saturation:
return "saturation";
case BlendMode::Color:
return "color";
case BlendMode::Luminosity:
return "luminosity";
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,47 @@
/*
* 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
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
struct BoxShadow {
bool operator==(const BoxShadow &other) const = default;
Float offsetX{};
Float offsetY{};
Float blurRadius{};
Float spreadDistance{};
SharedColor color{};
bool inset{};
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const
{
folly::dynamic result = folly::dynamic::object();
result["offsetX"] = offsetX;
result["offsetY"] = offsetY;
result["blurRadius"] = blurRadius;
result["spreadDistance"] = spreadDistance;
result["color"] = *color;
result["inset"] = inset;
return result;
}
#endif
};
#ifdef RN_SERIALIZABLE_STATE
inline folly::dynamic toDynamic(const BoxShadow &boxShadow)
{
return boxShadow.toDynamic();
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
react_native_android_selector(platform_SRC
platform/android/react/renderer/graphics/*.cpp
platform/cxx/react/renderer/graphics/*.cpp)
file(GLOB react_renderer_graphics_SRC CONFIGURE_DEPENDS *.cpp ${platform_SRC})
add_library(react_renderer_graphics OBJECT ${react_renderer_graphics_SRC})
react_native_android_selector(platform_DIR
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/)
target_include_directories(react_renderer_graphics
PUBLIC
${REACT_COMMON_DIR}
${platform_DIR}
)
react_native_android_selector(fbjni fbjni "")
target_link_libraries(react_renderer_graphics
glog
${fbjni}
folly_runtime
react_debug
react_utils
)
target_compile_reactnative_options(react_renderer_graphics PRIVATE)
target_compile_options(react_renderer_graphics PRIVATE -Wpedantic)

View File

@@ -0,0 +1,89 @@
/*
* 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.
*/
#include "Color.h"
#include <array>
namespace facebook::react {
std::string SharedColor::toString() const noexcept {
ColorComponents components = colorComponentsFromColor(*this);
std::array<char, 255> buffer{};
std::snprintf(
buffer.data(),
buffer.size(),
"rgba(%.0f, %.0f, %.0f, %g)",
components.red * 255.f,
components.green * 255.f,
components.blue * 255.f,
components.alpha);
return buffer.data();
}
bool isColorMeaningful(const SharedColor& color) noexcept {
if (!color) {
return false;
}
return hostPlatformColorIsColorMeaningful(*color);
}
// Create Color from float RGBA values in [0, 1] range
SharedColor colorFromComponents(ColorComponents components) {
return {hostPlatformColorFromComponents(components)};
}
// Read Color components in [0, 1] range
ColorComponents colorComponentsFromColor(SharedColor sharedColor) {
return colorComponentsFromHostPlatformColor(*sharedColor);
}
// Read alpha channel in [0, 255] range
uint8_t alphaFromColor(SharedColor color) noexcept {
return static_cast<uint8_t>(std::round(alphaFromHostPlatformColor(*color)));
}
// Read red channel in [0, 255] range
uint8_t redFromColor(SharedColor color) noexcept {
return static_cast<uint8_t>(std::round(redFromHostPlatformColor(*color)));
}
// Read green channel in [0, 255] range
uint8_t greenFromColor(SharedColor color) noexcept {
return static_cast<uint8_t>(std::round(greenFromHostPlatformColor(*color)));
}
// Read blue channel in [0, 255] range
uint8_t blueFromColor(SharedColor color) noexcept {
return static_cast<uint8_t>(std::round(blueFromHostPlatformColor(*color)));
}
// Create Color with RGBA values in [0, 255] range
SharedColor colorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return {hostPlatformColorFromRGBA(r, g, b, a)};
}
SharedColor clearColor() {
static SharedColor color = colorFromComponents(
ColorComponents{.red = 0, .green = 0, .blue = 0, .alpha = 0});
return color;
}
SharedColor blackColor() {
static SharedColor color = colorFromComponents(
ColorComponents{.red = 0, .green = 0, .blue = 0, .alpha = 1});
return color;
}
SharedColor whiteColor() {
static SharedColor color = colorFromComponents(
ColorComponents{.red = 1, .green = 1, .blue = 1, .alpha = 1});
return color;
}
} // namespace facebook::react

View File

@@ -0,0 +1,96 @@
/*
* 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
#include <react/renderer/graphics/ColorComponents.h>
#include <react/renderer/graphics/HostPlatformColor.h>
#include <functional>
#include <string>
#ifdef RN_SERIALIZABLE_STATE
#include <folly/dynamic.h>
#endif
namespace facebook::react {
/*
* On Android, a color can be represented as 32 bits integer, so there is no
* need to instantiate complex color objects and then pass them as shared
* pointers. Hense instead of using shared_ptr, we use a simple wrapper class
* which provides a pointer-like interface. On other platforms, colors may be
* represented by more complex objects that cannot be represented as 32-bits
* integers, so we hide the implementation detail in HostPlatformColor.h.
*/
class SharedColor {
public:
SharedColor() : color_(HostPlatformColor::UndefinedColor) {}
SharedColor(Color color) : color_(color) {}
Color &operator*()
{
return color_;
}
const Color &operator*() const
{
return color_;
}
bool operator==(const SharedColor &otherColor) const
{
return color_ == otherColor.color_;
}
bool operator!=(const SharedColor &otherColor) const
{
return color_ != otherColor.color_;
}
operator bool() const
{
return color_ != HostPlatformColor::UndefinedColor;
}
std::string toString() const noexcept;
private:
Color color_;
};
bool isColorMeaningful(const SharedColor &color) noexcept;
SharedColor colorFromComponents(ColorComponents components);
ColorComponents colorComponentsFromColor(SharedColor color);
uint8_t alphaFromColor(SharedColor color) noexcept;
uint8_t redFromColor(SharedColor color) noexcept;
uint8_t greenFromColor(SharedColor color) noexcept;
uint8_t blueFromColor(SharedColor color) noexcept;
SharedColor colorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
SharedColor clearColor();
SharedColor blackColor();
SharedColor whiteColor();
#ifdef RN_SERIALIZABLE_STATE
inline folly::dynamic toDynamic(const SharedColor &sharedColor)
{
return *sharedColor;
}
#endif
} // namespace facebook::react
template <>
struct std::hash<facebook::react::SharedColor> {
size_t operator()(const facebook::react::SharedColor &color) const
{
return std::hash<facebook::react::Color>{}(*color);
}
};

View File

@@ -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.
*/
#include "ColorComponents.h"
#include <mutex>
namespace facebook::react {
static ColorSpace defaultColorSpace = ColorSpace::sRGB;
ColorSpace getDefaultColorSpace() {
return defaultColorSpace;
}
void setDefaultColorSpace(ColorSpace newColorSpace) {
defaultColorSpace = newColorSpace;
}
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* 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
namespace facebook::react {
enum class ColorSpace { sRGB, DisplayP3 };
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

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#include "ColorStop.h"
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic ColorStop::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
result["color"] = *color;
result["position"] = position.toDynamic();
return result;
}
#endif
}; // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* 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
#include <react/renderer/debug/flags.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <optional>
#include <sstream>
namespace facebook::react {
struct ColorStop {
bool operator==(const ColorStop &other) const = default;
SharedColor color;
ValueUnit position;
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
void toString(std::stringstream &ss) const
{
ss << color.toString();
if (position.unit != UnitType::Undefined) {
ss << " ";
ss << position.toString();
}
}
#endif
};
struct ProcessedColorStop {
bool operator==(const ProcessedColorStop &other) const = default;
SharedColor color;
std::optional<Float> position;
};
}; // namespace facebook::react

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
#include "DoubleConversions.h"
#include <double-conversion/double-conversion.h>
#include <array>
namespace facebook::react {
std::string toString(double doubleValue, char suffix) {
// Format taken from folly's toString
static double_conversion::DoubleToStringConverter conv(
0,
nullptr,
nullptr,
'E',
-6, // detail::kConvMaxDecimalInShortestLow,
21, // detail::kConvMaxDecimalInShortestHigh,
6, // max leading padding zeros
1); // max trailing padding zeros
std::array<char, 256> buffer{};
double_conversion::StringBuilder builder(buffer.data(), buffer.size());
if (!conv.ToShortest(doubleValue, &builder)) {
// Serialize infinite and NaN as 0
builder.AddCharacter('0');
}
if (suffix != '\0') {
builder.AddCharacter(suffix);
}
return builder.Finalize();
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* 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
#include <string>
namespace facebook::react {
std::string toString(double doubleValue, char suffix);
} // namespace facebook::react

View File

@@ -0,0 +1,135 @@
/*
* 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
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <string>
#include <string_view>
#include <variant>
namespace facebook::react {
enum class FilterType {
Blur,
Brightness,
Contrast,
Grayscale,
HueRotate,
Invert,
Opacity,
Saturate,
Sepia,
DropShadow
};
inline FilterType filterTypeFromString(std::string_view filterName)
{
if (filterName == "blur") {
return FilterType::Blur;
} else if (filterName == "brightness") {
return FilterType::Brightness;
} else if (filterName == "contrast") {
return FilterType::Contrast;
} else if (filterName == "grayscale") {
return FilterType::Grayscale;
} else if (filterName == "hueRotate") {
return FilterType::HueRotate;
} else if (filterName == "invert") {
return FilterType::Invert;
} else if (filterName == "opacity") {
return FilterType::Opacity;
} else if (filterName == "saturate") {
return FilterType::Saturate;
} else if (filterName == "sepia") {
return FilterType::Sepia;
} else if (filterName == "dropShadow") {
return FilterType::DropShadow;
} else {
throw std::invalid_argument(std::string(filterName));
}
}
inline std::string toString(const FilterType &filterType)
{
switch (filterType) {
case FilterType::Blur:
return "blur";
case FilterType::Brightness:
return "brightness";
case FilterType::Contrast:
return "contrast";
case FilterType::Grayscale:
return "grayscale";
case FilterType::HueRotate:
return "hueRotate";
case FilterType::Invert:
return "invert";
case FilterType::Opacity:
return "opacity";
case FilterType::Saturate:
return "saturate";
case FilterType::Sepia:
return "sepia";
case FilterType::DropShadow:
return "dropShadow";
}
}
struct DropShadowParams {
bool operator==(const DropShadowParams &other) const = default;
Float offsetX{};
Float offsetY{};
Float standardDeviation{};
SharedColor color{};
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const
{
folly::dynamic result = folly::dynamic::object();
result["offsetX"] = offsetX;
result["offsetY"] = offsetY;
result["standardDeviation"] = standardDeviation;
result["color"] = *color;
return result;
}
#endif
};
struct FilterFunction {
bool operator==(const FilterFunction &other) const = default;
FilterType type{};
std::variant<Float, DropShadowParams> parameters{};
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const
{
folly::dynamic result = folly::dynamic::object();
std::string typeKey = toString(type);
if (std::holds_alternative<Float>(parameters)) {
result[typeKey] = std::get<Float>(parameters);
} else if (std::holds_alternative<DropShadowParams>(parameters)) {
const auto &dropShadowParams = std::get<DropShadowParams>(parameters);
result[typeKey] = dropShadowParams.toDynamic();
}
return result;
}
#endif
};
#ifdef RN_SERIALIZABLE_STATE
inline folly::dynamic toDynamic(const FilterFunction &filterFunction)
{
return filterFunction.toDynamic();
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* 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
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Point.h>
#include <react/renderer/graphics/Rect.h>
#include <react/renderer/graphics/RectangleCorners.h>
#include <react/renderer/graphics/RectangleEdges.h>
#include <react/renderer/graphics/Size.h>
#include <react/renderer/graphics/Vector.h>
#warning \
"The Geometry.h file is deprecated and will be removed in the next version of React Native. Please update your #include and #import statements to use the specific files. For example, if you imported Geometry.h to use Float.h, replase the #include <react/renderer/graphics/Geometry.h> with #include <react/renderer/graphics/Float.h>"

View File

@@ -0,0 +1,31 @@
/*
* 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
#include <optional>
#include <string_view>
namespace facebook::react {
// https://www.w3.org/TR/compositing-1/#isolation
enum class Isolation {
Auto,
Isolate,
};
inline std::optional<Isolation> isolationFromString(std::string_view isolationSetting)
{
if (isolationSetting == "auto") {
return Isolation::Auto;
} else if (isolationSetting == "isolate") {
return Isolation::Isolate;
} else {
return std::nullopt;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,92 @@
/*
* 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.
*/
#include "LinearGradient.h"
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic GradientDirection::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
result["type"] = [&]() {
switch (type) {
case GradientDirectionType::Angle:
return "angle";
case GradientDirectionType::Keyword:
return "keyword";
}
return "";
}();
if (std::holds_alternative<Float>(value)) {
result["value"] = std::get<Float>(value);
} else if (std::holds_alternative<GradientKeyword>(value)) {
result["value"] = [&]() {
switch (std::get<GradientKeyword>(value)) {
case GradientKeyword::ToTopRight:
return "to top right";
case GradientKeyword::ToBottomRight:
return "to bottom right";
case GradientKeyword::ToTopLeft:
return "to top left";
case GradientKeyword::ToBottomLeft:
return "to bottom left";
}
return "";
}();
}
return result;
}
folly::dynamic LinearGradient::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
result["type"] = "linear-gradient";
result["direction"] = direction.toDynamic();
folly::dynamic colorStopsArray = folly::dynamic::array();
for (const auto& colorStop : colorStops) {
colorStopsArray.push_back(colorStop.toDynamic());
}
result["colorStops"] = colorStopsArray;
return result;
}
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
void LinearGradient::toString(std::stringstream& ss) const {
ss << "linear-gradient(";
if (direction.type == GradientDirectionType::Angle) {
ss << std::get<Float>(direction.value) << "deg";
} else {
auto keyword = std::get<GradientKeyword>(direction.value);
switch (keyword) {
case GradientKeyword::ToTopRight:
ss << "to top right";
break;
case GradientKeyword::ToBottomRight:
ss << "to bottom right";
break;
case GradientKeyword::ToTopLeft:
ss << "to top left";
break;
case GradientKeyword::ToBottomLeft:
ss << "to bottom left";
break;
}
}
for (const auto& colorStop : colorStops) {
ss << ", ";
colorStop.toString(ss);
}
ss << ")";
}
#endif
}; // namespace facebook::react

View File

@@ -0,0 +1,62 @@
/*
* 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
#include <react/renderer/debug/flags.h>
#include <react/renderer/graphics/ColorStop.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <sstream>
#include <variant>
#include <vector>
namespace facebook::react {
enum class GradientDirectionType { Angle, Keyword };
enum class GradientKeyword {
ToTopRight,
ToBottomRight,
ToTopLeft,
ToBottomLeft,
};
struct GradientDirection {
GradientDirectionType type;
std::variant<Float, GradientKeyword> value;
bool operator==(const GradientDirection &other) const
{
return type == other.type && value == other.value;
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
};
struct LinearGradient {
GradientDirection direction;
std::vector<ColorStop> colorStops;
bool operator==(const LinearGradient &other) const
{
return direction == other.direction && colorStops == other.colorStops;
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
void toString(std::stringstream &ss) const;
#endif
};
}; // namespace facebook::react

View File

@@ -0,0 +1,83 @@
/*
* 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
#include <react/renderer/graphics/Float.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Contains a point in a two-dimensional coordinate system.
*/
struct Point {
Float x{0};
Float y{0};
inline Point &operator+=(const Point &point) noexcept
{
x += point.x;
y += point.y;
return *this;
}
inline Point &operator-=(const Point &point) noexcept
{
x -= point.x;
y -= point.y;
return *this;
}
inline Point &operator*=(const Point &point) noexcept
{
x *= point.x;
y *= point.y;
return *this;
}
inline Point operator+(const Point &rhs) const noexcept
{
return {
.x = this->x + rhs.x,
.y = this->y + rhs.y,
};
}
inline Point operator-(const Point &rhs) const noexcept
{
return {
.x = this->x - rhs.x,
.y = this->y - rhs.y,
};
}
inline Point operator-() const noexcept
{
return {
.x = -x,
.y = -y,
};
}
inline bool operator==(const Point &rhs) const = default;
inline bool operator!=(const Point &rhs) const = default;
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::Point> {
inline size_t operator()(const facebook::react::Point &point) const noexcept
{
return facebook::react::hash_combine(point.x, point.y);
}
};
} // namespace std

View File

@@ -0,0 +1,138 @@
/*
* 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.
*/
#include "RadialGradient.h"
#if RN_DEBUG_STRING_CONVERTIBLE
#include <react/renderer/debug/DebugStringConvertible.h>
#endif
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic RadialGradientSize::Dimensions::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
result["x"] = x.toDynamic();
result["y"] = y.toDynamic();
return result;
}
folly::dynamic RadialGradientSize::toDynamic() const {
if (std::holds_alternative<SizeKeyword>(value)) {
switch (std::get<SizeKeyword>(value)) {
case SizeKeyword::ClosestSide:
return "closest-side";
case SizeKeyword::FarthestSide:
return "farthest-side";
case SizeKeyword::ClosestCorner:
return "closest-corner";
case SizeKeyword::FarthestCorner:
return "farthest-corner";
}
} else if (std::holds_alternative<Dimensions>(value)) {
return std::get<Dimensions>(value).toDynamic();
}
return nullptr;
}
folly::dynamic RadialGradientPosition::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
if (top.has_value()) {
result["top"] = top.value().toDynamic();
}
if (left.has_value()) {
result["left"] = left.value().toDynamic();
}
if (right.has_value()) {
result["right"] = right.value().toDynamic();
}
if (bottom.has_value()) {
result["bottom"] = bottom.value().toDynamic();
}
return result;
}
folly::dynamic RadialGradient::toDynamic() const {
folly::dynamic result = folly::dynamic::object();
result["type"] = "radial-gradient";
switch (shape) {
case RadialGradientShape::Circle:
result["shape"] = "circle";
break;
case RadialGradientShape::Ellipse:
result["shape"] = "ellipse";
break;
default:
break;
}
result["size"] = size.toDynamic();
result["position"] = position.toDynamic();
folly::dynamic colorStopsArray = folly::dynamic::array();
for (const auto& colorStop : colorStops) {
colorStopsArray.push_back(colorStop.toDynamic());
}
result["colorStops"] = colorStopsArray;
return result;
}
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
void RadialGradient::toString(std::stringstream& ss) const {
ss << "radial-gradient("
<< (shape == RadialGradientShape::Circle ? "circle" : "ellipse") << " ";
if (std::holds_alternative<RadialGradientSize::SizeKeyword>(size.value)) {
auto& keyword = std::get<RadialGradientSize::SizeKeyword>(size.value);
switch (keyword) {
case RadialGradientSize::SizeKeyword::ClosestSide:
ss << "closest-side";
break;
case RadialGradientSize::SizeKeyword::FarthestSide:
ss << "farthest-side";
break;
case RadialGradientSize::SizeKeyword::ClosestCorner:
ss << "closest-corner";
break;
case RadialGradientSize::SizeKeyword::FarthestCorner:
ss << "farthest-corner";
break;
}
} else {
auto& dimensions = std::get<RadialGradientSize::Dimensions>(size.value);
ss << react::toString(dimensions.x) << " " << react::toString(dimensions.y);
}
ss << " at ";
if (position.left.has_value()) {
ss << position.left->toString() << " ";
}
if (position.top.has_value()) {
ss << position.top->toString() << " ";
}
if (position.right.has_value()) {
ss << position.right->toString() << " ";
}
if (position.bottom.has_value()) {
ss << position.bottom->toString() << " ";
}
for (const auto& colorStop : colorStops) {
ss << ", ";
colorStop.toString(ss);
}
ss << ")";
}
#endif
}; // namespace facebook::react

View File

@@ -0,0 +1,110 @@
/*
* 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
#include <react/renderer/debug/flags.h>
#include <react/renderer/graphics/ColorStop.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <optional>
#include <sstream>
#include <variant>
#include <vector>
#ifdef RN_SERIALIZABLE_STATE
#include <folly/dynamic.h>
#endif
namespace facebook::react {
enum class RadialGradientShape { Circle, Ellipse };
struct RadialGradientSize {
enum class SizeKeyword { ClosestSide, FarthestSide, ClosestCorner, FarthestCorner };
struct Dimensions {
ValueUnit x;
ValueUnit y;
bool operator==(const Dimensions &other) const
{
return x == other.x && y == other.y;
}
bool operator!=(const Dimensions &other) const
{
return !(*this == other);
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
};
std::variant<SizeKeyword, Dimensions> value;
bool operator==(const RadialGradientSize &other) const
{
return value == other.value;
}
bool operator!=(const RadialGradientSize &other) const
{
return !(*this == other);
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
};
struct RadialGradientPosition {
std::optional<ValueUnit> top;
std::optional<ValueUnit> left;
std::optional<ValueUnit> right;
std::optional<ValueUnit> bottom;
bool operator==(const RadialGradientPosition &other) const
{
return top == other.top && left == other.left && right == other.right && bottom == other.bottom;
}
bool operator!=(const RadialGradientPosition &other) const
{
return !(*this == other);
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
};
struct RadialGradient {
RadialGradientShape shape;
RadialGradientSize size;
RadialGradientPosition position;
std::vector<ColorStop> colorStops;
bool operator==(const RadialGradient &other) const
{
return shape == other.shape && size == other.size && position == other.position && colorStops == other.colorStops;
}
bool operator!=(const RadialGradient &other) const
{
return !(*this == other);
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
void toString(std::stringstream &ss) const;
#endif
};
}; // namespace facebook::react

View File

@@ -0,0 +1,55 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
source_files = ["*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}"]
header_search_paths = [
"\"$(PODS_TARGET_SRCROOT)/../../../\"",
]
s.name = "React-graphics"
s.version = version
s.summary = "Fabric for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources(source_files, ["*.h", "platform/ios/**/*.h"])
s.header_dir = "react/renderer/graphics"
s.framework = "UIKit"
if ENV['USE_FRAMEWORKS']
header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""]
end
resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_graphics")
s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO",
"HEADER_SEARCH_PATHS" => header_search_paths.join(" "),
"DEFINES_MODULE" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard() }
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-utils"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,138 @@
/*
* 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
#include <algorithm>
#include <tuple>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Point.h>
#include <react/renderer/graphics/Size.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Contains the location and dimensions of a rectangle.
*/
struct Rect {
Point origin{.x = 0, .y = 0};
Size size{.width = 0, .height = 0};
bool operator==(const Rect &rhs) const noexcept
{
return std::tie(this->origin, this->size) == std::tie(rhs.origin, rhs.size);
}
bool operator!=(const Rect &rhs) const noexcept
{
return !(*this == rhs);
}
Float getMaxX() const noexcept
{
return size.width > 0 ? origin.x + size.width : origin.x;
}
Float getMaxY() const noexcept
{
return size.height > 0 ? origin.y + size.height : origin.y;
}
Float getMinX() const noexcept
{
return size.width >= 0 ? origin.x : origin.x + size.width;
}
Float getMinY() const noexcept
{
return size.height >= 0 ? origin.y : origin.y + size.height;
}
Float getMidX() const noexcept
{
return origin.x + size.width / 2;
}
Float getMidY() const noexcept
{
return origin.y + size.height / 2;
}
Point getCenter() const noexcept
{
return {.x = getMidX(), .y = getMidY()};
}
void unionInPlace(const Rect &rect) noexcept
{
auto x1 = std::min(getMinX(), rect.getMinX());
auto y1 = std::min(getMinY(), rect.getMinY());
auto x2 = std::max(getMaxX(), rect.getMaxX());
auto y2 = std::max(getMaxY(), rect.getMaxY());
origin = {.x = x1, .y = y1};
size = {.width = x2 - x1, .height = y2 - y1};
}
bool containsPoint(Point point) noexcept
{
return point.x >= origin.x && point.y >= origin.y && point.x <= (origin.x + size.width) &&
point.y <= (origin.y + size.height);
}
static Rect intersect(const Rect &rect1, const Rect &rect2)
{
Float x1 = std::max(rect1.origin.x, rect2.origin.x);
Float y1 = std::max(rect1.origin.y, rect2.origin.y);
Float x2 = std::min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
Float y2 = std::min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
Float intersectionWidth = x2 - x1;
Float intersectionHeight = y2 - y1;
if (intersectionWidth < 0 || intersectionHeight < 0) {
return {};
}
return {.origin = {.x = x1, .y = y1}, .size = {.width = intersectionWidth, .height = intersectionHeight}};
}
static Rect boundingRect(const Point &a, const Point &b, const Point &c, const Point &d) noexcept
{
auto leftTopPoint = a;
auto rightBottomPoint = a;
leftTopPoint.x = std::min(leftTopPoint.x, b.x);
leftTopPoint.x = std::min(leftTopPoint.x, c.x);
leftTopPoint.x = std::min(leftTopPoint.x, d.x);
leftTopPoint.y = std::min(leftTopPoint.y, b.y);
leftTopPoint.y = std::min(leftTopPoint.y, c.y);
leftTopPoint.y = std::min(leftTopPoint.y, d.y);
rightBottomPoint.x = std::max(rightBottomPoint.x, b.x);
rightBottomPoint.x = std::max(rightBottomPoint.x, c.x);
rightBottomPoint.x = std::max(rightBottomPoint.x, d.x);
rightBottomPoint.y = std::max(rightBottomPoint.y, b.y);
rightBottomPoint.y = std::max(rightBottomPoint.y, c.y);
rightBottomPoint.y = std::max(rightBottomPoint.y, d.y);
return {
.origin = leftTopPoint,
.size = {.width = rightBottomPoint.x - leftTopPoint.x, .height = rightBottomPoint.y - leftTopPoint.y}};
}
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::Rect> {
size_t operator()(const facebook::react::Rect &rect) const noexcept
{
return facebook::react::hash_combine(rect.origin, rect.size);
}
};
} // namespace std

View File

@@ -0,0 +1,62 @@
/*
* 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
#include <tuple>
#include <react/renderer/graphics/Float.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Generic data structure describes some values associated with *corners*
* of a rectangle.
*/
template <typename T>
struct RectangleCorners {
T topLeft{};
T topRight{};
T bottomLeft{};
T bottomRight{};
bool operator==(const RectangleCorners<T> &rhs) const noexcept
{
return std::tie(this->topLeft, this->topRight, this->bottomLeft, this->bottomRight) ==
std::tie(rhs.topLeft, rhs.topRight, rhs.bottomLeft, rhs.bottomRight);
}
bool operator!=(const RectangleCorners<T> &rhs) const noexcept
{
return !(*this == rhs);
}
bool isUniform() const noexcept
{
return topLeft == topRight && topLeft == bottomLeft && topLeft == bottomRight;
}
};
/*
* CornerInsets
*/
using CornerInsets = RectangleCorners<Float>;
} // namespace facebook::react
namespace std {
template <typename T>
struct hash<facebook::react::RectangleCorners<T>> {
size_t operator()(const facebook::react::RectangleCorners<T> &corners) const noexcept
{
return facebook::react::hash_combine(corners.topLeft, corners.bottomLeft, corners.topRight, corners.bottomRight);
}
};
} // namespace std

View File

@@ -0,0 +1,104 @@
/*
* 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
#include <tuple>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Rect.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Generic data structure describes some values associated with *edges*
* of a rectangle.
*/
template <typename T>
struct RectangleEdges {
T left{};
T top{};
T right{};
T bottom{};
bool operator==(const RectangleEdges<T> &rhs) const noexcept
{
return std::tie(this->left, this->top, this->right, this->bottom) ==
std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom);
}
bool operator!=(const RectangleEdges<T> &rhs) const noexcept
{
return !(*this == rhs);
}
bool isUniform() const noexcept
{
return left == top && left == right && left == bottom;
}
static const RectangleEdges<T> ZERO;
};
template <typename T>
const RectangleEdges<T> RectangleEdges<T>::ZERO = {};
template <typename T>
RectangleEdges<T> operator+(const RectangleEdges<T> &lhs, const RectangleEdges<T> &rhs) noexcept
{
return RectangleEdges<T>{lhs.left + rhs.left, lhs.top + rhs.top, lhs.right + rhs.right, lhs.bottom + rhs.bottom};
}
template <typename T>
RectangleEdges<T> operator-(const RectangleEdges<T> &lhs, const RectangleEdges<T> &rhs) noexcept
{
return RectangleEdges<T>{lhs.left - rhs.left, lhs.top - rhs.top, lhs.right - rhs.right, lhs.bottom - rhs.bottom};
}
/*
* EdgeInsets
*/
using EdgeInsets = RectangleEdges<Float>;
/*
* Adjusts a rectangle by the given edge insets.
*/
inline Rect insetBy(const Rect &rect, const EdgeInsets &insets) noexcept
{
return Rect{
.origin = {.x = rect.origin.x + insets.left, .y = rect.origin.y + insets.top},
.size = {
.width = rect.size.width - insets.left - insets.right,
.height = rect.size.height - insets.top - insets.bottom}};
}
/*
* Adjusts a rectangle by the given edge outsets.
*/
inline Rect outsetBy(const Rect &rect, const EdgeInsets &outsets) noexcept
{
return Rect{
.origin = {.x = rect.origin.x - outsets.left, .y = rect.origin.y - outsets.top},
.size = {
.width = rect.size.width + outsets.left + outsets.right,
.height = rect.size.height + outsets.top + outsets.bottom}};
}
} // namespace facebook::react
namespace std {
template <typename T>
struct hash<facebook::react::RectangleEdges<T>> {
size_t operator()(const facebook::react::RectangleEdges<T> &edges) const noexcept
{
return facebook::react::hash_combine(edges.left, edges.right, edges.top, edges.bottom);
}
};
} // namespace std

View File

@@ -0,0 +1,62 @@
/*
* 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
#include <tuple>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Point.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
/*
* Contains width and height values.
*/
struct Size {
Float width{0};
Float height{0};
Size &operator+=(const Point &point) noexcept
{
width += point.x;
height += point.y;
return *this;
}
Size &operator*=(const Point &point) noexcept
{
width *= point.x;
height *= point.y;
return *this;
}
};
inline bool operator==(const Size &rhs, const Size &lhs) noexcept
{
return std::tie(lhs.width, lhs.height) == std::tie(rhs.width, rhs.height);
}
inline bool operator!=(const Size &rhs, const Size &lhs) noexcept
{
return !(lhs == rhs);
}
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::Size> {
size_t operator()(const facebook::react::Size &size) const
{
return facebook::react::hash_combine(size.width, size.height);
}
};
} // namespace std

View File

@@ -0,0 +1,517 @@
/*
* 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.
*/
#include "Transform.h"
#include <cmath>
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
/* static */ Transform Transform::Identity() noexcept {
return {};
}
/* static */ Transform Transform::VerticalInversion() noexcept {
return Transform::Scale(1, -1, 1);
}
/* static */ Transform Transform::HorizontalInversion() noexcept {
return Transform::Scale(-1, 1, 1);
}
/* static */ Transform Transform::Perspective(Float perspective) noexcept {
auto transform = Transform{};
auto Zero = ValueUnit(0, UnitType::Point);
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Perspective,
.x = ValueUnit(perspective, UnitType::Point),
.y = Zero,
.z = Zero});
transform.matrix[11] = -1 / perspective;
return transform;
}
/* static */ Transform Transform::Scale(Float x, Float y, Float z) noexcept {
auto transform = Transform{};
Float xprime = isZero(x) ? 0 : x;
Float yprime = isZero(y) ? 0 : y;
Float zprime = isZero(z) ? 0 : z;
if (xprime != 1 || yprime != 1 || zprime != 1) {
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Scale,
.x = ValueUnit(xprime, UnitType::Point),
.y = ValueUnit(yprime, UnitType::Point),
.z = ValueUnit(zprime, UnitType::Point)});
transform.matrix[0] = xprime;
transform.matrix[5] = yprime;
transform.matrix[10] = zprime;
}
return transform;
}
/* static */ Transform
Transform::Translate(Float x, Float y, Float z) noexcept {
auto transform = Transform{};
Float xprime = isZero(x) ? 0 : x;
Float yprime = isZero(y) ? 0 : y;
Float zprime = isZero(z) ? 0 : z;
if (xprime != 0 || yprime != 0 || zprime != 0) {
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Translate,
.x = ValueUnit(xprime, UnitType::Point),
.y = ValueUnit(yprime, UnitType::Point),
.z = ValueUnit(zprime, UnitType::Point)});
transform.matrix[12] = xprime;
transform.matrix[13] = yprime;
transform.matrix[14] = zprime;
}
return transform;
}
/* static */ Transform Transform::Skew(Float x, Float y) noexcept {
auto transform = Transform{};
Float xprime = isZero(x) ? 0 : x;
Float yprime = isZero(y) ? 0 : y;
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Skew,
.x = ValueUnit(xprime, UnitType::Point),
.y = ValueUnit(yprime, UnitType::Point),
.z = ValueUnit(0, UnitType::Point)});
transform.matrix[4] = std::tan(xprime);
transform.matrix[1] = std::tan(yprime);
return transform;
}
/* static */ Transform Transform::RotateX(Float radians) noexcept {
auto transform = Transform{};
if (!isZero(radians)) {
auto Zero = ValueUnit(0, UnitType::Point);
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Rotate,
.x = ValueUnit(radians, UnitType::Point),
.y = Zero,
.z = Zero});
transform.matrix[5] = std::cos(radians);
transform.matrix[6] = std::sin(radians);
transform.matrix[9] = -std::sin(radians);
transform.matrix[10] = std::cos(radians);
}
return transform;
}
/* static */ Transform Transform::RotateY(Float radians) noexcept {
auto transform = Transform{};
if (!isZero(radians)) {
auto Zero = ValueUnit(0, UnitType::Point);
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Rotate,
.x = Zero,
.y = ValueUnit(radians, UnitType::Point),
.z = Zero});
transform.matrix[0] = std::cos(radians);
transform.matrix[2] = -std::sin(radians);
transform.matrix[8] = std::sin(radians);
transform.matrix[10] = std::cos(radians);
}
return transform;
}
/* static */ Transform Transform::RotateZ(Float radians) noexcept {
auto transform = Transform{};
if (!isZero(radians)) {
auto Zero = ValueUnit(0, UnitType::Point);
transform.operations.push_back(
TransformOperation{
.type = TransformOperationType::Rotate,
.x = Zero,
.y = Zero,
.z = ValueUnit(radians, UnitType::Point)});
transform.matrix[0] = std::cos(radians);
transform.matrix[1] = std::sin(radians);
transform.matrix[4] = -std::sin(radians);
transform.matrix[5] = std::cos(radians);
}
return transform;
}
/* static */ Transform Transform::Rotate(Float x, Float y, Float z) noexcept {
auto transform = Transform{};
if (!isZero(x)) {
transform = transform * Transform::RotateX(x);
}
if (!isZero(y)) {
transform = transform * Transform::RotateY(y);
}
if (!isZero(z)) {
transform = transform * Transform::RotateZ(z);
}
return transform;
}
/* static */ Transform Transform::FromTransformOperation(
TransformOperation transformOperation,
const Size& size,
const Transform& transform) {
if (transformOperation.type == TransformOperationType::Perspective) {
return Transform::Perspective(transformOperation.x.resolve(0));
}
if (transformOperation.type == TransformOperationType::Scale) {
return Transform::Scale(
transformOperation.x.resolve(0),
transformOperation.y.resolve(0),
transformOperation.z.resolve(0));
}
if (transformOperation.type == TransformOperationType::Translate) {
auto translateX = transformOperation.x.resolve(size.width);
auto translateY = transformOperation.y.resolve(size.height);
return Transform::Translate(
translateX, translateY, transformOperation.z.resolve(0));
}
if (transformOperation.type == TransformOperationType::Skew) {
return Transform::Skew(
transformOperation.x.resolve(0), transformOperation.y.resolve(0));
}
if (transformOperation.type == TransformOperationType::Rotate) {
return Transform::Rotate(
transformOperation.x.resolve(0),
transformOperation.y.resolve(0),
transformOperation.z.resolve(0));
}
// when using arbitrary transform, the caller is responsible for applying the
// value
if (transformOperation.type == TransformOperationType::Arbitrary) {
auto arbitraryTransform = Transform{};
arbitraryTransform.operations.push_back(transformOperation);
arbitraryTransform.matrix = transform.matrix;
return arbitraryTransform;
}
// Identity
return Transform::Identity();
}
/* static */ TransformOperation Transform::DefaultTransformOperation(
TransformOperationType type) {
auto Zero = ValueUnit{0, UnitType::Point};
auto One = ValueUnit{1, UnitType::Point};
switch (type) {
case TransformOperationType::Arbitrary:
return TransformOperation{
.type = TransformOperationType::Arbitrary,
.x = Zero,
.y = Zero,
.z = Zero};
case TransformOperationType::Perspective:
return TransformOperation{
.type = TransformOperationType::Perspective,
.x = Zero,
.y = Zero,
.z = Zero};
case TransformOperationType::Scale:
return TransformOperation{
.type = TransformOperationType::Scale, .x = One, .y = One, .z = One};
case TransformOperationType::Translate:
return TransformOperation{
.type = TransformOperationType::Translate,
.x = Zero,
.y = Zero,
.z = Zero};
case TransformOperationType::Rotate:
return TransformOperation{
.type = TransformOperationType::Rotate,
.x = Zero,
.y = Zero,
.z = Zero};
case TransformOperationType::Skew:
return TransformOperation{
.type = TransformOperationType::Skew,
.x = Zero,
.y = Zero,
.z = Zero};
default:
case TransformOperationType::Identity:
return TransformOperation{
.type = TransformOperationType::Identity,
.x = Zero,
.y = Zero,
.z = Zero};
}
}
/* static */ Transform Transform::Interpolate(
Float animationProgress,
const Transform& lhs,
const Transform& rhs,
const Size& size) {
// Iterate through operations and reconstruct an interpolated resulting
// transform If at any point we hit an "Arbitrary" Transform, return at that
// point
Transform result = Transform::Identity();
for (size_t i = 0, j = 0;
i < lhs.operations.size() || j < rhs.operations.size();) {
bool haveLHS = i < lhs.operations.size();
bool haveRHS = j < rhs.operations.size();
if ((haveLHS &&
lhs.operations[i].type == TransformOperationType::Arbitrary) ||
(haveRHS &&
rhs.operations[j].type == TransformOperationType::Arbitrary)) {
return result;
}
if (haveLHS && lhs.operations[i].type == TransformOperationType::Identity) {
i++;
continue;
}
if (haveRHS && rhs.operations[j].type == TransformOperationType::Identity) {
j++;
continue;
}
// Here we either set:
// 1. lhs = next left op, rhs = next right op (when types are identical and
// both exist)
// 2. lhs = next left op, rhs = default of type (if types unequal, or rhs
// doesn't exist)
// 3. lhs = default of type, rhs = next right op (if types unequal, or rhs
// doesn't exist) This guarantees that the types of both sides are equal,
// and that one or both indices moves forward.
TransformOperationType type =
(haveLHS ? lhs.operations[i] : rhs.operations[j]).type;
TransformOperation lhsOp =
(haveLHS ? lhs.operations[i++]
: Transform::DefaultTransformOperation(type));
TransformOperation rhsOp =
(haveRHS && rhs.operations[j].type == type
? rhs.operations[j++]
: Transform::DefaultTransformOperation(type));
react_native_assert(type == lhsOp.type);
react_native_assert(type == rhsOp.type);
result = result *
Transform::FromTransformOperation(
TransformOperation{
.type = type,
.x = ValueUnit(
lhsOp.x.resolve(size.width) +
(rhsOp.x.resolve(size.width) -
lhsOp.x.resolve(size.width)) *
animationProgress,
UnitType::Point),
.y = ValueUnit(
lhsOp.y.resolve(size.height) +
(rhsOp.y.resolve(size.height) -
lhsOp.y.resolve(size.height)) *
animationProgress,
UnitType::Point),
.z = ValueUnit(
lhsOp.z.resolve(0) +
(rhsOp.z.resolve(0) - lhsOp.z.resolve(0)) *
animationProgress,
UnitType::Point)},
size);
}
return result;
}
/* static */ bool Transform::isVerticalInversion(
const Transform& transform) noexcept {
return floatEquality(transform.at(1, 1), static_cast<Float>(-1.0f));
}
/* static */ bool Transform::isHorizontalInversion(
const Transform& transform) noexcept {
return floatEquality(transform.at(0, 0), static_cast<Float>(-1.0f));
}
bool Transform::operator==(const Transform& rhs) const noexcept {
for (auto i = 0; i < 16; i++) {
if (matrix[i] != rhs.matrix[i]) {
return false;
}
}
if (this->operations.size() != rhs.operations.size()) {
return false;
}
for (size_t i = 0; i < this->operations.size(); i++) {
if (this->operations[i] != rhs.operations[i]) {
return false;
}
}
return true;
}
bool Transform::operator!=(const Transform& rhs) const noexcept {
return !(*this == rhs);
}
Transform Transform::operator*(const Transform& rhs) const {
if (*this == Transform::Identity()) {
return rhs;
}
const auto& lhs = *this;
auto result = Transform{};
for (const auto& op : this->operations) {
if (op.type == TransformOperationType::Identity &&
!result.operations.empty()) {
continue;
}
result.operations.push_back(op);
}
for (const auto& op : rhs.operations) {
if (op.type == TransformOperationType::Identity &&
!result.operations.empty()) {
continue;
}
result.operations.push_back(op);
}
auto lhs00 = lhs.matrix[0];
auto lhs01 = lhs.matrix[1];
auto lhs02 = lhs.matrix[2];
auto lhs03 = lhs.matrix[3];
auto lhs10 = lhs.matrix[4];
auto lhs11 = lhs.matrix[5];
auto lhs12 = lhs.matrix[6];
auto lhs13 = lhs.matrix[7];
auto lhs20 = lhs.matrix[8];
auto lhs21 = lhs.matrix[9];
auto lhs22 = lhs.matrix[10];
auto lhs23 = lhs.matrix[11];
auto lhs30 = lhs.matrix[12];
auto lhs31 = lhs.matrix[13];
auto lhs32 = lhs.matrix[14];
auto lhs33 = lhs.matrix[15];
auto rhs0 = rhs.matrix[0];
auto rhs1 = rhs.matrix[1];
auto rhs2 = rhs.matrix[2];
auto rhs3 = rhs.matrix[3];
result.matrix[0] = rhs0 * lhs00 + rhs1 * lhs10 + rhs2 * lhs20 + rhs3 * lhs30;
result.matrix[1] = rhs0 * lhs01 + rhs1 * lhs11 + rhs2 * lhs21 + rhs3 * lhs31;
result.matrix[2] = rhs0 * lhs02 + rhs1 * lhs12 + rhs2 * lhs22 + rhs3 * lhs32;
result.matrix[3] = rhs0 * lhs03 + rhs1 * lhs13 + rhs2 * lhs23 + rhs3 * lhs33;
rhs0 = rhs.matrix[4];
rhs1 = rhs.matrix[5];
rhs2 = rhs.matrix[6];
rhs3 = rhs.matrix[7];
result.matrix[4] = rhs0 * lhs00 + rhs1 * lhs10 + rhs2 * lhs20 + rhs3 * lhs30;
result.matrix[5] = rhs0 * lhs01 + rhs1 * lhs11 + rhs2 * lhs21 + rhs3 * lhs31;
result.matrix[6] = rhs0 * lhs02 + rhs1 * lhs12 + rhs2 * lhs22 + rhs3 * lhs32;
result.matrix[7] = rhs0 * lhs03 + rhs1 * lhs13 + rhs2 * lhs23 + rhs3 * lhs33;
rhs0 = rhs.matrix[8];
rhs1 = rhs.matrix[9];
rhs2 = rhs.matrix[10];
rhs3 = rhs.matrix[11];
result.matrix[8] = rhs0 * lhs00 + rhs1 * lhs10 + rhs2 * lhs20 + rhs3 * lhs30;
result.matrix[9] = rhs0 * lhs01 + rhs1 * lhs11 + rhs2 * lhs21 + rhs3 * lhs31;
result.matrix[10] = rhs0 * lhs02 + rhs1 * lhs12 + rhs2 * lhs22 + rhs3 * lhs32;
result.matrix[11] = rhs0 * lhs03 + rhs1 * lhs13 + rhs2 * lhs23 + rhs3 * lhs33;
rhs0 = rhs.matrix[12];
rhs1 = rhs.matrix[13];
rhs2 = rhs.matrix[14];
rhs3 = rhs.matrix[15];
result.matrix[12] = rhs0 * lhs00 + rhs1 * lhs10 + rhs2 * lhs20 + rhs3 * lhs30;
result.matrix[13] = rhs0 * lhs01 + rhs1 * lhs11 + rhs2 * lhs21 + rhs3 * lhs31;
result.matrix[14] = rhs0 * lhs02 + rhs1 * lhs12 + rhs2 * lhs22 + rhs3 * lhs32;
result.matrix[15] = rhs0 * lhs03 + rhs1 * lhs13 + rhs2 * lhs23 + rhs3 * lhs33;
return result;
}
Float& Transform::at(int i, int j) noexcept {
return matrix[(i * 4) + j];
}
const Float& Transform::at(int i, int j) const noexcept {
return matrix[(i * 4) + j];
}
Point operator*(const Point& point, const Transform& transform) {
if (transform == Transform::Identity()) {
return point;
}
auto result = transform * Vector{.x = point.x, .y = point.y, .z = 0, .w = 1};
return {.x = result.x, .y = result.y};
}
Rect operator*(const Rect& rect, const Transform& transform) {
auto center = rect.getCenter();
return transform.applyWithCenter(rect, center);
}
Rect Transform::applyWithCenter(const Rect& rect, const Point& center) const {
auto a = Point{.x = rect.origin.x, .y = rect.origin.y} - center;
auto b = Point{.x = rect.getMaxX(), .y = rect.origin.y} - center;
auto c = Point{.x = rect.getMaxX(), .y = rect.getMaxY()} - center;
auto d = Point{.x = rect.origin.x, .y = rect.getMaxY()} - center;
auto vectorA = *this * Vector{.x = a.x, .y = a.y, .z = 0, .w = 1};
auto vectorB = *this * Vector{.x = b.x, .y = b.y, .z = 0, .w = 1};
auto vectorC = *this * Vector{.x = c.x, .y = c.y, .z = 0, .w = 1};
auto vectorD = *this * Vector{.x = d.x, .y = d.y, .z = 0, .w = 1};
Point transformedA{.x = vectorA.x + center.x, .y = vectorA.y + center.y};
Point transformedB{.x = vectorB.x + center.x, .y = vectorB.y + center.y};
Point transformedC{.x = vectorC.x + center.x, .y = vectorC.y + center.y};
Point transformedD{.x = vectorD.x + center.x, .y = vectorD.y + center.y};
return Rect::boundingRect(
transformedA, transformedB, transformedC, transformedD);
}
EdgeInsets operator*(const EdgeInsets& edgeInsets, const Transform& transform) {
return EdgeInsets{
edgeInsets.left * transform.matrix[0],
edgeInsets.top * transform.matrix[5],
edgeInsets.right * transform.matrix[0],
edgeInsets.bottom * transform.matrix[5]};
}
Vector operator*(const Transform& transform, const Vector& vector) {
return {
.x = vector.x * transform.at(0, 0) + vector.y * transform.at(1, 0) +
vector.z * transform.at(2, 0) + vector.w * transform.at(3, 0),
.y = vector.x * transform.at(0, 1) + vector.y * transform.at(1, 1) +
vector.z * transform.at(2, 1) + vector.w * transform.at(3, 1),
.z = vector.x * transform.at(0, 2) + vector.y * transform.at(1, 2) +
vector.z * transform.at(2, 2) + vector.w * transform.at(3, 2),
.w = vector.x * transform.at(0, 3) + vector.y * transform.at(1, 3) +
vector.z * transform.at(2, 3) + vector.w * transform.at(3, 3),
};
}
Size operator*(const Size& size, const Transform& transform) {
if (transform == Transform::Identity()) {
return size;
}
auto result = Size{};
result.width = std::abs(transform.at(0, 0) * size.width);
result.height = std::abs(transform.at(1, 1) * size.height);
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,256 @@
/*
* 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
#include <array>
#include <vector>
#include <react/renderer/debug/flags.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Point.h>
#include <react/renderer/graphics/RectangleEdges.h>
#include <react/renderer/graphics/Size.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <react/renderer/graphics/Vector.h>
#include <react/utils/hash_combine.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook::react {
inline bool isZero(Float n)
{
// We use this ternary expression instead of abs, fabsf, etc, because
// Float can be double or float depending on compilation target.
return (n < 0 ? n * (-1) : n) < 0.00001;
}
/**
* Defines operations used to construct a transform matrix.
* An "Arbitrary" operation means that the transform was seeded with some
* arbitrary initial result.
*/
enum class TransformOperationType : uint8_t { Arbitrary, Identity, Perspective, Scale, Translate, Rotate, Skew };
struct TransformOperation {
TransformOperationType type;
ValueUnit x;
ValueUnit y;
ValueUnit z;
bool operator==(const TransformOperation &other) const = default;
};
struct TransformOrigin {
std::array<ValueUnit, 2> xy = {ValueUnit(0.0f, UnitType::Undefined), ValueUnit(0.0f, UnitType::Undefined)};
float z = 0.0f;
bool operator==(const TransformOrigin &other) const
{
return xy[0] == other.xy[0] && xy[1] == other.xy[1] && z == other.z;
}
bool operator!=(const TransformOrigin &other) const
{
return !(*this == other);
}
bool isSet() const
{
return xy[0].value != 0.0f || xy[0].unit != UnitType::Undefined || xy[1].value != 0.0f ||
xy[1].unit != UnitType::Undefined || z != 0.0f;
}
#ifdef RN_SERIALIZABLE_STATE
/**
* Convert to folly::dynamic.
*/
operator folly::dynamic() const
{
return folly::dynamic::array(xy[0].toDynamic(), xy[1].toDynamic(), z);
}
#endif
};
/*
* Defines transform matrix to apply affine transformations.
*/
struct Transform {
std::vector<TransformOperation> operations{};
std::array<Float, 16> matrix{{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}};
/*
* Given a TransformOperation, return the proper transform.
*/
static Transform FromTransformOperation(
TransformOperation transformOperation,
const Size &size,
const Transform &transform = Transform::Identity());
static TransformOperation DefaultTransformOperation(TransformOperationType type);
/*
* Returns the identity transform (`[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]`).
*/
static Transform Identity() noexcept;
/*
* Returns the vertival inversion transform (`[1 0 0 0; 0 -1 0 0; 0 0 1 0; 0 0
* 0 1]`).
*/
static Transform VerticalInversion() noexcept;
/*
* Returns the horizontal inversion transform (`[-1 0 0 0; 0 1 0 0; 0 0 1 0; 0
* 0 0 1]`).
*/
static Transform HorizontalInversion() noexcept;
/*
* Returns a Perspective transform.
*/
static Transform Perspective(Float perspective) noexcept;
/*
* Returns a Scale transform.
*/
static Transform Scale(Float factorX, Float factorY, Float factorZ) noexcept;
/*
* Returns a Translate transform.
*/
static Transform Translate(Float x, Float y, Float z) noexcept;
/*
* Returns a Skew transform.
*/
static Transform Skew(Float x, Float y) noexcept;
/*
* Returns a transform that rotates by `angle` radians along the given axis.
*/
static Transform RotateX(Float radians) noexcept;
static Transform RotateY(Float radians) noexcept;
static Transform RotateZ(Float radians) noexcept;
static Transform Rotate(Float angleX, Float angleY, Float angleZ) noexcept;
/**
* Perform an interpolation between lhs and rhs, given progress.
* This first decomposes the matrices into translation, scale, and rotation,
* performs slerp between the two rotations, and a linear interpolation
* of scale and translation.
*
* @param animationProgress of the animation
* @param lhs start of the interpolation
* @param rhs end of the interpolation
* @return the Transformation
*/
static Transform Interpolate(Float animationProgress, const Transform &lhs, const Transform &rhs, const Size &size);
static bool isVerticalInversion(const Transform &transform) noexcept;
static bool isHorizontalInversion(const Transform &transform) noexcept;
/*
* Equality operators.
*/
bool operator==(const Transform &rhs) const noexcept;
bool operator!=(const Transform &rhs) const noexcept;
/*
* Matrix subscript.
*/
Float &at(int i, int j) noexcept;
const Float &at(int i, int j) const noexcept;
/*
* Concatenates (multiplies) transform matrices.
*/
Transform operator*(const Transform &rhs) const;
Rect applyWithCenter(const Rect &rect, const Point &center) const;
/**
* Convert to folly::dynamic.
*/
#ifdef ANDROID
operator folly::dynamic() const
{
return folly::dynamic::array(
matrix[0],
matrix[1],
matrix[2],
matrix[3],
matrix[4],
matrix[5],
matrix[6],
matrix[7],
matrix[8],
matrix[9],
matrix[10],
matrix[11],
matrix[12],
matrix[13],
matrix[14],
matrix[15]);
}
#endif
};
/*
* Applies transformation to the given point.
*/
Point operator*(const Point &point, const Transform &transform);
/*
* Applies transformation to the given size.
*/
Size operator*(const Size &size, const Transform &transform);
/*
* Applies transformation to the given rect.
* ONLY SUPPORTS scale and translation transformation.
*/
Rect operator*(const Rect &rect, const Transform &transform);
/*
* Applies transformation to the given EdgeInsets.
* ONLY SUPPORTS scale transformation.
*/
EdgeInsets operator*(const EdgeInsets &edgeInsets, const Transform &transform);
Vector operator*(const Transform &transform, const Vector &vector);
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::Transform> {
size_t operator()(const facebook::react::Transform &transform) const
{
return facebook::react::hash_combine(
transform.matrix[0],
transform.matrix[1],
transform.matrix[2],
transform.matrix[3],
transform.matrix[4],
transform.matrix[5],
transform.matrix[6],
transform.matrix[7],
transform.matrix[8],
transform.matrix[9],
transform.matrix[10],
transform.matrix[11],
transform.matrix[12],
transform.matrix[13],
transform.matrix[14],
transform.matrix[15]);
}
};
} // namespace std

View File

@@ -0,0 +1,41 @@
/*
* 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.
*/
#include "ValueUnit.h"
#include "DoubleConversions.h"
namespace facebook::react {
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic ValueUnit::toDynamic() const {
switch (unit) {
case UnitType::Undefined:
return nullptr;
case UnitType::Point:
return value;
case UnitType::Percent:
return react::toString(value, '%');
default:
return nullptr;
}
}
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
std::string ValueUnit::toString() const {
if (unit == UnitType::Percent) {
return react::toString(value, '%');
} else if (unit == UnitType::Point) {
return react::toString(value, '\0') + "px";
} else {
return "undefined";
}
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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
#include <react/renderer/debug/flags.h>
#include <string>
#ifdef RN_SERIALIZABLE_STATE
#include <folly/dynamic.h>
#endif
namespace facebook::react {
enum class UnitType {
Undefined,
Point,
Percent,
};
struct ValueUnit {
float value{0.0f};
UnitType unit{UnitType::Undefined};
ValueUnit() = default;
ValueUnit(float v, UnitType u) : value(v), unit(u) {}
bool operator==(const ValueUnit &other) const
{
return value == other.value && unit == other.unit;
}
bool operator!=(const ValueUnit &other) const
{
return !(*this == other);
}
constexpr float resolve(float referenceLength) const
{
switch (unit) {
case UnitType::Point:
return value;
case UnitType::Percent:
return value * referenceLength * 0.01f;
case UnitType::Undefined:
default:
return 0.0f;
}
}
constexpr operator bool() const
{
return unit != UnitType::Undefined;
}
#ifdef RN_SERIALIZABLE_STATE
folly::dynamic toDynamic() const;
#endif
#if RN_DEBUG_STRING_CONVERTIBLE
std::string toString() const;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
struct Vector {
Float x{0};
Float y{0};
Float z{0};
Float w{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* 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
#include <react/renderer/core/graphicsConversions.h>
// This file belongs to the React-graphics module.
// This file also used to have a reference to two files that are located in the
// react/renderer/core folder. That folder belongs to a module that is called
// React-Fabric.
// The React-Fabric module declares an explicit dependency on
// React-graphics. Including those files in a React-graphics' file created a
// circular dependency because React-Fabric was explicitly depending on
// React-graphics, which was implicitly depending on React-Fabric. We break that
// dependency by moving the old `graphics/conversions.h` file to the
// React-Fabric module and renaming it `core/graphicsConversions.h`.
#warning \
"[DEPRECATION] `graphics/conversions.h` is deprecated and will be removed in the future. \
If this warning appears due to a library, please open an issue in that library, and ask for an update. \
Please, replace the `#include <react/renderer/graphics/conversions.h>` statements \
with `#include <react/renderer/core/graphicsConversions.h>`."

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/graphics/Color.h>
#include <react/utils/ContextContainer.h>
#pragma once
namespace facebook::react {
using parsePlatformColorFn = SharedColor (*)(const ContextContainer &, int32_t, const RawValue &);
inline void fromRawValueShared(
const ContextContainer &contextContainer,
int32_t surfaceId,
const RawValue &value,
SharedColor &result,
parsePlatformColorFn parsePlatformColor)
{
ColorComponents colorComponents = {0, 0, 0, 0};
if (value.hasType<int>()) {
auto argb = (int64_t)value;
auto ratio = 255.f;
colorComponents.alpha = ((argb >> 24) & 0xFF) / ratio;
colorComponents.red = ((argb >> 16) & 0xFF) / ratio;
colorComponents.green = ((argb >> 8) & 0xFF) / ratio;
colorComponents.blue = (argb & 0xFF) / ratio;
result = colorFromComponents(colorComponents);
} else if (value.hasType<std::vector<float>>()) {
auto items = (std::vector<float>)value;
auto length = items.size();
react_native_expect(length == 3 || length == 4);
colorComponents.red = items.at(0);
colorComponents.green = items.at(1);
colorComponents.blue = items.at(2);
colorComponents.alpha = length == 4 ? items.at(3) : 1.0f;
result = colorFromComponents(colorComponents);
} else {
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
const auto &items = (std::unordered_map<std::string, RawValue>)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(contextContainer, surfaceId, value);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* 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
#include <limits>
namespace facebook::react {
/*
* Exact type of float numbers which ideally should match a type behing
* platform- and chip-architecture-specific float type.
*/
using Float = float;
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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
#include <react/renderer/graphics/ColorComponents.h>
#include <cmath>
namespace facebook::react {
using Color = int32_t;
namespace HostPlatformColor {
constexpr facebook::react::Color UndefinedColor = 0;
}
inline Color hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return (a & 0xff) << 24 | (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff);
}
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);
}
inline ColorComponents colorComponentsFromHostPlatformColor(Color color)
{
float ratio = 255;
return ColorComponents{
.red = (float)((color >> 16) & 0xff) / ratio,
.green = (float)((color >> 8) & 0xff) / ratio,
.blue = (float)((color >> 0) & 0xff) / ratio,
.alpha = (float)((color >> 24) & 0xff) / ratio};
}
inline float alphaFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 24) & 0xff);
}
inline float redFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 16) & 0xff);
}
inline float greenFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 8) & 0xff);
}
inline float blueFromHostPlatformColor(Color color)
{
return static_cast<uint8_t>((color >> 0) & 0xff);
}
inline bool hostPlatformColorIsColorMeaningful(Color color) noexcept
{
return alphaFromHostPlatformColor(color) > 0;
}
} // namespace facebook::react

View File

@@ -0,0 +1,85 @@
/*
* 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
#include "configurePlatformColorCacheInvalidationHook.h"
#include <fbjni/fbjni.h>
#include <folly/container/EvictingCacheMap.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/fromRawValueShared.h>
#include <react/utils/ContextContainer.h>
#include <functional>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace facebook::react {
inline size_t hashGetColourArguments(int32_t surfaceId, const std::vector<std::string> &resourcePaths)
{
size_t seed = std::hash<int32_t>{}(surfaceId);
for (const auto &path : resourcePaths) {
seed ^= std::hash<std::string>{}(path) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
return seed;
}
inline SharedColor
parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value)
{
Color color = 0;
if (value.hasType<std::unordered_map<std::string, std::vector<std::string>>>()) {
auto map = (std::unordered_map<std::string, std::vector<std::string>>)value;
auto &resourcePaths = map["resource_paths"];
// JNI calls are time consuming. Let's cache results here to avoid
// unnecessary calls.
static std::mutex getColorCacheMutex;
static folly::EvictingCacheMap<size_t, Color> getColorCache(64);
// Listen for appearance changes, which should invalidate the cache
static std::once_flag setupCacheInvalidation;
std::call_once(setupCacheInvalidation, configurePlatformColorCacheInvalidationHook, [&] {
std::scoped_lock lock(getColorCacheMutex);
getColorCache.clear();
});
auto hash = hashGetColourArguments(surfaceId, resourcePaths);
{
std::scoped_lock lock(getColorCacheMutex);
auto iterator = getColorCache.find(hash);
if (iterator != getColorCache.end()) {
color = iterator->second;
} else {
const auto &fabricUIManager = contextContainer.at<jni::global_ref<jobject>>("FabricUIManager");
static auto getColorFromJava =
fabricUIManager->getClass()->getMethod<jint(jint, jni::JArrayClass<jni::JString>)>("getColor");
auto javaResourcePaths = jni::JArrayClass<jni::JString>::newArray(resourcePaths.size());
for (int i = 0; i < resourcePaths.size(); i++) {
javaResourcePaths->setElement(i, *jni::make_jstring(resourcePaths[i]));
}
color = getColorFromJava(fabricUIManager, surfaceId, *javaResourcePaths);
getColorCache.set(hash, color);
}
}
}
return color;
}
inline void
fromRawValue(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result)
{
fromRawValueShared(contextContainer, surfaceId, value, result, parsePlatformColor);
}
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#include "configurePlatformColorCacheInvalidationHook.h"
#include <fbjni/NativeRunnable.h>
#include <fbjni/fbjni.h>
namespace facebook::react {
void configurePlatformColorCacheInvalidationHook(std::function<void()>&& hook) {
auto appearanceModuleClass = jni::findClassLocal(
"com/facebook/react/modules/appearance/AppearanceModule");
if (appearanceModuleClass) {
auto callbackField =
appearanceModuleClass->getStaticField<jni::JRunnable::javaobject>(
"invalidatePlatformColorCache");
jni::local_ref<jni::JRunnable> invalidationCallback =
jni::JNativeRunnable::newObjectCxxArgs(std::move(hook));
appearanceModuleClass->setStaticFieldValue(
callbackField, invalidationCallback.get());
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#include <functional>
namespace facebook::react {
void configurePlatformColorCacheInvalidationHook(std::function<void()> &&hook);
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* 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
#include <limits>
namespace facebook::react {
/*
* Exact type of float numbers which ideally should match a type behing
* platform- and chip-architecture-specific float type.
*/
using Float = float;
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* 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
#include <react/renderer/graphics/ColorComponents.h>
#include <cmath>
#include <cstdint>
namespace facebook::react {
using Color = int32_t;
namespace HostPlatformColor {
constexpr facebook::react::Color UndefinedColor = 0;
}
inline Color hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return (a & 0xff) << 24 | (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff);
}
inline Color hostPlatformColorFromComponents(ColorComponents components)
{
float ratio = 255;
return hostPlatformColorFromRGBA(
static_cast<uint8_t>(std::round(components.red * ratio)),
static_cast<uint8_t>(std::round(components.green * ratio)),
static_cast<uint8_t>(std::round(components.blue * ratio)),
static_cast<uint8_t>(std::round(components.alpha * ratio)));
}
inline float alphaFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 24) & 0xff);
}
inline float redFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 16) & 0xff);
}
inline float greenFromHostPlatformColor(Color color)
{
return static_cast<float>((color >> 8) & 0xff);
}
inline float blueFromHostPlatformColor(Color color)
{
return static_cast<uint8_t>((color >> 0) & 0xff);
}
inline bool hostPlatformColorIsColorMeaningful(Color color) noexcept
{
return alphaFromHostPlatformColor(color) > 0;
}
inline ColorComponents colorComponentsFromHostPlatformColor(Color color)
{
float ratio = 255;
return ColorComponents{
.red = static_cast<float>(redFromHostPlatformColor(color)) / ratio,
.green = static_cast<float>(greenFromHostPlatformColor(color)) / ratio,
.blue = static_cast<float>(blueFromHostPlatformColor(color)) / ratio,
.alpha = static_cast<float>(alphaFromHostPlatformColor(color)) / ratio};
}
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* 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
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/fromRawValueShared.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
inline SharedColor
parsePlatformColor(const ContextContainer & /*contextContainer*/, int32_t /*surfaceId*/, const RawValue & /*value*/)
{
float alpha = 0;
float red = 0;
float green = 0;
float blue = 0;
return {colorFromComponents({red, green, blue, alpha})};
}
inline void
fromRawValue(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result)
{
fromRawValueShared(contextContainer, surfaceId, value, result, parsePlatformColor);
}
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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
#include <CoreGraphics/CoreGraphics.h>
#include <limits>
namespace facebook::react {
/*
* Exact type of float numbers which ideally should match a type behing
* platform- and chip-architecture-specific float type.
*/
using Float = CGFloat;
} // namespace facebook::react

View File

@@ -0,0 +1,129 @@
/*
* 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
#include <react/renderer/graphics/ColorComponents.h>
#include <react/utils/hash_combine.h>
#include <cmath>
namespace facebook::react {
struct DynamicColor {
int32_t lightColor = 0;
int32_t darkColor = 0;
int32_t highContrastLightColor = 0;
int32_t highContrastDarkColor = 0;
};
struct Color {
Color(int32_t color);
Color(const DynamicColor &dynamicColor);
Color(const ColorComponents &components);
Color() : uiColor_(nullptr) {};
int32_t getColor() const;
std::size_t getUIColorHash() const;
static Color createSemanticColor(std::vector<std::string> &semanticItems);
std::shared_ptr<void> getUIColor() const
{
return uiColor_;
}
float getChannel(int channelId) const;
ColorComponents getColorComponents() const
{
float ratio = 255;
int32_t primitiveColor = getColor();
return ColorComponents{
.red = (float)((primitiveColor >> 16) & 0xff) / ratio,
.green = (float)((primitiveColor >> 8) & 0xff) / ratio,
.blue = (float)((primitiveColor >> 0) & 0xff) / ratio,
.alpha = (float)((primitiveColor >> 24) & 0xff) / ratio};
}
bool operator==(const Color &other) const;
bool operator!=(const Color &other) const;
operator int32_t() const
{
return getColor();
}
private:
Color(std::shared_ptr<void> uiColor);
std::shared_ptr<void> uiColor_;
std::size_t uiColorHashValue_;
};
namespace HostPlatformColor {
#if defined(__clang__)
#define NO_DESTROY [[clang::no_destroy]]
#else
#define NO_DESTROY
#endif
NO_DESTROY static const facebook::react::Color UndefinedColor = Color();
} // namespace HostPlatformColor
inline Color hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
float ratio = 255;
const auto colorComponents = ColorComponents{
.red = r / ratio,
.green = g / ratio,
.blue = b / ratio,
.alpha = a / ratio,
};
return Color(colorComponents);
}
inline Color hostPlatformColorFromComponents(ColorComponents components)
{
return Color(components);
}
inline ColorComponents colorComponentsFromHostPlatformColor(Color color)
{
return color.getColorComponents();
}
inline float alphaFromHostPlatformColor(Color color)
{
return color.getChannel(3) * 255;
}
inline float redFromHostPlatformColor(Color color)
{
return color.getChannel(0) * 255;
}
inline float greenFromHostPlatformColor(Color color)
{
return color.getChannel(1) * 255;
}
inline float blueFromHostPlatformColor(Color color)
{
return color.getChannel(2) * 255;
}
inline bool hostPlatformColorIsColorMeaningful(Color color) noexcept
{
return alphaFromHostPlatformColor(color) > 0;
}
} // namespace facebook::react
template <>
struct std::hash<facebook::react::Color> {
size_t operator()(const facebook::react::Color &color) const
{
return color.getUIColorHash();
}
};

View File

@@ -0,0 +1,246 @@
/*
* 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 "HostPlatformColor.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/utils/ManagedObjectWrapper.h>
#import <string>
using namespace facebook::react;
NS_ASSUME_NONNULL_BEGIN
namespace facebook::react {
namespace {
bool UIColorIsP3ColorSpace(const std::shared_ptr<void> &uiColor)
{
UIColor *color = unwrapManagedObject(uiColor);
CGColorSpaceRef colorSpace = CGColorGetColorSpace(color.CGColor);
if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB) {
CFStringRef name = CGColorSpaceGetName(colorSpace);
if (name != NULL && (CFEqual(name, kCGColorSpaceDisplayP3) != 0u)) {
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;
UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a];
return color;
}
UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor)
{
int32_t light = dynamicColor.lightColor;
int32_t dark = dynamicColor.darkColor;
int32_t highContrastLight = dynamicColor.highContrastLightColor;
int32_t highContrastDark = dynamicColor.highContrastDarkColor;
UIColor *lightColor = UIColorFromInt32(light);
UIColor *darkColor = UIColorFromInt32(dark);
UIColor *highContrastLightColor = UIColorFromInt32(highContrastLight);
UIColor *highContrastDarkColor = UIColorFromInt32(highContrastDark);
if (lightColor != nil && darkColor != nil) {
UIColor *color = [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(UITraitCollection *_Nonnull collection) {
if (collection.userInterfaceStyle == UIUserInterfaceStyleDark) {
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastDark != 0) {
return highContrastDarkColor;
} else {
return darkColor;
}
} else {
if (collection.accessibilityContrast == UIAccessibilityContrastHigh && highContrastLight != 0) {
return highContrastLightColor;
} else {
return lightColor;
}
}
}];
return color;
} else {
return nil;
}
return nil;
}
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 ColorFromColorComponents(
{.red = (float)rgba[0], .green = (float)rgba[1], .blue = (float)rgba[2], .alpha = (float)rgba[3]});
}
int32_t ColorFromUIColorForSpecificTraitCollection(
const std::shared_ptr<void> &uiColor,
UITraitCollection *traitCollection)
{
UIColor *color = (UIColor *)unwrapManagedObject(uiColor);
if (color != nullptr) {
color = [color resolvedColorWithTraitCollection:traitCollection];
return ColorFromUIColor(color);
}
return 0;
}
int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
{
return ColorFromUIColorForSpecificTraitCollection(uiColor, [UITraitCollection currentTraitCollection]);
}
UIColor *_Nullable UIColorFromComponentsColor(const facebook::react::ColorComponents &components)
{
UIColor *uiColor = nil;
if (components.colorSpace == ColorSpace::DisplayP3) {
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];
}
return uiColor;
}
std::size_t hashFromUIColor(const std::shared_ptr<void> &uiColor)
{
if (uiColor == nullptr) {
return 0;
}
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)
{
uiColor_ = wrapManagedObject(UIColorFromInt32(color));
uiColorHashValue_ = facebook::react::hash_combine(color, 0);
}
Color::Color(const DynamicColor &dynamicColor)
{
uiColor_ = wrapManagedObject(UIColorFromDynamicColor(dynamicColor));
uiColorHashValue_ = facebook::react::hash_combine(
dynamicColor.darkColor,
dynamicColor.lightColor,
dynamicColor.highContrastDarkColor,
dynamicColor.highContrastLightColor,
0);
}
Color::Color(const ColorComponents &components)
{
uiColor_ = wrapManagedObject(UIColorFromComponentsColor(components));
uiColorHashValue_ = facebook::react::hash_combine(
ColorFromColorComponents(components), components.colorSpace == ColorSpace::DisplayP3);
}
Color::Color(std::shared_ptr<void> uiColor)
{
UIColor *color = ((UIColor *)unwrapManagedObject(uiColor));
if (color != nullptr) {
auto colorHash = hashFromUIColor(uiColor);
uiColorHashValue_ = colorHash;
}
uiColor_ = std::move(uiColor);
}
bool Color::operator==(const Color &other) const
{
return (!uiColor_ && !other.uiColor_) ||
(uiColor_ && other.uiColor_ && (uiColorHashValue_ == other.uiColorHashValue_));
}
bool Color::operator!=(const Color &other) const
{
return !(*this == other);
}
int32_t Color::getColor() const
{
return ColorFromUIColor(uiColor_);
}
float Color::getChannel(int channelId) const
{
CGFloat rgba[4];
UIColor *color = (__bridge UIColor *)getUIColor().get();
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
return static_cast<float>(rgba[channelId]);
}
std::size_t Color::getUIColorHash() const
{
return uiColorHashValue_;
}
Color Color::createSemanticColor(std::vector<std::string> &semanticItems)
{
auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems);
return Color(wrapManagedObject(semanticColor));
}
} // namespace facebook::react
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,26 @@
/*
* 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
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/fromRawValueShared.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value);
inline void
fromRawValue(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result)
{
fromRawValueShared(contextContainer, surfaceId, value, result, parsePlatformColor);
}
} // namespace facebook::react

View File

@@ -0,0 +1,74 @@
/*
* 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 "PlatformColorParser.h"
#import <react/renderer/core/RawValue.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/utils/ManagedObjectWrapper.h>
#import <string>
#import <unordered_map>
using namespace facebook::react;
NS_ASSUME_NONNULL_BEGIN
namespace facebook::react {
inline facebook::react::SharedColor RCTPlatformColorComponentsFromDynamicItems(
const facebook::react::ContextContainer &contextContainer,
int32_t surfaceId,
std::unordered_map<std::string, facebook::react::RawValue> &dynamicItems)
{
SharedColor lightSharedColor{};
SharedColor darkSharedColor{};
SharedColor highContrastLightSharedColor{};
SharedColor highContrastDarkSharedColor{};
if (dynamicItems.count("light") != 0u) {
fromRawValue(contextContainer, surfaceId, dynamicItems.at("light"), lightSharedColor);
}
if (dynamicItems.count("dark") != 0u) {
fromRawValue(contextContainer, surfaceId, dynamicItems.at("dark"), darkSharedColor);
}
if (dynamicItems.count("highContrastLight") != 0u) {
fromRawValue(contextContainer, surfaceId, dynamicItems.at("highContrastLight"), highContrastLightSharedColor);
}
if (dynamicItems.count("highContrastDark") != 0u) {
fromRawValue(contextContainer, surfaceId, dynamicItems.at("highContrastDark"), highContrastDarkSharedColor);
}
Color color = Color(
DynamicColor{
.lightColor = (*lightSharedColor).getColor(),
.darkColor = (*darkSharedColor).getColor(),
.highContrastLightColor = (*highContrastLightSharedColor).getColor(),
.highContrastDarkColor = (*highContrastDarkSharedColor).getColor()});
return SharedColor(color);
}
SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value)
{
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
auto items = (std::unordered_map<std::string, RawValue>)value;
if (items.find("semantic") != items.end() && items.at("semantic").hasType<std::vector<std::string>>()) {
auto semanticItems = (std::vector<std::string>)items.at("semantic");
return SharedColor(Color::createSemanticColor(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(contextContainer, surfaceId, dynamicItems);
}
}
return clearColor();
}
} // namespace facebook::react
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,20 @@
/*
* 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 <UIKit/UIKit.h>
#import <vector>
namespace facebook::react {
struct ColorComponents;
struct Color;
} // namespace facebook::react
facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(std::vector<std::string> &semanticItems);
UIColor *RCTPlatformColorFromSemanticItems(std::vector<std::string> &semanticItems);
UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color);

View File

@@ -0,0 +1,217 @@
/*
* 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 "RCTPlatformColorUtils.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <react/utils/ManagedObjectWrapper.h>
#include <string>
NS_ASSUME_NONNULL_BEGIN
static NSString *const kColorSuffix = @"Color";
static NSString *const kFallbackARGBKey = @"fallback-argb";
static NSDictionary<NSString *, NSDictionary *> *_PlatformColorSelectorsDict()
{
static NSDictionary<NSString *, NSDictionary *> *dict;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^(void) {
dict = @{
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
// Label Colors
@"label" : @{
kFallbackARGBKey : @(0xFF000000) // iOS 13.0
},
@"secondaryLabel" : @{
kFallbackARGBKey : @(0x993c3c43) // iOS 13.0
},
@"tertiaryLabel" : @{
kFallbackARGBKey : @(0x4c3c3c43) // iOS 13.0
},
@"quaternaryLabel" : @{
kFallbackARGBKey : @(0x2d3c3c43) // iOS 13.0
},
// Fill Colors
@"systemFill" : @{
kFallbackARGBKey : @(0x33787880) // iOS 13.0
},
@"secondarySystemFill" : @{
kFallbackARGBKey : @(0x28787880) // iOS 13.0
},
@"tertiarySystemFill" : @{
kFallbackARGBKey : @(0x1e767680) // iOS 13.0
},
@"quaternarySystemFill" : @{
kFallbackARGBKey : @(0x14747480) // iOS 13.0
},
// Text Colors
@"placeholderText" : @{
kFallbackARGBKey : @(0x4c3c3c43) // iOS 13.0
},
// Standard Content Background Colors
@"systemBackground" : @{
kFallbackARGBKey : @(0xFFffffff) // iOS 13.0
},
@"secondarySystemBackground" : @{
kFallbackARGBKey : @(0xFFf2f2f7) // iOS 13.0
},
@"tertiarySystemBackground" : @{
kFallbackARGBKey : @(0xFFffffff) // iOS 13.0
},
// Grouped Content Background Colors
@"systemGroupedBackground" : @{
kFallbackARGBKey : @(0xFFf2f2f7) // iOS 13.0
},
@"secondarySystemGroupedBackground" : @{
kFallbackARGBKey : @(0xFFffffff) // iOS 13.0
},
@"tertiarySystemGroupedBackground" : @{
kFallbackARGBKey : @(0xFFf2f2f7) // iOS 13.0
},
// Separator Colors
@"separator" : @{
kFallbackARGBKey : @(0x493c3c43) // iOS 13.0
},
@"opaqueSeparator" : @{
kFallbackARGBKey : @(0xFFc6c6c8) // iOS 13.0
},
// Link Color
@"link" : @{
kFallbackARGBKey : @(0xFF007aff) // iOS 13.0
},
// Nonadaptable Colors
@"darkText" : @{},
@"lightText" : @{},
// https://developer.apple.com/documentation/uikit/uicolor/standard_colors
// Adaptable Colors
@"systemBlue" : @{},
@"systemBrown" : @{
kFallbackARGBKey : @(0xFFa2845e) // iOS 13.0
},
@"systemCyan" : @{},
@"systemGreen" : @{},
@"systemIndigo" : @{
kFallbackARGBKey : @(0xFF5856d6) // iOS 13.0
},
@"systemMint" : @{},
@"systemOrange" : @{},
@"systemPink" : @{},
@"systemPurple" : @{},
@"systemRed" : @{},
@"systemTeal" : @{},
@"systemYellow" : @{},
// Adaptable Gray Colors
@"systemGray" : @{},
@"systemGray2" : @{
kFallbackARGBKey : @(0xFFaeaeb2) // iOS 13.0
},
@"systemGray3" : @{
kFallbackARGBKey : @(0xFFc7c7cc) // iOS 13.0
},
@"systemGray4" : @{
kFallbackARGBKey : @(0xFFd1d1d6) // iOS 13.0
},
@"systemGray5" : @{
kFallbackARGBKey : @(0xFFe5e5ea) // iOS 13.0
},
@"systemGray6" : @{
kFallbackARGBKey : @(0xFFf2f2f7) // iOS 13.0
},
// Transparent Color
@"clear" : @{
kFallbackARGBKey : @(0x00000000) // iOS 13.0
},
};
});
return dict;
}
static UIColor *_UIColorFromHexValue(NSNumber *hexValue)
{
NSUInteger hexIntValue = [hexValue unsignedIntegerValue];
CGFloat red = ((CGFloat)((hexIntValue & 0xFF000000) >> 24)) / 255.0;
CGFloat green = ((CGFloat)((hexIntValue & 0xFF0000) >> 16)) / 255.0;
CGFloat blue = ((CGFloat)((hexIntValue & 0xFF00) >> 8)) / 255.0;
CGFloat alpha = ((CGFloat)(hexIntValue & 0xFF)) / 255.0;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
static UIColor *_Nullable _UIColorFromSemanticString(NSString *semanticString)
{
NSString *platformColorString = [semanticString hasSuffix:kColorSuffix]
? [semanticString substringToIndex:[semanticString length] - [kColorSuffix length]]
: semanticString;
NSDictionary<NSString *, NSDictionary *> *platformColorSelectorsDict = _PlatformColorSelectorsDict();
NSDictionary<NSString *, id> *colorInfo = platformColorSelectorsDict[platformColorString];
if (colorInfo != nullptr) {
SEL objcColorSelector = NSSelectorFromString([platformColorString stringByAppendingString:kColorSuffix]);
if (![UIColor respondsToSelector:objcColorSelector]) {
NSNumber *fallbackRGB = colorInfo[kFallbackARGBKey];
if (fallbackRGB != nullptr) {
return _UIColorFromHexValue(fallbackRGB);
}
} else {
Class uiColorClass = [UIColor class];
IMP imp = [uiColorClass methodForSelector:objcColorSelector];
id (*getUIColor)(id, SEL) = ((id (*)(id, SEL))imp);
id colorObject = getUIColor(uiColorClass, objcColorSelector);
if ([colorObject isKindOfClass:[UIColor class]]) {
return colorObject;
}
}
}
return nil;
}
static inline NSString *_NSStringFromCString(
const std::string &string,
const NSStringEncoding &encoding = NSUTF8StringEncoding)
{
return [NSString stringWithCString:string.c_str() encoding:encoding];
}
static inline facebook::react::ColorComponents _ColorComponentsFromUIColor(UIColor *color)
{
CGFloat rgba[4];
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
return {.red = (float)rgba[0], .green = (float)rgba[1], .blue = (float)rgba[2], .alpha = (float)rgba[3]};
}
facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(std::vector<std::string> &semanticItems)
{
return _ColorComponentsFromUIColor(RCTPlatformColorFromSemanticItems(semanticItems));
}
UIColor *RCTPlatformColorFromSemanticItems(std::vector<std::string> &semanticItems)
{
for (const auto &semanticCString : semanticItems) {
NSString *semanticNSString = _NSStringFromCString(semanticCString);
UIColor *uiColor = [UIColor colorNamed:semanticNSString];
if (uiColor != nil) {
return uiColor;
}
uiColor = _UIColorFromSemanticString(semanticNSString);
if (uiColor != nil) {
return uiColor;
}
}
return UIColor.clearColor;
}
UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color)
{
return (UIColor *)facebook::react::unwrapManagedObject(color.getUIColor());
}
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,93 @@
/*
* 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
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
/*
* Convenience functions for rounding float values to be aligned with a device
* pixel grid.
*
* Usage example:
* auto scaleFactor = Float{3};
* auto value = Float{42.9001};
* auto crispValue = roundToPixel<&std::ceil>(value, scaleFactor);
* auto size = Size{value, value + 42.0};
* auto crispSize = roundToPixel<&std::ceil>(size, scaleFactor);
*/
template <Float (*RoundingFunction)(Float)>
Float roundToPixel(Float value, Float scaleFactor)
{
return RoundingFunction(value * scaleFactor) / scaleFactor;
}
template <Float (*RoundingFunction)(Float)>
Point roundToPixel(Point value, Float scaleFactor)
{
return Point{
roundToPixel<RoundingFunction>(value.x, scaleFactor), roundToPixel<RoundingFunction>(value.y, scaleFactor)};
}
template <Float (*RoundingFunction)(Float)>
Size roundToPixel(Size value, Float scaleFactor)
{
return Size{
roundToPixel<RoundingFunction>(value.width, scaleFactor),
roundToPixel<RoundingFunction>(value.height, scaleFactor)};
}
template <Float (*RoundingFunction)(Float)>
Rect roundToPixel(Rect value, Float scaleFactor)
{
return Rect{roundToPixel<RoundingFunction>(value.origin), roundToPixel<RoundingFunction>(value.size)};
}
/*
* GCC-based Android NDK does not have rounding functions as part of STL.
*/
inline float round(float value) noexcept
{
return ::roundf(value);
}
inline double round(double value) noexcept
{
return ::round(value);
}
inline long double round(long double value) noexcept
{
return ::roundl(value);
}
inline float ceil(float value) noexcept
{
return ::ceilf(value);
}
inline double ceil(double value) noexcept
{
return ::ceil(value);
}
inline long double ceil(long double value) noexcept
{
return ::ceill(value);
}
inline float floor(float value) noexcept
{
return ::floorf(value);
}
inline double floor(double value) noexcept
{
return ::floor(value);
}
inline long double floor(long double value) noexcept
{
return ::floorl(value);
}
} // namespace facebook::react

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
#include <react/renderer/graphics/Color.h>
#include <gtest/gtest.h>
TEST(ColorTest, testColorConversion) {
using namespace facebook::react;
{
auto color = colorFromRGBA(255, 0, 15, 17);
EXPECT_EQ(alphaFromColor(color), 17);
EXPECT_EQ(redFromColor(color), 255);
EXPECT_EQ(greenFromColor(color), 0);
EXPECT_EQ(blueFromColor(color), 15);
}
{
auto color = colorFromComponents(
{.red = 0.1f, .green = 0.2f, .blue = 0, .alpha = 0.3f});
EXPECT_EQ(alphaFromColor(color), std::round(255 * 0.3f));
EXPECT_EQ(redFromColor(color), std::round(255 * 0.1f));
EXPECT_EQ(greenFromColor(color), 255 * 0.2f);
EXPECT_EQ(blueFromColor(color), 0.f);
auto colorComponents = colorComponentsFromColor(color);
EXPECT_EQ(std::round(colorComponents.alpha * 10) / 10.f, 0.3f);
EXPECT_EQ(std::round(colorComponents.red * 10) / 10.f, 0.1f);
EXPECT_EQ(std::round(colorComponents.green * 10) / 10.f, 0.2f);
EXPECT_EQ(std::round(colorComponents.blue * 10) / 10.f, 0);
}
}

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
#include <memory>
#include <gtest/gtest.h>
TEST(GraphicsTest, testSomething) {
// TODO
}

View File

@@ -0,0 +1,87 @@
/*
* 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.
*/
#include <react/renderer/graphics/Point.h>
#include <gtest/gtest.h>
using namespace facebook::react;
TEST(PointTest, testConstructor) {
auto point = facebook::react::Point{1, 2};
EXPECT_EQ(point.x, 1);
EXPECT_EQ(point.y, 2);
}
TEST(PointTest, testPlusEqualOperator) {
auto point = facebook::react::Point{1, 2};
point += facebook::react::Point{3, 4};
EXPECT_EQ(point.x, 4);
EXPECT_EQ(point.y, 6);
}
TEST(PointTest, testMinusEqualOperator) {
auto point = facebook::react::Point{1, 2};
point -= facebook::react::Point{3, 4};
EXPECT_EQ(point.x, -2);
EXPECT_EQ(point.y, -2);
}
TEST(PointTest, testMultiplyEqualOperator) {
auto point = facebook::react::Point{1, 2};
point *= facebook::react::Point{3, 4};
EXPECT_EQ(point.x, 3);
EXPECT_EQ(point.y, 8);
}
TEST(PointTest, testPlusOperator) {
auto newPoint = facebook::react::Point{1, 2} + facebook::react::Point{3, 4};
EXPECT_EQ(newPoint.x, 4);
EXPECT_EQ(newPoint.y, 6);
}
TEST(PointTest, testMinusOperator) {
auto newPoint = facebook::react::Point{1, 2} - facebook::react::Point{3, 4};
EXPECT_EQ(newPoint.x, -2);
EXPECT_EQ(newPoint.y, -2);
}
TEST(PointTest, testEqualOperator) {
auto pointA = facebook::react::Point{1, 2};
auto pointB = facebook::react::Point{1, 2};
auto pointC = facebook::react::Point{1, 3};
auto pointD = facebook::react::Point{2, 2};
EXPECT_TRUE(pointA == pointB);
EXPECT_FALSE(pointA == pointC);
EXPECT_FALSE(pointA == pointD);
}
TEST(PointTest, testUnequalOperator) {
auto pointA = facebook::react::Point{1, 2};
auto pointB = facebook::react::Point{1, 2};
auto pointC = facebook::react::Point{1, 3};
auto pointD = facebook::react::Point{2, 2};
EXPECT_FALSE(pointA != pointB);
EXPECT_TRUE(pointA != pointC);
EXPECT_TRUE(pointA != pointD);
}
TEST(PointTest, testMinusUnaryOperator) {
auto point = facebook::react::Point{1, 2};
auto negativePoint = -point;
EXPECT_EQ(negativePoint.x, -1);
EXPECT_EQ(negativePoint.y, -2);
}

View File

@@ -0,0 +1,136 @@
/*
* 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.
*/
#include <react/renderer/graphics/Transform.h>
#include <gtest/gtest.h>
#include <cmath>
using namespace facebook::react;
TEST(TransformTest, transformingSize) {
auto size = facebook::react::Size{100, 200};
auto scaledSize = size * Transform::Scale(0.5, 0.5, 1);
EXPECT_EQ(scaledSize.width, 50);
EXPECT_EQ(scaledSize.height, 100);
}
TEST(TransformTest, transformingPoint) {
auto point = facebook::react::Point{100, 200};
auto translatedPoint = point * Transform::Translate(-50, -100, 0);
EXPECT_EQ(translatedPoint.x, 50);
EXPECT_EQ(translatedPoint.y, 100);
}
TEST(TransformTest, fromTransformOperationPercentage) {
auto point = facebook::react::Point{0, 0};
facebook::react::Size size = {120, 200};
auto operation = TransformOperation{
TransformOperationType::Translate,
ValueUnit{50.0f, UnitType::Percent},
ValueUnit{20.0f, UnitType::Percent},
{}};
auto translatedPoint =
point * Transform::FromTransformOperation(operation, size);
EXPECT_EQ(translatedPoint.x, 60);
EXPECT_EQ(translatedPoint.y, 40);
operation = TransformOperation{
TransformOperationType::Translate,
ValueUnit{40.0f, UnitType::Percent},
ValueUnit{20.0f, UnitType::Point},
{}};
translatedPoint = point * Transform::FromTransformOperation(operation, size);
EXPECT_EQ(translatedPoint.x, 48);
EXPECT_EQ(translatedPoint.y, 20);
}
TEST(TransformTest, scalingRect) {
auto point = facebook::react::Point{100, 200};
auto size = facebook::react::Size{300, 400};
auto rect = facebook::react::Rect{point, size};
auto transformedRect = rect * Transform::Scale(0.5, 0.5, 1);
EXPECT_EQ(transformedRect.origin.x, 175);
EXPECT_EQ(transformedRect.origin.y, 300);
EXPECT_EQ(transformedRect.size.width, 150);
EXPECT_EQ(transformedRect.size.height, 200);
}
TEST(TransformTest, scalingRectWithDifferentCenter) {
auto point = facebook::react::Point{100, 200};
auto size = facebook::react::Size{300, 400};
auto rect = facebook::react::Rect{point, size};
auto center = facebook::react::Point{0, 0};
auto transformedRect =
Transform::Scale(0.5, 0.5, 1).applyWithCenter(rect, center);
EXPECT_EQ(transformedRect.origin.x, 50);
EXPECT_EQ(transformedRect.origin.y, 100);
EXPECT_EQ(transformedRect.size.width, 150);
EXPECT_EQ(transformedRect.size.height, 200);
}
TEST(TransformTest, invertingSize) {
auto size = facebook::react::Size{300, 400};
auto transformedSize = size * Transform::VerticalInversion();
EXPECT_EQ(transformedSize.width, 300);
EXPECT_EQ(transformedSize.height, 400);
}
TEST(TransformTest, rotatingRect) {
auto point = facebook::react::Point{10, 10};
auto size = facebook::react::Size{10, 10};
auto rect = facebook::react::Rect{point, size};
auto transformedRect = rect * Transform::RotateZ(M_PI_4);
ASSERT_NEAR(transformedRect.origin.x, 7.9289, 0.0001);
ASSERT_NEAR(transformedRect.origin.y, 7.9289, 0.0001);
ASSERT_NEAR(transformedRect.size.width, 14.1421, 0.0001);
ASSERT_NEAR(transformedRect.size.height, 14.1421, 0.0001);
}
TEST(TransformTest, rotate3dOverload) {
auto point = facebook::react::Point{10, 10};
auto size = facebook::react::Size{10, 10};
auto rect = facebook::react::Rect{point, size};
auto transform = Transform::Rotate(0, 0, M_PI_4);
EXPECT_EQ(transform.operations.size(), 1);
EXPECT_EQ(transform.operations[0].type, TransformOperationType::Rotate);
EXPECT_EQ(transform.operations[0].x.resolve(0), 0);
EXPECT_EQ(transform.operations[0].y.resolve(0), 0);
ASSERT_NEAR(transform.operations[0].z.resolve(0), M_PI_4, 0.0001);
auto transformedRect = rect * Transform::Rotate(0, 0, M_PI_4);
ASSERT_NEAR(transformedRect.origin.x, 7.9289, 0.0001);
ASSERT_NEAR(transformedRect.origin.y, 7.9289, 0.0001);
ASSERT_NEAR(transformedRect.size.width, 14.1421, 0.0001);
ASSERT_NEAR(transformedRect.size.height, 14.1421, 0.0001);
}
TEST(TransformTest, scalingAndTranslatingRect) {
auto point = facebook::react::Point{100, 200};
auto size = facebook::react::Size{300, 400};
auto rect = facebook::react::Rect{point, size};
auto transformedRect =
rect * Transform::Scale(0.5, 0.5, 1) * Transform::Translate(1, 1, 0);
EXPECT_EQ(transformedRect.origin.x, 176);
EXPECT_EQ(transformedRect.origin.y, 301);
EXPECT_EQ(transformedRect.size.width, 150);
EXPECT_EQ(transformedRect.size.height, 200);
}