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,238 @@
/*
* 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 <cinttypes>
#include <optional>
#include <string>
#include <vector>
namespace facebook::react {
enum class AccessibilityTraits : uint32_t {
None = 0,
Button = (1 << 0),
Link = (1 << 1),
Image = (1 << 2),
Selected = (1 << 3),
PlaysSound = (1 << 4),
KeyboardKey = (1 << 5),
StaticText = (1 << 6),
SummaryElement = (1 << 7),
NotEnabled = (1 << 8),
UpdatesFrequently = (1 << 9),
SearchField = (1 << 10),
StartsMediaSession = (1 << 11),
Adjustable = (1 << 12),
AllowsDirectInteraction = (1 << 13),
CausesPageTurn = (1 << 14),
Header = (1 << 15),
Switch = (1 << 16),
TabBar = (1 << 17),
};
constexpr enum AccessibilityTraits operator|(const enum AccessibilityTraits lhs, const enum AccessibilityTraits rhs)
{
return (enum AccessibilityTraits)((uint32_t)lhs | (uint32_t)rhs);
}
constexpr enum AccessibilityTraits operator&(const enum AccessibilityTraits lhs, const enum AccessibilityTraits rhs)
{
return (enum AccessibilityTraits)((uint32_t)lhs & (uint32_t)rhs);
}
struct AccessibilityAction {
std::string name;
std::optional<std::string> label{};
};
inline static bool operator==(const AccessibilityAction &lhs, const AccessibilityAction &rhs)
{
return lhs.name == rhs.name && lhs.label == rhs.label;
}
inline static bool operator!=(const AccessibilityAction &lhs, const AccessibilityAction &rhs)
{
return !(rhs == lhs);
}
struct AccessibilityState {
bool disabled{false};
bool selected{false};
bool busy{false};
std::optional<bool> expanded{std::nullopt};
enum CheckedState { Unchecked, Checked, Mixed, None } checked{None};
};
constexpr bool operator==(const AccessibilityState &lhs, const AccessibilityState &rhs)
{
return lhs.disabled == rhs.disabled && lhs.selected == rhs.selected && lhs.checked == rhs.checked &&
lhs.busy == rhs.busy && lhs.expanded == rhs.expanded;
}
constexpr bool operator!=(const AccessibilityState &lhs, const AccessibilityState &rhs)
{
return !(rhs == lhs);
}
struct AccessibilityLabelledBy {
std::vector<std::string> value{};
};
inline static bool operator==(const AccessibilityLabelledBy &lhs, const AccessibilityLabelledBy &rhs)
{
return lhs.value == rhs.value;
}
inline static bool operator!=(const AccessibilityLabelledBy &lhs, const AccessibilityLabelledBy &rhs)
{
return !(lhs == rhs);
}
struct AccessibilityValue {
std::optional<int> min;
std::optional<int> max;
std::optional<int> now;
std::optional<std::string> text{};
};
constexpr bool operator==(const AccessibilityValue &lhs, const AccessibilityValue &rhs)
{
return lhs.min == rhs.min && lhs.max == rhs.max && lhs.now == rhs.now && lhs.text == rhs.text;
}
constexpr bool operator!=(const AccessibilityValue &lhs, const AccessibilityValue &rhs)
{
return !(rhs == lhs);
}
enum class ImportantForAccessibility : uint8_t {
Auto,
Yes,
No,
NoHideDescendants,
};
enum class AccessibilityLiveRegion : uint8_t {
None,
Polite,
Assertive,
};
enum class AccessibilityRole : uint8_t {
None,
Button,
Dropdownlist,
Togglebutton,
Link,
Search,
Image,
Keyboardkey,
Text,
Adjustable,
Imagebutton,
Header,
Summary,
Alert,
Checkbox,
Combobox,
Menu,
Menubar,
Menuitem,
Progressbar,
Radio,
Radiogroup,
Scrollbar,
Spinbutton,
Switch,
Tab,
Tabbar,
Tablist,
Timer,
List,
Toolbar,
Grid,
Pager,
Scrollview,
Horizontalscrollview,
Viewgroup,
Webview,
Drawerlayout,
Slidingdrawer,
Iconmenu,
};
enum class Role : uint8_t {
Alert,
Alertdialog,
Application,
Article,
Banner,
Button,
Cell,
Checkbox,
Columnheader,
Combobox,
Complementary,
Contentinfo,
Definition,
Dialog,
Directory,
Document,
Feed,
Figure,
Form,
Grid,
Group,
Heading,
Img,
Link,
List,
Listitem,
Log,
Main,
Marquee,
Math,
Menu,
Menubar,
Menuitem,
Meter,
Navigation,
None,
Note,
Option,
Presentation,
Progressbar,
Radio,
Radiogroup,
Region,
Row,
Rowgroup,
Rowheader,
Scrollbar,
Searchbox,
Separator,
Slider,
Spinbutton,
Status,
Summary,
Switch,
Tab,
Table,
Tablist,
Tabpanel,
Term,
Timer,
Toolbar,
Tooltip,
Tree,
Treegrid,
Treeitem,
};
} // namespace facebook::react

View File

@@ -0,0 +1,351 @@
/*
* 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 "AccessibilityProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/view/accessibilityPropsConversions.h>
#include <react/renderer/components/view/propsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
AccessibilityProps::AccessibilityProps(
const PropsParserContext& context,
const AccessibilityProps& sourceProps,
const RawProps& rawProps)
: accessible(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessible
: convertRawProp(
context,
rawProps,
"accessible",
sourceProps.accessible,
false)),
accessibilityState(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityState
: convertRawProp(
context,
rawProps,
"accessibilityState",
sourceProps.accessibilityState,
{})),
accessibilityLabel(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityLabel
: convertRawProp(
context,
rawProps,
"accessibilityLabel",
sourceProps.accessibilityLabel,
"")),
accessibilityOrder(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityOrder
: convertRawProp(
context,
rawProps,
"experimental_accessibilityOrder",
sourceProps.accessibilityOrder,
{})),
accessibilityLabelledBy(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityLabelledBy
: convertRawProp(
context,
rawProps,
"accessibilityLabelledBy",
sourceProps.accessibilityLabelledBy,
{})),
accessibilityLiveRegion(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityLiveRegion
: convertRawProp(
context,
rawProps,
"accessibilityLiveRegion",
sourceProps.accessibilityLiveRegion,
AccessibilityLiveRegion::None)),
accessibilityHint(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityHint
: convertRawProp(
context,
rawProps,
"accessibilityHint",
sourceProps.accessibilityHint,
"")),
accessibilityLanguage(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityLanguage
: convertRawProp(
context,
rawProps,
"accessibilityLanguage",
sourceProps.accessibilityLanguage,
"")),
accessibilityLargeContentTitle(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityLargeContentTitle
: convertRawProp(
context,
rawProps,
"accessibilityLargeContentTitle",
sourceProps.accessibilityLargeContentTitle,
"")),
accessibilityValue(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityValue
: convertRawProp(
context,
rawProps,
"accessibilityValue",
sourceProps.accessibilityValue,
{})),
accessibilityActions(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityActions
: convertRawProp(
context,
rawProps,
"accessibilityActions",
sourceProps.accessibilityActions,
{})),
accessibilityShowsLargeContentViewer(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityShowsLargeContentViewer
: convertRawProp(
context,
rawProps,
"accessibilityShowsLargeContentViewer",
sourceProps.accessibilityShowsLargeContentViewer,
false)),
accessibilityViewIsModal(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityViewIsModal
: convertRawProp(
context,
rawProps,
"accessibilityViewIsModal",
sourceProps.accessibilityViewIsModal,
false)),
accessibilityElementsHidden(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityElementsHidden
: convertRawProp(
context,
rawProps,
"accessibilityElementsHidden",
sourceProps.accessibilityElementsHidden,
false)),
accessibilityIgnoresInvertColors(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityIgnoresInvertColors
: convertRawProp(
context,
rawProps,
"accessibilityIgnoresInvertColors",
sourceProps.accessibilityIgnoresInvertColors,
false)),
accessibilityRespondsToUserInteraction(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.accessibilityRespondsToUserInteraction
: convertRawProp(
context,
rawProps,
"accessibilityRespondsToUserInteraction",
sourceProps.accessibilityRespondsToUserInteraction,
true)),
onAccessibilityTap(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onAccessibilityTap
: convertRawProp(
context,
rawProps,
"onAccessibilityTap",
sourceProps.onAccessibilityTap,
{})),
onAccessibilityMagicTap(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onAccessibilityMagicTap
: convertRawProp(
context,
rawProps,
"onAccessibilityMagicTap",
sourceProps.onAccessibilityMagicTap,
{})),
onAccessibilityEscape(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onAccessibilityEscape
: convertRawProp(
context,
rawProps,
"onAccessibilityEscape",
sourceProps.onAccessibilityEscape,
{})),
onAccessibilityAction(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onAccessibilityAction
: convertRawProp(
context,
rawProps,
"onAccessibilityAction",
sourceProps.onAccessibilityAction,
{})),
importantForAccessibility(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.importantForAccessibility
: convertRawProp(
context,
rawProps,
"importantForAccessibility",
sourceProps.importantForAccessibility,
ImportantForAccessibility::Auto)),
testId(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.testId
: convertRawProp(
context,
rawProps,
"testID",
sourceProps.testId,
"")) {
// It is a (severe!) perf deoptimization to request props out-of-order.
// Thus, since we need to request the same prop twice here
// (accessibilityRole) we "must" do them subsequently here to prevent
// a regression. It is reasonable to ask if the `at` function can be improved;
// it probably can, but this is a fairly rare edge-case that (1) is easy-ish
// to work around here, and (2) would require very careful work to address
// this case and not regress the more common cases.
if (ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) {
accessibilityRole = sourceProps.accessibilityRole;
role = sourceProps.role;
accessibilityTraits = sourceProps.accessibilityTraits;
} else {
auto* accessibilityRoleValue =
rawProps.at("accessibilityRole", nullptr, nullptr);
auto* roleValue = rawProps.at("role", nullptr, nullptr);
auto* precedentRoleValue =
roleValue != nullptr ? roleValue : accessibilityRoleValue;
if (accessibilityRoleValue == nullptr ||
!accessibilityRoleValue->hasValue()) {
accessibilityRole = sourceProps.accessibilityRole;
} else {
fromRawValue(context, *accessibilityRoleValue, accessibilityRole);
}
if (roleValue == nullptr || !roleValue->hasValue()) {
role = sourceProps.role;
} else {
fromRawValue(context, *roleValue, role);
}
if (precedentRoleValue == nullptr || !precedentRoleValue->hasValue()) {
accessibilityTraits = sourceProps.accessibilityTraits;
} else {
fromRawValue(context, *precedentRoleValue, accessibilityTraits);
}
}
}
void AccessibilityProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* /*propName*/,
const RawValue& value) {
static auto defaults = AccessibilityProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(accessible);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityOrder);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLanguage);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityShowsLargeContentViewer);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLargeContentTitle);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityValue);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityActions);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityViewIsModal);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityElementsHidden);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityIgnoresInvertColors);
RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityRespondsToUserInteraction);
RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityTap);
RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityMagicTap);
RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape);
RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction);
RAW_SET_PROP_SWITCH_CASE_BASIC(importantForAccessibility);
RAW_SET_PROP_SWITCH_CASE_BASIC(role);
RAW_SET_PROP_SWITCH_CASE(testId, "testID");
case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityRole"): {
AccessibilityTraits traits = AccessibilityTraits::None;
std::string roleString;
if (value.hasValue()) {
fromRawValue(context, value, traits);
fromRawValue(context, value, roleString);
}
accessibilityTraits = traits;
accessibilityRole = roleString;
return;
}
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList AccessibilityProps::getDebugProps() const {
const auto& defaultProps = AccessibilityProps();
return SharedDebugStringConvertibleList{
debugStringConvertibleItem(
"accessibilityRole",
accessibilityRole,
defaultProps.accessibilityRole),
debugStringConvertibleItem(
"accessible", accessible, defaultProps.accessible),
debugStringConvertibleItem(
"accessibilityActions",
accessibilityActions,
defaultProps.accessibilityActions),
debugStringConvertibleItem(
"accessibilityState",
accessibilityState,
defaultProps.accessibilityState),
debugStringConvertibleItem(
"accessibilityElementsHidden",
accessibilityElementsHidden,
defaultProps.accessibilityElementsHidden),
debugStringConvertibleItem(
"accessibilityHint",
accessibilityHint,
defaultProps.accessibilityHint),
debugStringConvertibleItem(
"accessibilityLabel",
accessibilityLabel,
defaultProps.accessibilityLabel),
debugStringConvertibleItem(
"accessibilityLiveRegion",
accessibilityLiveRegion,
defaultProps.accessibilityLiveRegion),
debugStringConvertibleItem(
"importantForAccessibility",
importantForAccessibility,
defaultProps.importantForAccessibility),
debugStringConvertibleItem("testID", testId, defaultProps.testId),
};
}
#endif // RN_DEBUG_STRING_CONVERTIBLE
} // namespace facebook::react

View File

@@ -0,0 +1,67 @@
/*
* 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/components/view/AccessibilityPrimitives.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class AccessibilityProps {
public:
AccessibilityProps() = default;
AccessibilityProps(
const PropsParserContext &context,
const AccessibilityProps &sourceProps,
const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
bool accessible{false};
std::optional<AccessibilityState> accessibilityState{std::nullopt};
std::string accessibilityLabel;
std::vector<std::string> accessibilityOrder{};
AccessibilityLabelledBy accessibilityLabelledBy{};
AccessibilityLiveRegion accessibilityLiveRegion{AccessibilityLiveRegion::None};
AccessibilityTraits accessibilityTraits{AccessibilityTraits::None};
std::string accessibilityRole;
std::string accessibilityHint;
std::string accessibilityLanguage;
std::string accessibilityLargeContentTitle;
AccessibilityValue accessibilityValue;
std::vector<AccessibilityAction> accessibilityActions{};
bool accessibilityShowsLargeContentViewer{false};
bool accessibilityViewIsModal{false};
bool accessibilityElementsHidden{false};
bool accessibilityIgnoresInvertColors{false};
// This prop is enabled by default on iOS, we need to match this on
// C++ because if not, it will default to false before render which prevents
// the view from being updated with the correct value.
bool accessibilityRespondsToUserInteraction{true};
bool onAccessibilityTap{};
bool onAccessibilityMagicTap{};
bool onAccessibilityEscape{};
bool onAccessibilityAction{};
ImportantForAccessibility importantForAccessibility{ImportantForAccessibility::Auto};
Role role{Role::None};
std::string testId;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,619 @@
/*
* 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 "BackgroundImagePropsConversions.h"
#include <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/renderer/components/view/CSSConversions.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/css/CSSBackgroundImage.h>
#include <react/renderer/css/CSSLengthUnit.h>
#include <react/renderer/css/CSSPercentage.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/graphics/ColorStop.h>
#include <react/renderer/graphics/LinearGradient.h>
#include <react/renderer/graphics/RadialGradient.h>
#include <react/renderer/graphics/ValueUnit.h>
namespace facebook::react {
using RawValueMap = std::unordered_map<std::string, RawValue>;
using RawValueList = std::vector<RawValue>;
inline GradientKeyword parseGradientKeyword(const std::string& keyword) {
if (keyword == "to top right") {
return GradientKeyword::ToTopRight;
} else if (keyword == "to bottom right") {
return GradientKeyword::ToBottomRight;
} else if (keyword == "to top left") {
return GradientKeyword::ToTopLeft;
} else if (keyword == "to bottom left") {
return GradientKeyword::ToBottomLeft;
} else {
throw std::invalid_argument("Invalid gradient keyword: " + keyword);
}
}
void parseProcessedBackgroundImage(
const PropsParserContext& context,
const RawValue& value,
std::vector<BackgroundImage>& result) {
react_native_expect(value.hasType<RawValueList>());
if (!value.hasType<RawValueList>()) {
result = {};
return;
}
std::vector<BackgroundImage> backgroundImage{};
auto rawBackgroundImage = static_cast<RawValueList>(value);
for (const auto& rawBackgroundImageValue : rawBackgroundImage) {
bool isMap = rawBackgroundImageValue.hasType<RawValueMap>();
react_native_expect(isMap);
if (!isMap) {
result = {};
return;
}
auto rawBackgroundImageMap =
static_cast<RawValueMap>(rawBackgroundImageValue);
auto typeIt = rawBackgroundImageMap.find("type");
if (typeIt == rawBackgroundImageMap.end() ||
!typeIt->second.hasType<std::string>()) {
continue;
}
std::string type = (std::string)(typeIt->second);
std::vector<ColorStop> colorStops;
auto colorStopsIt = rawBackgroundImageMap.find("colorStops");
if (colorStopsIt != rawBackgroundImageMap.end() &&
colorStopsIt->second.hasType<RawValueList>()) {
auto rawColorStops = static_cast<RawValueList>(colorStopsIt->second);
for (const auto& stop : rawColorStops) {
if (stop.hasType<RawValueMap>()) {
auto stopMap = static_cast<RawValueMap>(stop);
auto positionIt = stopMap.find("position");
auto colorIt = stopMap.find("color");
if (positionIt != stopMap.end() && colorIt != stopMap.end()) {
ColorStop colorStop;
if (positionIt->second.hasValue()) {
auto valueUnit = toValueUnit(positionIt->second);
if (!valueUnit) {
result = {};
return;
}
colorStop.position = valueUnit;
}
if (colorIt->second.hasValue()) {
fromRawValue(
context.contextContainer,
context.surfaceId,
colorIt->second,
colorStop.color);
}
colorStops.push_back(colorStop);
}
}
}
}
if (type == "linear-gradient") {
LinearGradient linearGradient;
auto directionIt = rawBackgroundImageMap.find("direction");
if (directionIt != rawBackgroundImageMap.end() &&
directionIt->second.hasType<RawValueMap>()) {
auto directionMap = static_cast<RawValueMap>(directionIt->second);
auto directionTypeIt = directionMap.find("type");
auto valueIt = directionMap.find("value");
if (directionTypeIt != directionMap.end() &&
valueIt != directionMap.end()) {
std::string directionType = (std::string)(directionTypeIt->second);
if (directionType == "angle") {
linearGradient.direction.type = GradientDirectionType::Angle;
if (valueIt->second.hasType<Float>()) {
linearGradient.direction.value = (Float)(valueIt->second);
}
} else if (directionType == "keyword") {
linearGradient.direction.type = GradientDirectionType::Keyword;
if (valueIt->second.hasType<std::string>()) {
linearGradient.direction.value =
parseGradientKeyword((std::string)(valueIt->second));
}
}
}
}
if (!colorStops.empty()) {
linearGradient.colorStops = colorStops;
}
backgroundImage.emplace_back(std::move(linearGradient));
} else if (type == "radial-gradient") {
RadialGradient radialGradient;
auto shapeIt = rawBackgroundImageMap.find("shape");
if (shapeIt != rawBackgroundImageMap.end() &&
shapeIt->second.hasType<std::string>()) {
auto shape = (std::string)(shapeIt->second);
radialGradient.shape = shape == "circle" ? RadialGradientShape::Circle
: RadialGradientShape::Ellipse;
}
auto sizeIt = rawBackgroundImageMap.find("size");
if (sizeIt != rawBackgroundImageMap.end()) {
if (sizeIt->second.hasType<std::string>()) {
auto sizeStr = (std::string)(sizeIt->second);
if (sizeStr == "closest-side") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestSide;
} else if (sizeStr == "farthest-side") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestSide;
} else if (sizeStr == "closest-corner") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestCorner;
} else if (sizeStr == "farthest-corner") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestCorner;
}
} else if (sizeIt->second.hasType<RawValueMap>()) {
auto sizeMap = static_cast<RawValueMap>(sizeIt->second);
auto xIt = sizeMap.find("x");
auto yIt = sizeMap.find("y");
if (xIt != sizeMap.end() && yIt != sizeMap.end()) {
radialGradient.size = RadialGradientSize{
.value = RadialGradientSize::Dimensions{
.x = toValueUnit(xIt->second),
.y = toValueUnit(yIt->second)}};
}
}
auto positionIt = rawBackgroundImageMap.find("position");
if (positionIt != rawBackgroundImageMap.end() &&
positionIt->second.hasType<RawValueMap>()) {
auto positionMap = static_cast<RawValueMap>(positionIt->second);
auto topIt = positionMap.find("top");
auto bottomIt = positionMap.find("bottom");
auto leftIt = positionMap.find("left");
auto rightIt = positionMap.find("right");
if (topIt != positionMap.end()) {
auto topValue = toValueUnit(topIt->second);
radialGradient.position.top = topValue;
} else if (bottomIt != positionMap.end()) {
auto bottomValue = toValueUnit(bottomIt->second);
radialGradient.position.bottom = bottomValue;
}
if (leftIt != positionMap.end()) {
auto leftValue = toValueUnit(leftIt->second);
radialGradient.position.left = leftValue;
} else if (rightIt != positionMap.end()) {
auto rightValue = toValueUnit(rightIt->second);
radialGradient.position.right = rightValue;
}
}
}
if (!colorStops.empty()) {
radialGradient.colorStops = colorStops;
}
backgroundImage.emplace_back(std::move(radialGradient));
}
}
result = backgroundImage;
}
void parseUnprocessedBackgroundImageList(
const PropsParserContext& context,
const std::vector<RawValue>& value,
std::vector<BackgroundImage>& result) {
std::vector<BackgroundImage> backgroundImage{};
for (const auto& rawBackgroundImageValue : value) {
bool isMap = rawBackgroundImageValue.hasType<RawValueMap>();
react_native_expect(isMap);
if (!isMap) {
result = {};
return;
}
auto rawBackgroundImageMap =
static_cast<RawValueMap>(rawBackgroundImageValue);
auto typeIt = rawBackgroundImageMap.find("type");
if (typeIt == rawBackgroundImageMap.end() ||
!typeIt->second.hasType<std::string>()) {
continue;
}
std::string type = (std::string)(typeIt->second);
std::vector<ColorStop> colorStops;
auto colorStopsIt = rawBackgroundImageMap.find("colorStops");
if (colorStopsIt != rawBackgroundImageMap.end() &&
colorStopsIt->second.hasType<RawValueList>()) {
auto rawColorStops = static_cast<RawValueList>(colorStopsIt->second);
for (const auto& stop : rawColorStops) {
if (stop.hasType<RawValueMap>()) {
auto stopMap = static_cast<RawValueMap>(stop);
auto positionsIt = stopMap.find("positions");
auto colorIt = stopMap.find("color");
// has only color. e.g. (red, green)
if (positionsIt == stopMap.end() ||
(positionsIt->second.hasType<RawValueList>() &&
static_cast<RawValueList>(positionsIt->second).empty())) {
auto color = coerceColor(colorIt->second, context);
if (!color) {
// invalid color
result = {};
return;
}
colorStops.push_back(
ColorStop{.color = std::move(color), .position = ValueUnit()});
continue;
}
// Color hint (red, 20%, blue)
// or Color Stop with positions (red, 20% 30%)
if (positionsIt != stopMap.end() &&
positionsIt->second.hasType<RawValueList>()) {
auto positions = static_cast<RawValueList>(positionsIt->second);
for (const auto& position : positions) {
auto positionValue = toValueUnit(position);
if (!positionValue) {
// invalid position
result = {};
return;
}
ColorStop colorStop;
colorStop.position = positionValue;
if (colorIt != stopMap.end()) {
auto color = coerceColor(colorIt->second, context);
if (color) {
colorStop.color = color;
}
}
colorStops.emplace_back(colorStop);
}
}
}
}
}
if (type == "linear-gradient") {
LinearGradient linearGradient;
auto directionIt = rawBackgroundImageMap.find("direction");
if (directionIt != rawBackgroundImageMap.end()) {
if (directionIt->second.hasType<std::string>()) {
std::string directionStr = (std::string)(directionIt->second);
auto cssDirection =
parseCSSProperty<CSSLinearGradientDirection>(directionStr);
if (std::holds_alternative<CSSLinearGradientDirection>(
cssDirection)) {
const auto& direction =
std::get<CSSLinearGradientDirection>(cssDirection);
if (std::holds_alternative<CSSAngle>(direction.value)) {
linearGradient.direction.type = GradientDirectionType::Angle;
linearGradient.direction.value =
std::get<CSSAngle>(direction.value).degrees;
} else if (std::holds_alternative<
CSSLinearGradientDirectionKeyword>(
direction.value)) {
linearGradient.direction.type = GradientDirectionType::Keyword;
auto keyword =
std::get<CSSLinearGradientDirectionKeyword>(direction.value);
switch (keyword) {
case CSSLinearGradientDirectionKeyword::ToTopLeft:
linearGradient.direction.value = GradientKeyword::ToTopLeft;
break;
case CSSLinearGradientDirectionKeyword::ToTopRight:
linearGradient.direction.value = GradientKeyword::ToTopRight;
break;
case CSSLinearGradientDirectionKeyword::ToBottomLeft:
linearGradient.direction.value =
GradientKeyword::ToBottomLeft;
break;
case CSSLinearGradientDirectionKeyword::ToBottomRight:
linearGradient.direction.value =
GradientKeyword::ToBottomRight;
break;
}
}
}
}
}
if (!colorStops.empty()) {
linearGradient.colorStops = colorStops;
}
backgroundImage.emplace_back(std::move(linearGradient));
} else if (type == "radial-gradient") {
RadialGradient radialGradient;
auto shapeIt = rawBackgroundImageMap.find("shape");
if (shapeIt != rawBackgroundImageMap.end() &&
shapeIt->second.hasType<std::string>()) {
auto shape = (std::string)(shapeIt->second);
radialGradient.shape = shape == "circle" ? RadialGradientShape::Circle
: RadialGradientShape::Ellipse;
}
auto sizeIt = rawBackgroundImageMap.find("size");
if (sizeIt != rawBackgroundImageMap.end()) {
if (sizeIt->second.hasType<std::string>()) {
auto sizeStr = (std::string)(sizeIt->second);
if (sizeStr == "closest-side") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestSide;
} else if (sizeStr == "farthest-side") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestSide;
} else if (sizeStr == "closest-corner") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestCorner;
} else if (sizeStr == "farthest-corner") {
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestCorner;
}
} else if (sizeIt->second.hasType<RawValueMap>()) {
auto sizeMap = static_cast<RawValueMap>(sizeIt->second);
auto xIt = sizeMap.find("x");
auto yIt = sizeMap.find("y");
if (xIt != sizeMap.end() && yIt != sizeMap.end()) {
radialGradient.size = {RadialGradientSize::Dimensions{
.x = toValueUnit(xIt->second), .y = toValueUnit(yIt->second)}};
}
}
auto positionIt = rawBackgroundImageMap.find("position");
if (positionIt != rawBackgroundImageMap.end() &&
positionIt->second.hasType<RawValueMap>()) {
auto positionMap = static_cast<RawValueMap>(positionIt->second);
auto topIt = positionMap.find("top");
auto bottomIt = positionMap.find("bottom");
auto leftIt = positionMap.find("left");
auto rightIt = positionMap.find("right");
if (topIt != positionMap.end()) {
auto topValue = toValueUnit(topIt->second);
radialGradient.position.top = topValue;
} else if (bottomIt != positionMap.end()) {
auto bottomValue = toValueUnit(bottomIt->second);
radialGradient.position.bottom = bottomValue;
}
if (leftIt != positionMap.end()) {
auto leftValue = toValueUnit(leftIt->second);
radialGradient.position.left = leftValue;
} else if (rightIt != positionMap.end()) {
auto rightValue = toValueUnit(rightIt->second);
radialGradient.position.right = rightValue;
}
}
}
if (!colorStops.empty()) {
radialGradient.colorStops = colorStops;
}
backgroundImage.emplace_back(std::move(radialGradient));
}
}
result = backgroundImage;
}
namespace {
ValueUnit convertLengthPercentageToValueUnit(
const std::variant<CSSLength, CSSPercentage>& value) {
if (std::holds_alternative<CSSLength>(value)) {
return {std::get<CSSLength>(value).value, UnitType::Point};
} else {
return {std::get<CSSPercentage>(value).value, UnitType::Percent};
}
}
void fromCSSColorStop(
const std::variant<CSSColorStop, CSSColorHint>& item,
std::vector<ColorStop>& colorStops) {
if (std::holds_alternative<CSSColorStop>(item)) {
const auto& colorStop = std::get<CSSColorStop>(item);
// handle two positions case: [color, position, position] -> push two
// stops
if (colorStop.startPosition.has_value() &&
colorStop.endPosition.has_value()) {
// first stop with start position
colorStops.push_back(
ColorStop{
.color = fromCSSColor(colorStop.color),
.position = convertLengthPercentageToValueUnit(
*colorStop.startPosition)});
// second stop with end position (same color)
colorStops.push_back(
ColorStop{
.color = fromCSSColor(colorStop.color),
.position =
convertLengthPercentageToValueUnit(*colorStop.endPosition)});
} else {
// single color stop
ColorStop stop;
stop.color = fromCSSColor(colorStop.color);
// handle start position if present
if (colorStop.startPosition.has_value()) {
stop.position =
convertLengthPercentageToValueUnit(*colorStop.startPosition);
}
colorStops.push_back(stop);
}
} else if (std::holds_alternative<CSSColorHint>(item)) {
const auto& colorHint = std::get<CSSColorHint>(item);
// color hint: add a stop with null color and the hint position
ColorStop hintStop;
hintStop.position = convertLengthPercentageToValueUnit(colorHint.position);
colorStops.push_back(hintStop);
}
}
std::optional<BackgroundImage> fromCSSBackgroundImage(
const CSSBackgroundImageVariant& cssBackgroundImage) {
if (std::holds_alternative<CSSLinearGradientFunction>(cssBackgroundImage)) {
const auto& gradient =
std::get<CSSLinearGradientFunction>(cssBackgroundImage);
LinearGradient linearGradient;
if (gradient.direction.has_value()) {
if (std::holds_alternative<CSSAngle>(gradient.direction->value)) {
const auto& angle = std::get<CSSAngle>(gradient.direction->value);
linearGradient.direction.type = GradientDirectionType::Angle;
linearGradient.direction.value = angle.degrees;
} else if (std::holds_alternative<CSSLinearGradientDirectionKeyword>(
gradient.direction->value)) {
const auto& dirKeyword = std::get<CSSLinearGradientDirectionKeyword>(
gradient.direction->value);
linearGradient.direction.type = GradientDirectionType::Keyword;
switch (dirKeyword) {
case CSSLinearGradientDirectionKeyword::ToTopLeft:
linearGradient.direction.value = GradientKeyword::ToTopLeft;
break;
case CSSLinearGradientDirectionKeyword::ToTopRight:
linearGradient.direction.value = GradientKeyword::ToTopRight;
break;
case CSSLinearGradientDirectionKeyword::ToBottomLeft:
linearGradient.direction.value = GradientKeyword::ToBottomLeft;
break;
case CSSLinearGradientDirectionKeyword::ToBottomRight:
linearGradient.direction.value = GradientKeyword::ToBottomRight;
break;
}
}
}
for (const auto& item : gradient.items) {
fromCSSColorStop(item, linearGradient.colorStops);
}
return BackgroundImage{linearGradient};
} else if (std::holds_alternative<CSSRadialGradientFunction>(
cssBackgroundImage)) {
const auto& gradient =
std::get<CSSRadialGradientFunction>(cssBackgroundImage);
RadialGradient radialGradient;
if (gradient.shape.has_value()) {
radialGradient.shape = (*gradient.shape == CSSRadialGradientShape::Circle)
? RadialGradientShape::Circle
: RadialGradientShape::Ellipse;
}
if (gradient.size.has_value()) {
if (std::holds_alternative<CSSRadialGradientSizeKeyword>(
*gradient.size)) {
const auto& sizeKeyword =
std::get<CSSRadialGradientSizeKeyword>(*gradient.size);
switch (sizeKeyword) {
case CSSRadialGradientSizeKeyword::ClosestSide:
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestSide;
break;
case CSSRadialGradientSizeKeyword::ClosestCorner:
radialGradient.size.value =
RadialGradientSize::SizeKeyword::ClosestCorner;
break;
case CSSRadialGradientSizeKeyword::FarthestSide:
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestSide;
break;
case CSSRadialGradientSizeKeyword::FarthestCorner:
radialGradient.size.value =
RadialGradientSize::SizeKeyword::FarthestCorner;
break;
}
} else if (std::holds_alternative<CSSRadialGradientExplicitSize>(
*gradient.size)) {
const auto& explicitSize =
std::get<CSSRadialGradientExplicitSize>(*gradient.size);
radialGradient.size.value = RadialGradientSize::Dimensions{
.x = convertLengthPercentageToValueUnit(explicitSize.sizeX),
.y = convertLengthPercentageToValueUnit(explicitSize.sizeY)};
}
}
if (gradient.position.has_value()) {
const auto& pos = *gradient.position;
if (pos.top.has_value()) {
radialGradient.position.top =
convertLengthPercentageToValueUnit(*pos.top);
}
if (pos.bottom.has_value()) {
radialGradient.position.bottom =
convertLengthPercentageToValueUnit(*pos.bottom);
}
if (pos.left.has_value()) {
radialGradient.position.left =
convertLengthPercentageToValueUnit(*pos.left);
}
if (pos.right.has_value()) {
radialGradient.position.right =
convertLengthPercentageToValueUnit(*pos.right);
}
}
for (const auto& item : gradient.items) {
fromCSSColorStop(item, radialGradient.colorStops);
}
return BackgroundImage{radialGradient};
}
return std::nullopt;
}
} // namespace
void parseUnprocessedBackgroundImageString(
const std::string& value,
std::vector<BackgroundImage>& result) {
auto backgroundImageList = parseCSSProperty<CSSBackgroundImageList>(value);
if (!std::holds_alternative<CSSBackgroundImageList>(backgroundImageList)) {
result = {};
return;
}
std::vector<BackgroundImage> backgroundImages;
for (const auto& cssBackgroundImage :
std::get<CSSBackgroundImageList>(backgroundImageList)) {
if (auto backgroundImage = fromCSSBackgroundImage(cssBackgroundImage)) {
backgroundImages.push_back(*backgroundImage);
} else {
result = {};
return;
}
}
result = backgroundImages;
}
} // namespace facebook::react

View File

@@ -0,0 +1,44 @@
/*
* 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/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/graphics/BackgroundImage.h>
namespace facebook::react {
void parseProcessedBackgroundImage(
const PropsParserContext &context,
const RawValue &value,
std::vector<BackgroundImage> &result);
void parseUnprocessedBackgroundImageList(
const PropsParserContext &context,
const std::vector<RawValue> &value,
std::vector<BackgroundImage> &result);
void parseUnprocessedBackgroundImageString(const std::string &value, std::vector<BackgroundImage> &result);
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, std::vector<BackgroundImage> &result)
{
if (ReactNativeFeatureFlags::enableNativeCSSParsing()) {
if (value.hasType<std::string>()) {
parseUnprocessedBackgroundImageString((std::string)value, result);
} else if (value.hasType<std::vector<RawValue>>()) {
parseUnprocessedBackgroundImageList(context, (std::vector<RawValue>)value, result);
} else {
result = {};
}
} else {
parseProcessedBackgroundImage(context, value, result);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,50 @@
/*
* 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 "Touch.h"
namespace facebook::react {
void setTouchPayloadOnObject(
jsi::Object& object,
jsi::Runtime& runtime,
const BaseTouch& touch) {
object.setProperty(runtime, "locationX", touch.offsetPoint.x);
object.setProperty(runtime, "locationY", touch.offsetPoint.y);
object.setProperty(runtime, "pageX", touch.pagePoint.x);
object.setProperty(runtime, "pageY", touch.pagePoint.y);
object.setProperty(runtime, "screenX", touch.screenPoint.x);
object.setProperty(runtime, "screenY", touch.screenPoint.y);
object.setProperty(runtime, "identifier", touch.identifier);
object.setProperty(runtime, "target", touch.target);
object.setProperty(runtime, "timestamp", touch.timestamp * 1000);
object.setProperty(runtime, "force", touch.force);
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const BaseTouch& /*touch*/) {
return "Touch";
}
std::vector<DebugStringConvertibleObject> getDebugProps(
const BaseTouch& touch,
DebugStringConvertibleOptions options) {
return {
{"pagePoint", getDebugDescription(touch.pagePoint, options)},
{"offsetPoint", getDebugDescription(touch.offsetPoint, options)},
{"screenPoint", getDebugDescription(touch.screenPoint, options)},
{"identifier", getDebugDescription(touch.identifier, options)},
{"target", getDebugDescription(touch.target, options)},
{"force", getDebugDescription(touch.force, options)},
{"timestamp", getDebugDescription(touch.timestamp, options)},
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,88 @@
/*
* 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 <jsi/jsi.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Point.h>
namespace facebook::react {
/*
* Describes an individual touch point for a touch event.
* See https://www.w3.org/TR/touch-events/ for more details.
*/
struct BaseTouch {
/*
* The coordinate of point relative to the root component in points.
*/
Point pagePoint;
/*
* The coordinate of point relative to the target component in points.
*/
Point offsetPoint;
/*
* The coordinate of point relative to the screen component in points.
*/
Point screenPoint;
/*
* An identification number for each touch point.
*/
int identifier;
/*
* The tag of a component on which the touch point started when it was first
* placed on the surface, even if the touch point has since moved outside the
* interactive area of that element.
*/
Tag target;
/*
* The force of the touch.
*/
Float force;
/*
* The time in seconds when the touch occurred or when it was last mutated.
*/
Float timestamp;
/*
* The particular implementation of `Hasher` and (especially) `Comparator`
* make sense only when `Touch` object is used as a *key* in indexed
* collections. Because of that they are expressed as separate classes.
*/
struct Hasher {
size_t operator()(const BaseTouch &touch) const
{
return std::hash<decltype(touch.identifier)>()(touch.identifier);
}
};
struct Comparator {
bool operator()(const BaseTouch &lhs, const BaseTouch &rhs) const
{
return lhs.identifier == rhs.identifier;
}
};
};
void setTouchPayloadOnObject(jsi::Object &object, jsi::Runtime &runtime, const BaseTouch &touch);
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const BaseTouch &touch);
std::vector<DebugStringConvertibleObject> getDebugProps(const BaseTouch &touch, DebugStringConvertibleOptions options);
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,124 @@
/*
* 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 "BaseViewEventEmitter.h"
namespace facebook::react {
#pragma mark - Accessibility
void BaseViewEventEmitter::onAccessibilityAction(
const std::string& name) const {
dispatchEvent("accessibilityAction", [name](jsi::Runtime& runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "actionName", name);
return payload;
});
}
void BaseViewEventEmitter::onAccessibilityTap() const {
dispatchEvent("accessibilityTap");
}
void BaseViewEventEmitter::onAccessibilityMagicTap() const {
dispatchEvent("magicTap");
}
void BaseViewEventEmitter::onAccessibilityEscape() const {
dispatchEvent("accessibilityEscape");
}
#pragma mark - Layout
void BaseViewEventEmitter::onLayout(const LayoutMetrics& layoutMetrics) const {
// A copy of a shared pointer (`layoutEventState_`) establishes shared
// ownership that will be captured by lambda.
auto layoutEventState = layoutEventState_;
// Dispatched `frame` values to JavaScript thread are throttled here.
// Basic ideas:
// - Scheduling a lambda with some value that already was dispatched, does
// nothing.
// - If some lambda is already in flight, we don't schedule another;
// - When a lambda is being executed on the JavaScript thread, the *most
// recent* `frame` value is used (not the value that was current at the
// moment of scheduling the lambda).
//
// This implies the following caveats:
// - Some events can be skipped;
// - When values change rapidly, even events with different values
// can be skipped (only the very last will be delivered).
// - Ordering is preserved.
{
std::scoped_lock guard(layoutEventState->mutex);
// If a *particular* `frame` was already dispatched to the JavaScript side,
// no other work is required.
if (layoutEventState->frame == layoutMetrics.frame &&
layoutEventState->wasDispatched) {
return;
}
// If the *particular* `frame` was not already dispatched *or*
// some *other* `frame` was dispatched before,
// we need to schedule the dispatching.
layoutEventState->wasDispatched = false;
layoutEventState->frame = layoutMetrics.frame;
// Something is already in flight, dispatching another event is not
// required.
if (layoutEventState->isDispatching) {
return;
}
layoutEventState->isDispatching = true;
}
dispatchEvent("layout", [layoutEventState](jsi::Runtime& runtime) {
auto frame = Rect{};
{
std::scoped_lock guard(layoutEventState->mutex);
layoutEventState->isDispatching = false;
// If some *particular* `frame` was already dispatched before,
// and since then there were no other new values of the `frame`
// observed, do nothing.
if (layoutEventState->wasDispatched) {
return jsi::Value::null();
}
frame = layoutEventState->frame;
// If some *particular* `frame` was *not* already dispatched before,
// it's time to dispatch it and mark as dispatched.
layoutEventState->wasDispatched = true;
}
auto layout = jsi::Object(runtime);
layout.setProperty(runtime, "x", frame.origin.x);
layout.setProperty(runtime, "y", frame.origin.y);
layout.setProperty(runtime, "width", frame.size.width);
layout.setProperty(runtime, "height", frame.size.height);
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "layout", std::move(layout));
return jsi::Value(std::move(payload));
});
}
#pragma mark - Focus
void BaseViewEventEmitter::onFocus() const {
dispatchEvent("focus");
}
void BaseViewEventEmitter::onBlur() const {
dispatchEvent("blur");
}
} // namespace facebook::react

View File

@@ -0,0 +1,70 @@
/*
* 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 <memory>
#include <mutex>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/ReactPrimitives.h>
#include "TouchEventEmitter.h"
namespace facebook::react {
class BaseViewEventEmitter : public TouchEventEmitter {
public:
using TouchEventEmitter::TouchEventEmitter;
#pragma mark - Accessibility
void onAccessibilityAction(const std::string &name) const;
void onAccessibilityTap() const;
void onAccessibilityMagicTap() const;
void onAccessibilityEscape() const;
#pragma mark - Layout
void onLayout(const LayoutMetrics &layoutMetrics) const;
#pragma mark - Focus
void onFocus() const;
void onBlur() const;
private:
/*
* Contains the most recent `frame` and a `mutex` protecting access to it.
*/
struct LayoutEventState {
/*
* Protects an access to other fields of the struct.
*/
std::mutex mutex;
/*
* Last dispatched `frame` value or value that's being dispatched right now.
*/
Rect frame{};
/*
* Indicates that the `frame` value was already dispatched (and dispatching
* of the *same* value is not needed).
*/
bool wasDispatched{false};
/*
* Indicates that some lambda is already being dispatching (and dispatching
* another one is not needed).
*/
bool isDispatching{false};
};
mutable std::shared_ptr<LayoutEventState> layoutEventState_{std::make_shared<LayoutEventState>()};
};
} // namespace facebook::react

View File

@@ -0,0 +1,654 @@
/*
* 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 "BaseViewProps.h"
#include <algorithm>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/view/BackgroundImagePropsConversions.h>
#include <react/renderer/components/view/BoxShadowPropsConversions.h>
#include <react/renderer/components/view/FilterPropsConversions.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/components/view/primitives.h>
#include <react/renderer/components/view/propsConversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/renderer/graphics/ValueUnit.h>
namespace facebook::react {
namespace {
std::array<float, 3> getTranslateForTransformOrigin(
float viewWidth,
float viewHeight,
TransformOrigin transformOrigin) {
float viewCenterX = viewWidth / 2;
float viewCenterY = viewHeight / 2;
std::array<float, 3> origin = {viewCenterX, viewCenterY, transformOrigin.z};
for (size_t i = 0; i < transformOrigin.xy.size(); ++i) {
auto& currentOrigin = transformOrigin.xy[i];
if (currentOrigin.unit == UnitType::Point) {
origin[i] = currentOrigin.value;
} else if (currentOrigin.unit == UnitType::Percent) {
origin[i] =
((i == 0) ? viewWidth : viewHeight) * currentOrigin.value / 100.0f;
}
}
float newTranslateX = -viewCenterX + origin[0];
float newTranslateY = -viewCenterY + origin[1];
float newTranslateZ = origin[2];
return std::array{newTranslateX, newTranslateY, newTranslateZ};
}
} // namespace
BaseViewProps::BaseViewProps(
const PropsParserContext& context,
const BaseViewProps& sourceProps,
const RawProps& rawProps,
const std::function<bool(const std::string&)>& filterObjectKeys)
: YogaStylableProps(context, sourceProps, rawProps, filterObjectKeys),
AccessibilityProps(context, sourceProps, rawProps),
opacity(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.opacity
: convertRawProp(
context,
rawProps,
"opacity",
sourceProps.opacity,
(Float)1.0)),
backgroundColor(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backgroundColor
: convertRawProp(
context,
rawProps,
"backgroundColor",
sourceProps.backgroundColor,
{})),
borderRadii(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.borderRadii
: convertRawProp(
context,
rawProps,
"border",
"Radius",
sourceProps.borderRadii,
{})),
borderColors(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.borderColors
: convertRawProp(
context,
rawProps,
"border",
"Color",
sourceProps.borderColors,
{})),
borderCurves(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.borderCurves
: convertRawProp(
context,
rawProps,
"border",
"Curve",
sourceProps.borderCurves,
{})),
borderStyles(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.borderStyles
: convertRawProp(
context,
rawProps,
"border",
"Style",
sourceProps.borderStyles,
{})),
outlineColor(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.outlineColor
: convertRawProp(
context,
rawProps,
"outlineColor",
sourceProps.outlineColor,
{})),
outlineOffset(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.outlineOffset
: convertRawProp(
context,
rawProps,
"outlineOffset",
sourceProps.outlineOffset,
{})),
outlineStyle(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.outlineStyle
: convertRawProp(
context,
rawProps,
"outlineStyle",
sourceProps.outlineStyle,
{})),
outlineWidth(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.outlineWidth
: convertRawProp(
context,
rawProps,
"outlineWidth",
sourceProps.outlineWidth,
{})),
shadowColor(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.shadowColor
: convertRawProp(
context,
rawProps,
"shadowColor",
sourceProps.shadowColor,
{})),
shadowOffset(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.shadowOffset
: convertRawProp(
context,
rawProps,
"shadowOffset",
sourceProps.shadowOffset,
{})),
shadowOpacity(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.shadowOpacity
: convertRawProp(
context,
rawProps,
"shadowOpacity",
sourceProps.shadowOpacity,
{})),
shadowRadius(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.shadowRadius
: convertRawProp(
context,
rawProps,
"shadowRadius",
sourceProps.shadowRadius,
{})),
cursor(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.cursor
: convertRawProp(
context,
rawProps,
"cursor",
sourceProps.cursor,
{})),
boxShadow(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.boxShadow
: convertRawProp(
context,
rawProps,
"boxShadow",
sourceProps.boxShadow,
{})),
filter(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.filter
: convertRawProp(
context,
rawProps,
"filter",
sourceProps.filter,
{})),
backgroundImage(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backgroundImage
: convertRawProp(
context,
rawProps,
"experimental_backgroundImage",
sourceProps.backgroundImage,
{})),
backgroundSize(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backgroundSize
: convertRawProp(
context,
rawProps,
"experimental_backgroundSize",
sourceProps.backgroundSize,
{})),
backgroundPosition(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backgroundPosition
: convertRawProp(
context,
rawProps,
"experimental_backgroundPosition",
sourceProps.backgroundPosition,
{})),
backgroundRepeat(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backgroundRepeat
: convertRawProp(
context,
rawProps,
"experimental_backgroundRepeat",
sourceProps.backgroundRepeat,
{})),
mixBlendMode(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.mixBlendMode
: convertRawProp(
context,
rawProps,
"mixBlendMode",
sourceProps.mixBlendMode,
{})),
isolation(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.isolation
: convertRawProp(
context,
rawProps,
"isolation",
sourceProps.isolation,
{})),
transform(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.transform
: convertRawProp(
context,
rawProps,
"transform",
sourceProps.transform,
{})),
transformOrigin(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.transformOrigin
: convertRawProp(
context,
rawProps,
"transformOrigin",
sourceProps.transformOrigin,
{})),
backfaceVisibility(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.backfaceVisibility
: convertRawProp(
context,
rawProps,
"backfaceVisibility",
sourceProps.backfaceVisibility,
{})),
shouldRasterize(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.shouldRasterize
: convertRawProp(
context,
rawProps,
"shouldRasterizeIOS",
sourceProps.shouldRasterize,
{})),
zIndex(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.zIndex
: convertRawProp(
context,
rawProps,
"zIndex",
sourceProps.zIndex,
{})),
pointerEvents(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.pointerEvents
: convertRawProp(
context,
rawProps,
"pointerEvents",
sourceProps.pointerEvents,
{})),
hitSlop(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.hitSlop
: convertRawProp(
context,
rawProps,
"hitSlop",
sourceProps.hitSlop,
{})),
onLayout(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.onLayout
: convertRawProp(
context,
rawProps,
"onLayout",
sourceProps.onLayout,
{})),
events(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.events
: convertRawProp(context, rawProps, sourceProps.events, {})),
collapsable(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.collapsable
: convertRawProp(
context,
rawProps,
"collapsable",
sourceProps.collapsable,
true)),
collapsableChildren(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.collapsableChildren
: convertRawProp(
context,
rawProps,
"collapsableChildren",
sourceProps.collapsableChildren,
true)),
removeClippedSubviews(
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.removeClippedSubviews
: convertRawProp(
context,
rawProps,
"removeClippedSubviews",
sourceProps.removeClippedSubviews,
false)) {}
#define VIEW_EVENT_CASE(eventType) \
case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \
const auto offset = ViewEvents::Offset::eventType; \
ViewEvents defaultViewEvents{}; \
bool res = defaultViewEvents[offset]; \
if (value.hasValue()) { \
fromRawValue(context, value, res); \
} \
events[offset] = res; \
return; \
}
void BaseViewProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
YogaStylableProps::setProp(context, hash, propName, value);
AccessibilityProps::setProp(context, hash, propName, value);
static auto defaults = BaseViewProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(opacity);
RAW_SET_PROP_SWITCH_CASE_BASIC(backgroundColor);
RAW_SET_PROP_SWITCH_CASE(backgroundImage, "experimental_backgroundImage");
RAW_SET_PROP_SWITCH_CASE(backgroundSize, "experimental_backgroundSize");
RAW_SET_PROP_SWITCH_CASE(
backgroundPosition, "experimental_backgroundPosition");
RAW_SET_PROP_SWITCH_CASE(backgroundRepeat, "experimental_backgroundRepeat");
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOffset);
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOpacity);
RAW_SET_PROP_SWITCH_CASE_BASIC(shadowRadius);
RAW_SET_PROP_SWITCH_CASE_BASIC(transform);
RAW_SET_PROP_SWITCH_CASE_BASIC(backfaceVisibility);
RAW_SET_PROP_SWITCH_CASE_BASIC(shouldRasterize);
RAW_SET_PROP_SWITCH_CASE_BASIC(zIndex);
RAW_SET_PROP_SWITCH_CASE_BASIC(pointerEvents);
RAW_SET_PROP_SWITCH_CASE_BASIC(isolation);
RAW_SET_PROP_SWITCH_CASE_BASIC(hitSlop);
RAW_SET_PROP_SWITCH_CASE_BASIC(onLayout);
RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable);
RAW_SET_PROP_SWITCH_CASE_BASIC(collapsableChildren);
RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews);
RAW_SET_PROP_SWITCH_CASE_BASIC(cursor);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineOffset);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineStyle);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineWidth);
RAW_SET_PROP_SWITCH_CASE_BASIC(filter);
RAW_SET_PROP_SWITCH_CASE_BASIC(boxShadow);
RAW_SET_PROP_SWITCH_CASE_BASIC(mixBlendMode);
// events field
VIEW_EVENT_CASE(PointerEnter);
VIEW_EVENT_CASE(PointerEnterCapture);
VIEW_EVENT_CASE(PointerMove);
VIEW_EVENT_CASE(PointerMoveCapture);
VIEW_EVENT_CASE(PointerLeave);
VIEW_EVENT_CASE(PointerLeaveCapture);
VIEW_EVENT_CASE(PointerOver);
VIEW_EVENT_CASE(PointerOverCapture);
VIEW_EVENT_CASE(PointerOut);
VIEW_EVENT_CASE(PointerOutCapture);
VIEW_EVENT_CASE(MoveShouldSetResponder);
VIEW_EVENT_CASE(MoveShouldSetResponderCapture);
VIEW_EVENT_CASE(StartShouldSetResponder);
VIEW_EVENT_CASE(StartShouldSetResponderCapture);
VIEW_EVENT_CASE(ResponderGrant);
VIEW_EVENT_CASE(ResponderReject);
VIEW_EVENT_CASE(ResponderStart);
VIEW_EVENT_CASE(ResponderEnd);
VIEW_EVENT_CASE(ResponderRelease);
VIEW_EVENT_CASE(ResponderMove);
VIEW_EVENT_CASE(ResponderTerminate);
VIEW_EVENT_CASE(ResponderTerminationRequest);
VIEW_EVENT_CASE(ShouldBlockNativeResponder);
VIEW_EVENT_CASE(TouchStart);
VIEW_EVENT_CASE(TouchMove);
VIEW_EVENT_CASE(TouchEnd);
VIEW_EVENT_CASE(TouchCancel);
// BorderRadii
SET_CASCADED_RECTANGLE_CORNERS(borderRadii, "border", "Radius", value);
SET_CASCADED_RECTANGLE_EDGES(borderColors, "border", "Color", value);
SET_CASCADED_RECTANGLE_EDGES(borderStyles, "border", "Style", value);
}
}
#pragma mark - Convenience Methods
static BorderRadii ensureNoOverlap(const BorderRadii& radii, const Size& size) {
// "Corner curves must not overlap: When the sum of any two adjacent border
// radii exceeds the size of the border box, UAs must proportionally reduce
// the used values of all border radii until none of them overlap."
// Source: https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
float leftEdgeRadii = radii.topLeft.vertical + radii.bottomLeft.vertical;
float topEdgeRadii = radii.topLeft.horizontal + radii.topRight.horizontal;
float rightEdgeRadii = radii.topRight.vertical + radii.bottomRight.vertical;
float bottomEdgeRadii =
radii.bottomLeft.horizontal + radii.bottomRight.horizontal;
float leftEdgeRadiiScale =
(leftEdgeRadii > 0) ? std::min(size.height / leftEdgeRadii, (Float)1) : 0;
float topEdgeRadiiScale =
(topEdgeRadii > 0) ? std::min(size.width / topEdgeRadii, (Float)1) : 0;
float rightEdgeRadiiScale = (rightEdgeRadii > 0)
? std::min(size.height / rightEdgeRadii, (Float)1)
: 0;
float bottomEdgeRadiiScale = (bottomEdgeRadii > 0)
? std::min(size.width / bottomEdgeRadii, (Float)1)
: 0;
return BorderRadii{
.topLeft =
{static_cast<float>(
radii.topLeft.vertical *
std::min(topEdgeRadiiScale, leftEdgeRadiiScale)),
static_cast<float>(
radii.topLeft.horizontal *
std::min(topEdgeRadiiScale, leftEdgeRadiiScale))},
.topRight =
{static_cast<float>(
radii.topRight.vertical *
std::min(topEdgeRadiiScale, rightEdgeRadiiScale)),
static_cast<float>(
radii.topRight.horizontal *
std::min(topEdgeRadiiScale, rightEdgeRadiiScale))},
.bottomLeft =
{static_cast<float>(
radii.bottomLeft.vertical *
std::min(bottomEdgeRadiiScale, leftEdgeRadiiScale)),
static_cast<float>(
radii.bottomLeft.horizontal *
std::min(bottomEdgeRadiiScale, leftEdgeRadiiScale))},
.bottomRight =
{static_cast<float>(
radii.bottomRight.vertical *
std::min(bottomEdgeRadiiScale, rightEdgeRadiiScale)),
static_cast<float>(
radii.bottomRight.horizontal *
std::min(bottomEdgeRadiiScale, rightEdgeRadiiScale))},
};
}
static BorderRadii radiiPercentToPoint(
const RectangleCorners<ValueUnit>& radii,
const Size& size) {
return BorderRadii{
.topLeft =
{radii.topLeft.resolve(size.height),
radii.topLeft.resolve(size.width)},
.topRight =
{radii.topRight.resolve(size.height),
radii.topRight.resolve(size.width)},
.bottomLeft =
{radii.bottomLeft.resolve(size.height),
radii.bottomLeft.resolve(size.width)},
.bottomRight =
{radii.bottomRight.resolve(size.height),
radii.bottomRight.resolve(size.width)},
};
}
CascadedBorderWidths BaseViewProps::getBorderWidths() const {
return CascadedBorderWidths{
.left = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Left)),
.top = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Top)),
.right = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Right)),
.bottom =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Bottom)),
.start = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Start)),
.end = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::End)),
.horizontal =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Horizontal)),
.vertical =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Vertical)),
.all = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::All)),
};
}
BorderMetrics BaseViewProps::resolveBorderMetrics(
const LayoutMetrics& layoutMetrics) const {
auto isRTL =
bool{layoutMetrics.layoutDirection == LayoutDirection::RightToLeft};
auto borderWidths = getBorderWidths();
BorderRadii radii = radiiPercentToPoint(
borderRadii.resolve(isRTL, ValueUnit{0.0f, UnitType::Point}),
layoutMetrics.frame.size);
return {
.borderColors = borderColors.resolve(isRTL, {}),
.borderWidths = borderWidths.resolve(isRTL, 0),
.borderRadii = ensureNoOverlap(radii, layoutMetrics.frame.size),
.borderCurves = borderCurves.resolve(isRTL, BorderCurve::Circular),
.borderStyles = borderStyles.resolve(isRTL, BorderStyle::Solid),
};
}
Transform BaseViewProps::resolveTransform(
const LayoutMetrics& layoutMetrics) const {
const auto& frameSize = layoutMetrics.frame.size;
return resolveTransform(frameSize, transform, transformOrigin);
}
Transform BaseViewProps::resolveTransform(
const Size& frameSize,
const Transform& transform,
const TransformOrigin& transformOrigin) {
auto transformMatrix = Transform{};
// transform is matrix
if (transform.operations.size() == 1 &&
transform.operations[0].type == TransformOperationType::Arbitrary) {
transformMatrix = transform;
} else {
for (const auto& operation : transform.operations) {
transformMatrix = transformMatrix *
Transform::FromTransformOperation(operation, frameSize, transform);
}
}
if (transformOrigin.isSet()) {
std::array<float, 3> translateOffsets = getTranslateForTransformOrigin(
frameSize.width, frameSize.height, transformOrigin);
transformMatrix =
Transform::Translate(
translateOffsets[0], translateOffsets[1], translateOffsets[2]) *
transformMatrix *
Transform::Translate(
-translateOffsets[0], -translateOffsets[1], -translateOffsets[2]);
}
return transformMatrix;
}
bool BaseViewProps::getClipsContentToBounds() const {
return yogaStyle.overflow() != yoga::Overflow::Visible;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList BaseViewProps::getDebugProps() const {
const auto& defaultBaseViewProps = BaseViewProps();
return AccessibilityProps::getDebugProps() +
YogaStylableProps::getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem(
"opacity", opacity, defaultBaseViewProps.opacity),
debugStringConvertibleItem(
"backgroundColor",
backgroundColor,
defaultBaseViewProps.backgroundColor),
debugStringConvertibleItem(
"zIndex", zIndex, defaultBaseViewProps.zIndex.value_or(0)),
debugStringConvertibleItem(
"pointerEvents",
pointerEvents,
defaultBaseViewProps.pointerEvents),
debugStringConvertibleItem(
"transform", transform, defaultBaseViewProps.transform),
debugStringConvertibleItem(
"backgroundImage",
backgroundImage,
defaultBaseViewProps.backgroundImage),
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,127 @@
/*
* 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/components/view/AccessibilityProps.h>
#include <react/renderer/components/view/YogaStylableProps.h>
#include <react/renderer/components/view/primitives.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/BackgroundImage.h>
#include <react/renderer/graphics/BackgroundPosition.h>
#include <react/renderer/graphics/BackgroundRepeat.h>
#include <react/renderer/graphics/BackgroundSize.h>
#include <react/renderer/graphics/BlendMode.h>
#include <react/renderer/graphics/BoxShadow.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Filter.h>
#include <react/renderer/graphics/Isolation.h>
#include <react/renderer/graphics/Transform.h>
#include <optional>
namespace facebook::react {
class BaseViewProps : public YogaStylableProps, public AccessibilityProps {
public:
BaseViewProps() = default;
BaseViewProps(
const PropsParserContext &context,
const BaseViewProps &sourceProps,
const RawProps &rawProps,
const std::function<bool(const std::string &)> &filterObjectKeys = nullptr);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
// Color
Float opacity{1.0};
SharedColor backgroundColor{};
// Borders
CascadedBorderRadii borderRadii{};
CascadedBorderColors borderColors{};
CascadedBorderCurves borderCurves{}; // iOS only?
CascadedBorderStyles borderStyles{};
// Outline
SharedColor outlineColor{};
Float outlineOffset{};
OutlineStyle outlineStyle{OutlineStyle::Solid};
Float outlineWidth{};
// Shadow
SharedColor shadowColor{};
Size shadowOffset{0, -3};
Float shadowOpacity{};
Float shadowRadius{3};
Cursor cursor{};
// Box shadow
std::vector<BoxShadow> boxShadow{};
// Filter
std::vector<FilterFunction> filter{};
// Background Image
std::vector<BackgroundImage> backgroundImage{};
// Background Size
std::vector<BackgroundSize> backgroundSize{};
// Background Position
std::vector<BackgroundPosition> backgroundPosition{};
// Background Repeat
std::vector<BackgroundRepeat> backgroundRepeat{};
// MixBlendMode
BlendMode mixBlendMode{BlendMode::Normal};
// Isolate
Isolation isolation{Isolation::Auto};
// Transform
Transform transform{};
TransformOrigin transformOrigin{};
BackfaceVisibility backfaceVisibility{};
bool shouldRasterize{};
std::optional<int> zIndex{};
// Events
PointerEventsMode pointerEvents{};
EdgeInsets hitSlop{};
bool onLayout{};
ViewEvents events{};
bool collapsable{true};
bool collapsableChildren{true};
bool removeClippedSubviews{false};
#pragma mark - Convenience Methods
CascadedBorderWidths getBorderWidths() const;
BorderMetrics resolveBorderMetrics(const LayoutMetrics &layoutMetrics) const;
Transform resolveTransform(const LayoutMetrics &layoutMetrics) const;
bool getClipsContentToBounds() const;
static Transform
resolveTransform(const Size &frameSize, const Transform &transform, const TransformOrigin &transformOrigin);
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,263 @@
/*
* 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 <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/view/CSSConversions.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/css/CSSShadow.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/graphics/BoxShadow.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace facebook::react {
inline void
parseProcessedBoxShadow(const PropsParserContext &context, const RawValue &value, std::vector<BoxShadow> &result)
{
react_native_expect(value.hasType<std::vector<RawValue>>());
if (!value.hasType<std::vector<RawValue>>()) {
result = {};
return;
}
std::vector<BoxShadow> boxShadows{};
auto rawBoxShadows = static_cast<std::vector<RawValue>>(value);
for (const auto &rawBoxShadow : rawBoxShadows) {
bool isMap = rawBoxShadow.hasType<std::unordered_map<std::string, RawValue>>();
react_native_expect(isMap);
if (!isMap) {
// If any box shadow is malformed then we should not apply any of them
// which is the web behavior.
result = {};
return;
}
auto rawBoxShadowMap = static_cast<std::unordered_map<std::string, RawValue>>(rawBoxShadow);
BoxShadow boxShadow{};
auto offsetX = rawBoxShadowMap.find("offsetX");
react_native_expect(offsetX != rawBoxShadowMap.end());
if (offsetX == rawBoxShadowMap.end()) {
result = {};
return;
}
react_native_expect(offsetX->second.hasType<Float>());
if (!offsetX->second.hasType<Float>()) {
result = {};
return;
}
boxShadow.offsetX = (Float)offsetX->second;
auto offsetY = rawBoxShadowMap.find("offsetY");
react_native_expect(offsetY != rawBoxShadowMap.end());
if (offsetY == rawBoxShadowMap.end()) {
result = {};
return;
}
react_native_expect(offsetY->second.hasType<Float>());
if (!offsetY->second.hasType<Float>()) {
result = {};
return;
}
boxShadow.offsetY = (Float)offsetY->second;
auto blurRadius = rawBoxShadowMap.find("blurRadius");
if (blurRadius != rawBoxShadowMap.end()) {
react_native_expect(blurRadius->second.hasType<Float>());
if (!blurRadius->second.hasType<Float>()) {
result = {};
return;
}
boxShadow.blurRadius = (Float)blurRadius->second;
}
auto spreadDistance = rawBoxShadowMap.find("spreadDistance");
if (spreadDistance != rawBoxShadowMap.end()) {
react_native_expect(spreadDistance->second.hasType<Float>());
if (!spreadDistance->second.hasType<Float>()) {
result = {};
return;
}
boxShadow.spreadDistance = (Float)spreadDistance->second;
}
auto inset = rawBoxShadowMap.find("inset");
if (inset != rawBoxShadowMap.end()) {
react_native_expect(inset->second.hasType<bool>());
if (!inset->second.hasType<bool>()) {
result = {};
return;
}
boxShadow.inset = (bool)inset->second;
}
auto color = rawBoxShadowMap.find("color");
if (color != rawBoxShadowMap.end()) {
fromRawValue(context.contextContainer, context.surfaceId, color->second, boxShadow.color);
}
boxShadows.push_back(boxShadow);
}
result = boxShadows;
}
inline std::optional<BoxShadow> fromCSSShadow(const CSSShadow &cssShadow)
{
// TODO: handle non-px values
if (cssShadow.offsetX.unit != CSSLengthUnit::Px || cssShadow.offsetY.unit != CSSLengthUnit::Px ||
cssShadow.blurRadius.unit != CSSLengthUnit::Px || cssShadow.spreadDistance.unit != CSSLengthUnit::Px) {
return {};
}
return BoxShadow{
.offsetX = cssShadow.offsetX.value,
.offsetY = cssShadow.offsetY.value,
.blurRadius = cssShadow.blurRadius.value,
.spreadDistance = cssShadow.spreadDistance.value,
.color = fromCSSColor(cssShadow.color),
.inset = cssShadow.inset,
};
}
inline void parseUnprocessedBoxShadowString(std::string &&value, std::vector<BoxShadow> &result)
{
auto boxShadowList = parseCSSProperty<CSSShadowList>((std::string)value);
if (!std::holds_alternative<CSSShadowList>(boxShadowList)) {
result = {};
return;
}
for (const auto &cssShadow : std::get<CSSShadowList>(boxShadowList)) {
if (auto boxShadow = fromCSSShadow(cssShadow)) {
result.push_back(*boxShadow);
} else {
result = {};
return;
}
}
}
inline std::optional<BoxShadow> parseBoxShadowRawValue(const PropsParserContext &context, const RawValue &value)
{
if (!value.hasType<std::unordered_map<std::string, RawValue>>()) {
return {};
}
auto boxShadow = std::unordered_map<std::string, RawValue>(value);
auto rawOffsetX = boxShadow.find("offsetX");
if (rawOffsetX == boxShadow.end()) {
return {};
}
auto offsetX = coerceLength(rawOffsetX->second);
if (!offsetX.has_value()) {
return {};
}
auto rawOffsetY = boxShadow.find("offsetY");
if (rawOffsetY == boxShadow.end()) {
return {};
}
auto offsetY = coerceLength(rawOffsetY->second);
if (!offsetY.has_value()) {
return {};
}
Float blurRadius = 0;
auto rawBlurRadius = boxShadow.find("blurRadius");
if (rawBlurRadius != boxShadow.end()) {
if (auto blurRadiusValue = coerceLength(rawBlurRadius->second)) {
if (*blurRadiusValue < 0) {
return {};
}
blurRadius = *blurRadiusValue;
} else {
return {};
}
}
Float spreadDistance = 0;
auto rawSpreadDistance = boxShadow.find("spreadDistance");
if (rawSpreadDistance != boxShadow.end()) {
if (auto spreadDistanceValue = coerceLength(rawSpreadDistance->second)) {
spreadDistance = *spreadDistanceValue;
} else {
return {};
}
}
bool inset = false;
auto rawInset = boxShadow.find("inset");
if (rawInset != boxShadow.end()) {
if (rawInset->second.hasType<bool>()) {
inset = (bool)rawInset->second;
} else {
return {};
}
}
SharedColor color;
auto rawColor = boxShadow.find("color");
if (rawColor != boxShadow.end()) {
color = coerceColor(rawColor->second, context);
if (!color) {
return {};
}
}
return BoxShadow{
.offsetX = *offsetX,
.offsetY = *offsetY,
.blurRadius = blurRadius,
.spreadDistance = spreadDistance,
.color = color,
.inset = inset};
}
inline void parseUnprocessedBoxShadowList(
const PropsParserContext &context,
std::vector<RawValue> &&value,
std::vector<BoxShadow> &result)
{
for (const auto &rawValue : value) {
if (auto boxShadow = parseBoxShadowRawValue(context, rawValue)) {
result.push_back(*boxShadow);
} else {
result = {};
return;
}
}
}
inline void
parseUnprocessedBoxShadow(const PropsParserContext &context, const RawValue &value, std::vector<BoxShadow> &result)
{
if (value.hasType<std::string>()) {
parseUnprocessedBoxShadowString((std::string)value, result);
} else if (value.hasType<std::vector<RawValue>>()) {
parseUnprocessedBoxShadowList(context, (std::vector<RawValue>)value, result);
} else {
result = {};
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, std::vector<BoxShadow> &result)
{
if (ReactNativeFeatureFlags::enableNativeCSSParsing()) {
parseUnprocessedBoxShadow(context, value, result);
} else {
parseProcessedBoxShadow(context, value, result);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
# 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/components/view/*.cpp
platform/cxx/react/renderer/components/view/*.cpp
)
file(GLOB rrc_view_SRC CONFIGURE_DEPENDS
*.cpp
${platform_SRC})
add_library(rrc_view OBJECT ${rrc_view_SRC})
react_native_android_selector(platform_DIR
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/)
target_include_directories(rrc_view PUBLIC ${REACT_COMMON_DIR} ${platform_DIR})
target_link_libraries(rrc_view
folly_runtime
glog
glog_init
jsi
logger
react_debug
react_renderer_core
react_renderer_css
react_renderer_debug
react_renderer_graphics
yoga)
target_compile_reactnative_options(rrc_view PRIVATE)
target_compile_options(rrc_view PRIVATE -Wpedantic)

View File

@@ -0,0 +1,97 @@
/*
* 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/PropsParserContext.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/css/CSSColor.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSPercentage.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
inline SharedColor fromCSSColor(const CSSColor &cssColor)
{
return hostPlatformColorFromRGBA(cssColor.r, cssColor.g, cssColor.b, cssColor.a);
}
inline std::optional<Float> coerceAmount(const RawValue &value)
{
if (value.hasType<Float>()) {
return (Float)value;
}
if (value.hasType<std::string>()) {
auto cssVal = parseCSSProperty<CSSNumber, CSSPercentage>((std::string)value);
if (std::holds_alternative<CSSNumber>(cssVal)) {
return std::get<CSSNumber>(cssVal).value;
} else if (std::holds_alternative<CSSPercentage>(cssVal)) {
return std::get<CSSPercentage>(cssVal).value / 100.0f;
}
}
return {};
}
inline std::optional<Float> coerceAngle(const RawValue &value)
{
if (value.hasType<Float>()) {
return (Float)value;
}
if (value.hasType<std::string>()) {
auto cssVal = parseCSSProperty<CSSAngle>((std::string)value);
if (std::holds_alternative<CSSAngle>(cssVal)) {
return std::get<CSSAngle>(cssVal).degrees;
}
}
return {};
}
inline SharedColor coerceColor(const RawValue &value, const PropsParserContext &context)
{
if (value.hasType<std::string>()) {
auto cssColor = parseCSSProperty<CSSColor>((std::string)value);
if (!std::holds_alternative<CSSColor>(cssColor)) {
return {};
}
return fromCSSColor(std::get<CSSColor>(cssColor));
}
SharedColor color;
fromRawValue(context.contextContainer, context.surfaceId, value, color);
return color;
}
inline std::optional<Float> coerceLength(const RawValue &value)
{
if (value.hasType<Float>()) {
return (Float)value;
}
if (value.hasType<std::string>()) {
auto len = parseCSSProperty<CSSLength>((std::string)value);
if (!std::holds_alternative<CSSLength>(len)) {
return {};
}
auto cssLen = std::get<CSSLength>(len);
if (cssLen.unit != CSSLengthUnit::Px) {
return {};
}
return cssLen.value;
}
return {};
}
} // namespace facebook::react

View File

@@ -0,0 +1,126 @@
/*
* 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/components/view/HostPlatformViewTraitsInitializer.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
#include <react/renderer/core/ConcreteShadowNode.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <react/renderer/debug/DebugStringConvertibleItem.h>
#include <type_traits>
namespace facebook::react {
/*
* Template for all <View>-like classes (classes which have all same props
* as <View> and similar basic behaviour).
* For example: <Paragraph>, <Image>, but not <Text>, <RawText>.
*/
template <
const char *concreteComponentName,
typename ViewPropsT = ViewProps,
typename ViewEventEmitterT = ViewEventEmitter,
typename StateDataT = StateData>
requires(std::is_base_of_v<ViewProps, ViewPropsT>)
class ConcreteViewShadowNode : public ConcreteShadowNode<
concreteComponentName,
YogaLayoutableShadowNode,
ViewPropsT,
ViewEventEmitterT,
StateDataT> {
static_assert(std::is_base_of<ViewProps, ViewPropsT>::value, "ViewPropsT must be a descendant of ViewProps");
static_assert(
std::is_base_of<YogaStylableProps, ViewPropsT>::value,
"ViewPropsT must be a descendant of YogaStylableProps");
static_assert(
std::is_base_of<AccessibilityProps, ViewPropsT>::value,
"ViewPropsT must be a descendant of AccessibilityProps");
public:
using BaseShadowNode =
ConcreteShadowNode<concreteComponentName, YogaLayoutableShadowNode, ViewPropsT, ViewEventEmitterT, StateDataT>;
ConcreteViewShadowNode(
const ShadowNodeFragment &fragment,
const ShadowNodeFamily::Shared &family,
ShadowNodeTraits traits)
: BaseShadowNode(fragment, family, traits)
{
initialize();
}
ConcreteViewShadowNode(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
: BaseShadowNode(sourceShadowNode, fragment)
{
initialize();
}
using ConcreteViewProps = ViewPropsT;
using BaseShadowNode::BaseShadowNode;
static ShadowNodeTraits BaseTraits()
{
auto traits = BaseShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::ViewKind);
traits.set(ShadowNodeTraits::Trait::FormsStackingContext);
traits.set(ShadowNodeTraits::Trait::FormsView);
return traits;
}
Transform getTransform() const override
{
auto layoutMetrics = BaseShadowNode::getLayoutMetrics();
return BaseShadowNode::getConcreteProps().resolveTransform(layoutMetrics);
}
bool canBeTouchTarget() const override
{
auto pointerEvents = BaseShadowNode::getConcreteProps().ViewProps::pointerEvents;
return pointerEvents == PointerEventsMode::Auto || pointerEvents == PointerEventsMode::BoxOnly;
}
bool canChildrenBeTouchTarget() const override
{
auto pointerEvents = BaseShadowNode::getConcreteProps().ViewProps::pointerEvents;
return pointerEvents == PointerEventsMode::Auto || pointerEvents == PointerEventsMode::BoxNone;
}
private:
void initialize() noexcept
{
auto &props = BaseShadowNode::getConcreteProps();
if (props.yogaStyle.display() == yoga::Display::None) {
BaseShadowNode::traits_.set(ShadowNodeTraits::Trait::Hidden);
} else {
BaseShadowNode::traits_.unset(ShadowNodeTraits::Trait::Hidden);
}
// `zIndex` is only defined for non-`static` positioned views.
if (props.yogaStyle.positionType() != yoga::PositionType::Static) {
BaseShadowNode::orderIndex_ = props.zIndex.value_or(0);
} else {
BaseShadowNode::orderIndex_ = 0;
}
bool isKeyboardFocusable = HostPlatformViewTraitsInitializer::isKeyboardFocusable(props) || props.accessible;
if (isKeyboardFocusable) {
BaseShadowNode::traits_.set(ShadowNodeTraits::Trait::KeyboardFocusable);
} else {
BaseShadowNode::traits_.unset(ShadowNodeTraits::Trait::KeyboardFocusable);
}
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,362 @@
/*
* 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 <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/view/CSSConversions.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/css/CSSFilter.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/graphics/Filter.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace facebook::react {
inline void
parseProcessedFilter(const PropsParserContext &context, const RawValue &value, std::vector<FilterFunction> &result)
{
react_native_expect(value.hasType<std::vector<RawValue>>());
if (!value.hasType<std::vector<RawValue>>()) {
result = {};
return;
}
std::vector<FilterFunction> filter{};
auto rawFilter = static_cast<std::vector<RawValue>>(value);
for (const auto &rawFilterPrimitive : rawFilter) {
bool isMap = rawFilterPrimitive.hasType<std::unordered_map<std::string, RawValue>>();
react_native_expect(isMap);
if (!isMap) {
// If a filter is malformed then we should not apply any of them which
// is the web behavior.
result = {};
return;
}
auto rawFilterFunction = static_cast<std::unordered_map<std::string, RawValue>>(rawFilterPrimitive);
FilterFunction filterFunction{};
try {
filterFunction.type = filterTypeFromString(rawFilterFunction.begin()->first);
if (filterFunction.type == FilterType::DropShadow) {
auto rawDropShadow = static_cast<std::unordered_map<std::string, RawValue>>(rawFilterFunction.begin()->second);
DropShadowParams dropShadowParams{};
auto offsetX = rawDropShadow.find("offsetX");
react_native_expect(offsetX != rawDropShadow.end());
if (offsetX == rawDropShadow.end()) {
result = {};
return;
}
react_native_expect(offsetX->second.hasType<Float>());
if (!offsetX->second.hasType<Float>()) {
result = {};
return;
}
dropShadowParams.offsetX = (Float)offsetX->second;
auto offsetY = rawDropShadow.find("offsetY");
react_native_expect(offsetY != rawDropShadow.end());
if (offsetY == rawDropShadow.end()) {
result = {};
return;
}
react_native_expect(offsetY->second.hasType<Float>());
if (!offsetY->second.hasType<Float>()) {
result = {};
return;
}
dropShadowParams.offsetY = (Float)offsetY->second;
auto standardDeviation = rawDropShadow.find("standardDeviation");
if (standardDeviation != rawDropShadow.end()) {
react_native_expect(standardDeviation->second.hasType<Float>());
if (!standardDeviation->second.hasType<Float>()) {
result = {};
return;
}
dropShadowParams.standardDeviation = (Float)standardDeviation->second;
}
auto color = rawDropShadow.find("color");
if (color != rawDropShadow.end()) {
fromRawValue(context.contextContainer, context.surfaceId, color->second, dropShadowParams.color);
}
filterFunction.parameters = dropShadowParams;
} else {
filterFunction.parameters = (float)rawFilterFunction.begin()->second;
}
filter.push_back(std::move(filterFunction));
} catch (const std::exception &e) {
LOG(ERROR) << "Could not parse FilterFunction: " << e.what();
result = {};
return;
}
}
result = filter;
}
inline FilterType filterTypeFromVariant(const CSSFilterFunctionVariant &filter)
{
return std::visit(
[](auto &&filter) -> FilterType {
using FilterT = std::decay_t<decltype(filter)>;
if constexpr (std::is_same_v<FilterT, CSSBlurFilter>) {
return FilterType::Blur;
}
if constexpr (std::is_same_v<FilterT, CSSBrightnessFilter>) {
return FilterType::Brightness;
}
if constexpr (std::is_same_v<FilterT, CSSContrastFilter>) {
return FilterType::Contrast;
}
if constexpr (std::is_same_v<FilterT, CSSDropShadowFilter>) {
return FilterType::DropShadow;
}
if constexpr (std::is_same_v<FilterT, CSSGrayscaleFilter>) {
return FilterType::Grayscale;
}
if constexpr (std::is_same_v<FilterT, CSSHueRotateFilter>) {
return FilterType::HueRotate;
}
if constexpr (std::is_same_v<FilterT, CSSInvertFilter>) {
return FilterType::Invert;
}
if constexpr (std::is_same_v<FilterT, CSSOpacityFilter>) {
return FilterType::Opacity;
}
if constexpr (std::is_same_v<FilterT, CSSSaturateFilter>) {
return FilterType::Saturate;
}
if constexpr (std::is_same_v<FilterT, CSSSepiaFilter>) {
return FilterType::Sepia;
}
},
filter);
}
inline std::optional<FilterFunction> fromCSSFilter(const CSSFilterFunctionVariant &cssFilter)
{
return std::visit(
[&](auto &&filter) -> std::optional<FilterFunction> {
using FilterT = std::decay_t<decltype(filter)>;
if constexpr (std::is_same_v<FilterT, CSSBlurFilter>) {
// TODO: support non-px values
if (filter.amount.unit != CSSLengthUnit::Px) {
return {};
}
return FilterFunction{
.type = filterTypeFromVariant(cssFilter),
.parameters = filter.amount.value,
};
}
if constexpr (std::is_same_v<FilterT, CSSDropShadowFilter>) {
// TODO: support non-px values
if (filter.offsetX.unit != CSSLengthUnit::Px || filter.offsetY.unit != CSSLengthUnit::Px ||
filter.standardDeviation.unit != CSSLengthUnit::Px) {
return {};
}
return FilterFunction{
.type = FilterType::DropShadow,
.parameters = DropShadowParams{
.offsetX = filter.offsetX.value,
.offsetY = filter.offsetY.value,
.standardDeviation = filter.standardDeviation.value,
.color = fromCSSColor(filter.color),
}};
}
if constexpr (
std::is_same_v<FilterT, CSSBrightnessFilter> || std::is_same_v<FilterT, CSSContrastFilter> ||
std::is_same_v<FilterT, CSSGrayscaleFilter> || std::is_same_v<FilterT, CSSInvertFilter> ||
std::is_same_v<FilterT, CSSOpacityFilter> || std::is_same_v<FilterT, CSSSaturateFilter> ||
std::is_same_v<FilterT, CSSSepiaFilter>) {
return FilterFunction{
.type = filterTypeFromVariant(cssFilter),
.parameters = filter.amount,
};
}
if constexpr (std::is_same_v<FilterT, CSSHueRotateFilter>) {
return FilterFunction{
.type = filterTypeFromVariant(cssFilter),
.parameters = filter.degrees,
};
}
},
cssFilter);
}
inline void parseUnprocessedFilterString(std::string &&value, std::vector<FilterFunction> &result)
{
auto filterList = parseCSSProperty<CSSFilterList>((std::string)value);
if (!std::holds_alternative<CSSFilterList>(filterList)) {
result = {};
return;
}
for (const auto &cssFilter : std::get<CSSFilterList>(filterList)) {
if (auto filter = fromCSSFilter(cssFilter)) {
result.push_back(*filter);
} else {
result = {};
return;
}
}
}
inline std::optional<FilterFunction> parseDropShadow(const PropsParserContext &context, const RawValue &value)
{
if (value.hasType<std::string>()) {
auto val = parseCSSProperty<CSSDropShadowFilter>(std::string("drop-shadow(") + (std::string)value + ")");
if (std::holds_alternative<CSSDropShadowFilter>(val)) {
return fromCSSFilter(std::get<CSSDropShadowFilter>(val));
}
return {};
}
if (!value.hasType<std::unordered_map<std::string, RawValue>>()) {
return {};
}
auto rawDropShadow = static_cast<std::unordered_map<std::string, RawValue>>(value);
DropShadowParams dropShadowParams{};
auto offsetX = rawDropShadow.find("offsetX");
if (offsetX == rawDropShadow.end()) {
return {};
}
if (auto parsedOffsetX = coerceLength(offsetX->second)) {
dropShadowParams.offsetX = *parsedOffsetX;
} else {
return {};
}
auto offsetY = rawDropShadow.find("offsetY");
if (offsetY == rawDropShadow.end()) {
return {};
}
if (auto parsedOffsetY = coerceLength(offsetY->second)) {
dropShadowParams.offsetY = *parsedOffsetY;
} else {
return {};
}
auto standardDeviation = rawDropShadow.find("standardDeviation");
if (standardDeviation != rawDropShadow.end()) {
if (auto parsedStandardDeviation = coerceLength(standardDeviation->second)) {
if (*parsedStandardDeviation < 0.0f) {
return {};
}
dropShadowParams.standardDeviation = *parsedStandardDeviation;
} else {
return {};
}
}
auto color = rawDropShadow.find("color");
if (color != rawDropShadow.end()) {
if (auto parsedColor = coerceColor(color->second, context)) {
dropShadowParams.color = *parsedColor;
} else {
return {};
}
}
return FilterFunction{.type = FilterType::DropShadow, .parameters = dropShadowParams};
}
inline std::optional<FilterFunction> parseFilterRawValue(const PropsParserContext &context, const RawValue &value)
{
if (!value.hasType<std::unordered_map<std::string, RawValue>>()) {
return {};
}
auto rawFilter = static_cast<std::unordered_map<std::string, RawValue>>(value);
if (rawFilter.size() != 1) {
return {};
}
const auto &filterKey = rawFilter.begin()->first;
if (filterKey == "drop-shadow") {
return parseDropShadow(context, rawFilter.begin()->second);
} else if (filterKey == "blur") {
if (auto length = coerceLength(rawFilter.begin()->second)) {
if (*length < 0.0f) {
return {};
}
return FilterFunction{.type = FilterType::Blur, .parameters = *length};
}
return {};
} else if (filterKey == "hue-rotate") {
if (auto angle = coerceAngle(rawFilter.begin()->second)) {
return FilterFunction{.type = FilterType::HueRotate, .parameters = *angle};
}
return {};
} else {
if (auto amount = coerceAmount(rawFilter.begin()->second)) {
if (*amount < 0.0f) {
return {};
}
return FilterFunction{.type = filterTypeFromString(filterKey), .parameters = *amount};
}
return {};
}
}
inline void parseUnprocessedFilterList(
const PropsParserContext &context,
std::vector<RawValue> &&value,
std::vector<FilterFunction> &result)
{
for (const auto &rawValue : value) {
if (auto Filter = parseFilterRawValue(context, rawValue)) {
result.push_back(*Filter);
} else {
result = {};
return;
}
}
}
inline void
parseUnprocessedFilter(const PropsParserContext &context, const RawValue &value, std::vector<FilterFunction> &result)
{
if (value.hasType<std::string>()) {
parseUnprocessedFilterString((std::string)value, result);
} else if (value.hasType<std::vector<RawValue>>()) {
parseUnprocessedFilterList(context, (std::vector<RawValue>)value, result);
} else {
result = {};
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, std::vector<FilterFunction> &result)
{
if (ReactNativeFeatureFlags::enableNativeCSSParsing()) {
parseUnprocessedFilter(context, value, result);
} else {
parseProcessedFilter(context, value, result);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,17 @@
/*
* 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/components/view/LayoutConformanceShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using LayoutConformanceComponentDescriptor = ConcreteComponentDescriptor<LayoutConformanceShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* 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/components/view/YogaStylableProps.h>
#include <react/renderer/components/view/propsConversions.h>
namespace facebook::react {
struct LayoutConformanceProps final : public YogaStylableProps {
/**
* Whether to layout the subtree with strict conformance to W3C standard
* (YGErrataNone) or for compatibility with legacy RN bugs (YGErrataAll)
*/
LayoutConformance mode{LayoutConformance::Strict};
LayoutConformanceProps() = default;
LayoutConformanceProps(
const PropsParserContext &context,
const LayoutConformanceProps &sourceProps,
const RawProps &rawProps)
: YogaStylableProps(context, sourceProps, rawProps),
mode{convertRawProp(context, rawProps, "mode", mode, LayoutConformance::Strict)}
{
}
};
} // 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/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/LayoutConformanceProps.h>
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
namespace facebook::react {
// NOLINTNEXTLINE(facebook-hte-CArray,modernize-avoid-c-arrays)
constexpr const char LayoutConformanceShadowNodeComponentName[] = "LayoutConformance";
class LayoutConformanceShadowNode final : public ConcreteShadowNode<
LayoutConformanceShadowNodeComponentName,
YogaLayoutableShadowNode,
LayoutConformanceProps> {
public:
using ConcreteShadowNode::ConcreteShadowNode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,106 @@
/*
* 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 "PointerEvent.h"
namespace facebook::react {
jsi::Value PointerEvent::asJSIValue(jsi::Runtime& runtime) const {
auto object = jsi::Object(runtime);
object.setProperty(runtime, "pointerId", this->pointerId);
object.setProperty(runtime, "pressure", this->pressure);
object.setProperty(runtime, "pointerType", this->pointerType);
object.setProperty(runtime, "clientX", this->clientPoint.x);
object.setProperty(runtime, "clientY", this->clientPoint.y);
// x/y are an alias to clientX/Y
object.setProperty(runtime, "x", this->clientPoint.x);
object.setProperty(runtime, "y", this->clientPoint.y);
// since RN doesn't have a scrollable root, pageX/Y will always equal
// clientX/Y
object.setProperty(runtime, "pageX", this->clientPoint.x);
object.setProperty(runtime, "pageY", this->clientPoint.y);
object.setProperty(runtime, "screenX", this->screenPoint.x);
object.setProperty(runtime, "screenY", this->screenPoint.y);
object.setProperty(runtime, "offsetX", this->offsetPoint.x);
object.setProperty(runtime, "offsetY", this->offsetPoint.y);
object.setProperty(runtime, "width", this->width);
object.setProperty(runtime, "height", this->height);
object.setProperty(runtime, "tiltX", this->tiltX);
object.setProperty(runtime, "tiltY", this->tiltY);
object.setProperty(runtime, "detail", this->detail);
object.setProperty(runtime, "buttons", this->buttons);
object.setProperty(runtime, "tangentialPressure", this->tangentialPressure);
object.setProperty(runtime, "twist", this->twist);
object.setProperty(runtime, "ctrlKey", this->ctrlKey);
object.setProperty(runtime, "shiftKey", this->shiftKey);
object.setProperty(runtime, "altKey", this->altKey);
object.setProperty(runtime, "metaKey", this->metaKey);
object.setProperty(runtime, "isPrimary", this->isPrimary);
object.setProperty(runtime, "button", this->button);
return object;
}
EventPayloadType PointerEvent::getType() const {
return EventPayloadType::PointerEvent;
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const PointerEvent& /*pointerEvent*/) {
return "PointerEvent";
}
std::vector<DebugStringConvertibleObject> getDebugProps(
const PointerEvent& pointerEvent,
DebugStringConvertibleOptions options) {
return {
{.name = "pointerId",
.value = getDebugDescription(pointerEvent.pointerId, options)},
{.name = "pressure",
.value = getDebugDescription(pointerEvent.pressure, options)},
{.name = "pointerType",
.value = getDebugDescription(pointerEvent.pointerType, options)},
{.name = "clientPoint",
.value = getDebugDescription(pointerEvent.clientPoint, options)},
{.name = "screenPoint",
.value = getDebugDescription(pointerEvent.screenPoint, options)},
{.name = "offsetPoint",
.value = getDebugDescription(pointerEvent.offsetPoint, options)},
{.name = "width",
.value = getDebugDescription(pointerEvent.width, options)},
{.name = "height",
.value = getDebugDescription(pointerEvent.height, options)},
{.name = "tiltX",
.value = getDebugDescription(pointerEvent.tiltX, options)},
{.name = "tiltY",
.value = getDebugDescription(pointerEvent.tiltY, options)},
{.name = "detail",
.value = getDebugDescription(pointerEvent.detail, options)},
{.name = "buttons",
.value = getDebugDescription(pointerEvent.buttons, options)},
{.name = "tangentialPressure",
.value = getDebugDescription(pointerEvent.tangentialPressure, options)},
{.name = "twist",
.value = getDebugDescription(pointerEvent.twist, options)},
{.name = "ctrlKey",
.value = getDebugDescription(pointerEvent.ctrlKey, options)},
{.name = "shiftKey",
.value = getDebugDescription(pointerEvent.shiftKey, options)},
{.name = "altKey",
.value = getDebugDescription(pointerEvent.altKey, options)},
{.name = "metaKey",
.value = getDebugDescription(pointerEvent.metaKey, options)},
{.name = "isPrimary",
.value = getDebugDescription(pointerEvent.isPrimary, options)},
{.name = "button",
.value = getDebugDescription(pointerEvent.button, options)},
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,130 @@
/*
* 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/EventPayload.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Point.h>
namespace facebook::react {
struct PointerEvent : public EventPayload {
/*
* A unique identifier for the pointer causing the event.
*/
int pointerId;
/*
* The normalized pressure of the pointer input in the range 0 to 1, where 0
* and 1 represent the minimum and maximum pressure the hardware is capable of
* detecting, respectively.
*/
Float pressure;
/*
* Indicates the device type that caused the event (mouse, pen, touch, etc.)
*/
std::string pointerType;
/*
* Point within the application's viewport at which the event occurred (as
* opposed to the coordinate within the page).
*/
Point clientPoint;
/*
* The X/Y coordinate of the pointer in global (screen) coordinates.
*/
Point screenPoint;
/*
* The X/Y coordinate of the pointer relative to the position of the padding
* edge of the target node.
*/
Point offsetPoint;
/*
* The width (magnitude on the X axis), in CSS pixels, of the contact geometry
* of the pointer
*/
Float width;
/*
* The height (magnitude on the y axis), in CSS pixels, of the contact
* geometry of the pointer
*/
Float height;
/*
* The plane angle (in degrees, in the range of -90 to 90) between the YZ
* plane and the plane containing both the pointer (e.g. pen stylus) axis and
* the Y axis.
*/
int tiltX;
/*
* The plane angle (in degrees, in the range of -90 to 90) between the XZ
* plane and the plane containing both the pointer (e.g. pen stylus) axis and
* the X axis.
*/
int tiltY;
/*
* Returns a long with details about the event, depending on the event type.
*/
int detail;
/*
* The buttons being depressed (if any) when the mouse event was fired.
*/
int buttons;
/*
* The normalized tangential pressure of the pointer input (also known as
* barrel pressure or cylinder stress) in the range -1 to 1, where 0 is the
* neutral position of the control.
*/
Float tangentialPressure;
/*
* The clockwise rotation of the pointer (e.g. pen stylus) around its major
* axis in degrees, with a value in the range 0 to 359.
*/
int twist;
/*
* Returns true if the control key was down when the event was fired.
*/
bool ctrlKey;
/*
* Returns true if the shift key was down when the event was fired.
*/
bool shiftKey;
/*
* Returns true if the alt key was down when the event was fired.
*/
bool altKey;
/*
* Returns true if the meta key was down when the event was fired.
*/
bool metaKey;
/*
* Indicates if the pointer represents the primary pointer of this pointer
* type.
*/
bool isPrimary;
/*
* The button number that was pressed (if applicable) when the pointer event
* was fired.
*/
int button;
/*
* EventPayload implementations
*/
jsi::Value asJSIValue(jsi::Runtime &runtime) const override;
EventPayloadType getType() const override;
};
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const PointerEvent &pointerEvent);
std::vector<DebugStringConvertibleObject> getDebugProps(
const PointerEvent &pointerEvent,
DebugStringConvertibleOptions options);
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,15 @@
/*
* 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/components/view/HostPlatformTouch.h>
namespace facebook::react {
using Touch = HostPlatformTouch;
using Touches = std::unordered_set<Touch, Touch::Hasher, Touch::Comparator>;
} // namespace facebook::react

View File

@@ -0,0 +1,33 @@
/*
* 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 "TouchEvent.h"
namespace facebook::react {
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const TouchEvent& /*touchEvent*/) {
return "TouchEvent";
}
std::vector<DebugStringConvertibleObject> getDebugProps(
const TouchEvent& touchEvent,
DebugStringConvertibleOptions options) {
return {
{.name = "touches",
.value = getDebugDescription(touchEvent.touches, options)},
{.name = "changedTouches",
.value = getDebugDescription(touchEvent.changedTouches, options)},
{.name = "targetTouches",
.value = getDebugDescription(touchEvent.targetTouches, options)},
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,51 @@
/*
* 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/DebugStringConvertible.h>
#include <unordered_set>
#include <react/renderer/components/view/Touch.h>
namespace facebook::react {
/*
* Defines the `touchstart`, `touchend`, `touchmove`, and `touchcancel` event
* types.
*/
struct TouchEvent {
/*
* A list of Touches for every point of contact currently touching the
* surface.
*/
Touches touches;
/*
* A list of Touches for every point of contact which contributed to the
* event.
*/
Touches changedTouches;
/*
* A list of Touches for every point of contact that is touching the surface
* and started on the element that is the target of the current event.
*/
Touches targetTouches;
};
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const TouchEvent &touchEvent);
std::vector<DebugStringConvertibleObject> getDebugProps(
const TouchEvent &touchEvent,
DebugStringConvertibleOptions options);
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,147 @@
/*
* 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 "TouchEventEmitter.h"
namespace facebook::react {
#pragma mark - Touches
static jsi::Value touchesPayload(
jsi::Runtime& runtime,
const Touches& touches) {
auto array = jsi::Array(runtime, touches.size());
int i = 0;
for (const auto& touch : touches) {
auto object = jsi::Object(runtime);
setTouchPayloadOnObject(object, runtime, touch);
array.setValueAtIndex(runtime, i++, object);
}
return array;
}
static jsi::Value touchEventPayload(
jsi::Runtime& runtime,
const TouchEvent& event) {
auto object = jsi::Object(runtime);
object.setProperty(
runtime, "touches", touchesPayload(runtime, event.touches));
object.setProperty(
runtime, "changedTouches", touchesPayload(runtime, event.changedTouches));
object.setProperty(
runtime, "targetTouches", touchesPayload(runtime, event.targetTouches));
if (!event.changedTouches.empty()) {
const auto& firstChangedTouch = *event.changedTouches.begin();
setTouchPayloadOnObject(object, runtime, firstChangedTouch);
}
return object;
}
void TouchEventEmitter::dispatchTouchEvent(
std::string type,
TouchEvent event,
RawEvent::Category category) const {
dispatchEvent(
std::move(type),
[event = std::move(event)](jsi::Runtime& runtime) {
return touchEventPayload(runtime, event);
},
category);
}
void TouchEventEmitter::dispatchPointerEvent(
std::string type,
PointerEvent event,
RawEvent::Category category) const {
dispatchEvent(
std::move(type),
std::make_shared<PointerEvent>(std::move(event)),
category);
}
void TouchEventEmitter::onTouchStart(TouchEvent event) const {
dispatchTouchEvent(
"touchStart", std::move(event), RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onTouchMove(TouchEvent event) const {
dispatchUniqueEvent(
"touchMove", [event = std::move(event)](jsi::Runtime& runtime) {
return touchEventPayload(runtime, event);
});
}
void TouchEventEmitter::onTouchEnd(TouchEvent event) const {
dispatchTouchEvent(
"touchEnd", std::move(event), RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onTouchCancel(TouchEvent event) const {
dispatchTouchEvent(
"touchCancel", std::move(event), RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onClick(PointerEvent event) const {
dispatchPointerEvent("click", std::move(event), RawEvent::Category::Discrete);
}
void TouchEventEmitter::onPointerCancel(PointerEvent event) const {
dispatchPointerEvent(
"pointerCancel", std::move(event), RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onPointerDown(PointerEvent event) const {
dispatchPointerEvent(
"pointerDown", std::move(event), RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onPointerMove(PointerEvent event) const {
dispatchUniqueEvent(
"pointerMove", std::make_shared<PointerEvent>(std::move(event)));
}
void TouchEventEmitter::onPointerUp(PointerEvent event) const {
dispatchPointerEvent(
"pointerUp", std::move(event), RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onPointerEnter(PointerEvent event) const {
dispatchPointerEvent(
"pointerEnter", std::move(event), RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onPointerLeave(PointerEvent event) const {
dispatchPointerEvent(
"pointerLeave", std::move(event), RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onPointerOver(PointerEvent event) const {
dispatchPointerEvent(
"pointerOver", std::move(event), RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onPointerOut(PointerEvent event) const {
dispatchPointerEvent(
"pointerOut", std::move(event), RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onGotPointerCapture(PointerEvent event) const {
dispatchPointerEvent(
"gotPointerCapture",
std::move(event),
RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onLostPointerCapture(PointerEvent event) const {
dispatchPointerEvent(
"lostPointerCapture",
std::move(event),
RawEvent::Category::ContinuousEnd);
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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/components/view/PointerEvent.h>
#include <react/renderer/components/view/TouchEvent.h>
#include <react/renderer/core/EventEmitter.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class TouchEventEmitter;
using SharedTouchEventEmitter = std::shared_ptr<const TouchEventEmitter>;
class TouchEventEmitter : public EventEmitter {
public:
using EventEmitter::EventEmitter;
void onTouchStart(TouchEvent event) const;
void onTouchMove(TouchEvent event) const;
void onTouchEnd(TouchEvent event) const;
void onTouchCancel(TouchEvent event) const;
void onClick(PointerEvent event) const;
void onPointerCancel(PointerEvent event) const;
void onPointerDown(PointerEvent event) const;
void onPointerMove(PointerEvent event) const;
void onPointerUp(PointerEvent event) const;
void onPointerEnter(PointerEvent event) const;
void onPointerLeave(PointerEvent event) const;
void onPointerOver(PointerEvent event) const;
void onPointerOut(PointerEvent event) const;
void onGotPointerCapture(PointerEvent event) const;
void onLostPointerCapture(PointerEvent event) const;
private:
void dispatchTouchEvent(std::string type, TouchEvent event, RawEvent::Category category) const;
void dispatchPointerEvent(std::string type, PointerEvent event, RawEvent::Category category) const;
};
} // namespace facebook::react

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.
*/
#pragma once
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
class ViewComponentDescriptor : public ConcreteComponentDescriptor<ViewShadowNode> {
public:
ViewComponentDescriptor(const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor<ViewShadowNode>(parameters)
{
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,15 @@
/*
* 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/components/view/HostPlatformViewEventEmitter.h>
namespace facebook::react {
using ViewEventEmitter = HostPlatformViewEventEmitter;
using SharedViewEventEmitter = std::shared_ptr<const ViewEventEmitter>;
} // namespace facebook::react

View File

@@ -0,0 +1,15 @@
/*
* 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/components/view/HostPlatformViewProps.h>
namespace facebook::react {
using ViewProps = HostPlatformViewProps;
using SharedViewProps = std::shared_ptr<const ViewProps>;
} // namespace facebook::react

View File

@@ -0,0 +1,56 @@
/*
* 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/components/view/ViewProps.h>
#include <react/renderer/graphics/Transform.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook::react {
/**
* Given animation progress, old props, new props, and an "interpolated" shared
* props struct, this will mutate the "interpolated" struct in-place to give it
* values interpolated between the old and new props.
*/
static inline void interpolateViewProps(
Float animationProgress,
const Props::Shared &oldPropsShared,
const Props::Shared &newPropsShared,
Props::Shared &interpolatedPropsShared,
const Size &size)
{
const ViewProps *oldViewProps = static_cast<const ViewProps *>(oldPropsShared.get());
const ViewProps *newViewProps = static_cast<const ViewProps *>(newPropsShared.get());
ViewProps *interpolatedProps = const_cast<ViewProps *>(static_cast<const ViewProps *>(interpolatedPropsShared.get()));
interpolatedProps->opacity =
oldViewProps->opacity + (newViewProps->opacity - oldViewProps->opacity) * animationProgress;
interpolatedProps->transform =
Transform::Interpolate(animationProgress, oldViewProps->transform, newViewProps->transform, size);
// Android uses RawProps, not props, to update props on the platform...
// Since interpolated props don't interpolate at all using RawProps, we need
// to "re-hydrate" raw props after interpolating. This is what actually gets
// sent to the mounting layer. This is a temporary hack, only for platforms
// that use RawProps/folly::dynamic instead of concrete props on the
// mounting layer. Once we can remove this, we should change `rawProps` to
// be const again.
#ifdef ANDROID
if (!interpolatedProps->rawProps.isNull()) {
interpolatedProps->rawProps["opacity"] = interpolatedProps->opacity;
interpolatedProps->rawProps["transform"] = (folly::dynamic)interpolatedProps->transform;
}
#endif
}
} // namespace facebook::react

View File

@@ -0,0 +1,95 @@
/*
* 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 "ViewShadowNode.h"
#include <react/renderer/components/view/HostPlatformViewTraitsInitializer.h>
#include <react/renderer/components/view/primitives.h>
namespace facebook::react {
// NOLINTNEXTLINE(facebook-hte-CArray,modernize-avoid-c-arrays)
const char ViewComponentName[] = "View";
ViewShadowNodeProps::ViewShadowNodeProps(
const PropsParserContext& context,
const ViewShadowNodeProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps) {};
ViewShadowNode::ViewShadowNode(
const ShadowNodeFragment& fragment,
const ShadowNodeFamily::Shared& family,
ShadowNodeTraits traits)
: ConcreteViewShadowNode(fragment, family, traits) {
initialize();
}
ViewShadowNode::ViewShadowNode(
const ShadowNode& sourceShadowNode,
const ShadowNodeFragment& fragment)
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
initialize();
}
void ViewShadowNode::initialize() noexcept {
auto& viewProps = static_cast<const ViewProps&>(*props_);
auto hasBorder = [&]() {
for (auto edge : yoga::ordinals<yoga::Edge>()) {
if (viewProps.yogaStyle.border(edge).isDefined()) {
return true;
}
}
return false;
};
bool formsStackingContext = !viewProps.collapsable ||
viewProps.pointerEvents == PointerEventsMode::None ||
!viewProps.nativeId.empty() || viewProps.accessible ||
viewProps.opacity != 1.0 || viewProps.transform != Transform{} ||
(viewProps.zIndex.has_value() &&
viewProps.yogaStyle.positionType() != yoga::PositionType::Static) ||
viewProps.yogaStyle.display() == yoga::Display::None ||
viewProps.getClipsContentToBounds() || viewProps.events.bits.any() ||
isColorMeaningful(viewProps.shadowColor) ||
viewProps.accessibilityElementsHidden ||
viewProps.accessibilityViewIsModal ||
viewProps.importantForAccessibility != ImportantForAccessibility::Auto ||
viewProps.removeClippedSubviews || viewProps.cursor != Cursor::Auto ||
!viewProps.filter.empty() ||
viewProps.mixBlendMode != BlendMode::Normal ||
viewProps.isolation == Isolation::Isolate ||
HostPlatformViewTraitsInitializer::formsStackingContext(viewProps) ||
!viewProps.accessibilityOrder.empty();
bool formsView = formsStackingContext ||
isColorMeaningful(viewProps.backgroundColor) || hasBorder() ||
!viewProps.testId.empty() || !viewProps.boxShadow.empty() ||
!viewProps.backgroundImage.empty() ||
HostPlatformViewTraitsInitializer::formsView(viewProps) ||
viewProps.outlineWidth > 0;
if (formsView) {
traits_.set(ShadowNodeTraits::Trait::FormsView);
} else {
traits_.unset(ShadowNodeTraits::Trait::FormsView);
}
if (formsStackingContext) {
traits_.set(ShadowNodeTraits::Trait::FormsStackingContext);
} else {
traits_.unset(ShadowNodeTraits::Trait::FormsStackingContext);
}
if (!viewProps.collapsableChildren) {
traits_.set(ShadowNodeTraits::Trait::ChildrenFormStackingContext);
} else {
traits_.unset(ShadowNodeTraits::Trait::ChildrenFormStackingContext);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* 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/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
namespace facebook::react {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char ViewComponentName[];
/**
* Implementation of the ViewProps that propagates feature flag.
*/
class ViewShadowNodeProps final : public ViewProps {
public:
ViewShadowNodeProps() = default;
ViewShadowNodeProps(
const PropsParserContext &context,
const ViewShadowNodeProps &sourceProps,
const RawProps &rawProps);
};
/*
* `ShadowNode` for <View> component.
*/
class ViewShadowNode final : public ConcreteViewShadowNode<ViewComponentName, ViewShadowNodeProps, ViewEventEmitter> {
public:
ViewShadowNode(const ShadowNodeFragment &fragment, const ShadowNodeFamily::Shared &family, ShadowNodeTraits traits);
ViewShadowNode(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment);
private:
void initialize() noexcept;
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
/*
* 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 <memory>
#include <vector>
#include <yoga/node/Node.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/view/YogaStylableProps.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/Sealable.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class YogaLayoutableShadowNode : public LayoutableShadowNode {
public:
using Shared = std::shared_ptr<const YogaLayoutableShadowNode>;
using ListOfShared = std::vector<Shared>;
#pragma mark - Constructors
YogaLayoutableShadowNode(
const ShadowNodeFragment &fragment,
const ShadowNodeFamily::Shared &family,
ShadowNodeTraits traits);
YogaLayoutableShadowNode(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment);
void completeClone(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment) override;
#pragma mark - Mutating Methods
/*
* Connects `measureFunc` function of Yoga node with
* `LayoutableShadowNode::measure()` method.
*/
void enableMeasurement();
void appendChild(const std::shared_ptr<const ShadowNode> &child) override;
void replaceChild(
const ShadowNode &oldChild,
const std::shared_ptr<const ShadowNode> &newChild,
size_t suggestedIndex = SIZE_MAX) override;
void updateYogaChildren();
void updateYogaProps();
/*
* Sets layoutable size of node.
*/
void setSize(Size size) const;
void setPadding(RectangleEdges<Float> padding) const;
/*
* Sets position type of Yoga node (relative, absolute).
*/
void setPositionType(YGPositionType positionType) const;
#pragma mark - LayoutableShadowNode
void dirtyLayout() override;
bool getIsLayoutClean() const override;
/*
* Computes layout using Yoga layout engine.
* See `LayoutableShadowNode` for more details.
*/
void layoutTree(LayoutContext layoutContext, LayoutConstraints layoutConstraints) override;
void layout(LayoutContext layoutContext) override;
Rect getContentBounds() const;
protected:
/**
* Subclasses which provide MeasurableYogaNode may override to signal that a
* new ShadowNode revision does not need to invalidate existing measurements.
*/
virtual bool shouldNewRevisionDirtyMeasurement(const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
const;
/*
* Yoga config associated (only) with this particular node.
*/
yoga::Config yogaConfig_;
/*
* All Yoga functions only accept non-const arguments, so we have to mark
* Yoga node as `mutable` here to avoid `static_cast`ing the pointer to this
* all the time.
*/
mutable yoga::Node yogaNode_;
private:
/*
* Goes over `yogaNode_.getChildren()` and in case child's owner is
* equal to address of `yogaNode_`, it sets child's owner address
* to `0xBADC0FFEE0DDF00D`. This is magic constant, the intention
* is to make debugging easier when the address pops up in debugger.
* This prevents ABA problem where child yoga node goes from owned -> unowned
* -> back to owned because its parent is allocated at the same address.
*/
void updateYogaChildrenOwnersIfNeeded();
/*
* Return true if child's yogaNode's owner is this->yogaNode_. Otherwise
* returns false.
*/
bool doesOwn(const YogaLayoutableShadowNode &child) const;
/*
* Appends a Yoga node to the Yoga node associated with this node.
* The method does *not* do anything besides that (no cloning or `owner` field
* adjustment).
*/
void appendYogaChild(const YogaLayoutableShadowNode::Shared &childNode);
/*
* Makes the child node with a given `index` (and Yoga node associated with) a
* valid child node satisfied requirements of the Concurrent Layout approach.
*/
void adoptYogaChild(size_t index);
/**
* Applies contextual values to the ShadowNode's Yoga tree after the
* ShadowTree has been constructed, but before it has been is laid out or
* committed.
*/
void configureYogaTree(float pointScaleFactor, YGErrata defaultErrata, bool swapLeftAndRight);
/**
* Return an errata based on a `layoutConformance` prop if given, otherwise
* the passed default
*/
YGErrata resolveErrata(YGErrata defaultErrata) const;
/**
* Replcaes a child with a mutable clone of itself, returning the clone.
*/
YogaLayoutableShadowNode &cloneChildInPlace(size_t layoutableChildIndex);
static yoga::Config &initializeYogaConfig(yoga::Config &config, YGConfigConstRef previousConfig = nullptr);
static YGNodeRef
yogaNodeCloneCallbackConnector(YGNodeConstRef oldYogaNode, YGNodeConstRef parentYogaNode, size_t childIndex);
static YGSize yogaNodeMeasureCallbackConnector(
YGNodeConstRef yogaNode,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode);
static float yogaNodeBaselineCallbackConnector(YGNodeConstRef yogaNode, float width, float height);
static YogaLayoutableShadowNode &shadowNodeFromContext(YGNodeConstRef yogaNode);
#pragma mark - RTL Legacy Autoflip
/*
* Reassigns the following values:
* - (left|right) → (start|end)
* - margin(Left|Right) → margin(Start|End)
* - padding(Left|Right) → padding(Start|End)
* - borderTop(Left|Right)Radius → borderTop(Start|End)Radius
* - borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
* - border(Left|Right)Width → border(Start|End)Width
* - border(Left|Right)Color → border(Start|End)Color
* This is neccesarry to be backwards compatible with old renderer, it swaps
* the values as well in https://fburl.com/diffusion/kl7bjr3h
*/
void swapStyleLeftAndRight();
/*
* In shadow node passed as argument, reassigns following values
* - borderTop(Left|Right)Radius → borderTop(Start|End)Radius
* - borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
* - border(Left|Right)Width → border(Start|End)Width
* - border(Left|Right)Color → border(Start|End)Color
*/
void swapLeftAndRightInViewProps();
/*
* In yoga node passed as argument, reassigns following values
* - (left|right) → (start|end)
* - margin(Left|Right) → margin(Start|End)
* - padding(Left|Right) → padding(Start|End)
*/
void swapLeftAndRightInYogaStyleProps();
/*
* Combine a base yoga::Style with aliased properties which should be
* flattened into it. E.g. reconciling "marginInlineStart" and "marginStart".
*/
static yoga::Style applyAliasedProps(const yoga::Style &baseStyle, const YogaStylableProps &props);
#pragma mark - Consistency Ensuring Helpers
void ensureConsistency() const;
void ensureYogaChildrenAlignment() const;
void ensureYogaChildrenLookFine() const;
#pragma mark - Private member variables
/*
* List of children which derive from YogaLayoutableShadowNode
*/
ListOfShared yogaLayoutableChildren_;
/*
* Whether the full Yoga subtree of this Node has been configured.
*/
bool yogaTreeHasBeenConfigured_{false};
};
} // namespace facebook::react

View File

@@ -0,0 +1,520 @@
/*
* 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 "YogaStylableProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/components/view/propsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <yoga/Yoga.h>
#include <yoga/style/StyleLength.h>
#include "conversions.h"
namespace facebook::react {
YogaStylableProps::YogaStylableProps(
const PropsParserContext& context,
const YogaStylableProps& sourceProps,
const RawProps& rawProps,
const std::function<bool(const std::string&)>& filterObjectKeys)
: Props() {
initialize(context, sourceProps, rawProps, filterObjectKeys);
yogaStyle = ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
? sourceProps.yogaStyle
: convertRawProp(context, rawProps, sourceProps.yogaStyle);
if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) {
convertRawPropAliases(context, sourceProps, rawProps);
}
};
template <typename T>
static inline const T getFieldValue(
const PropsParserContext& context,
const RawValue& value,
const T& defaultValue) {
if (value.hasValue()) {
T res;
fromRawValue(context, value, res);
return res;
}
return defaultValue;
}
#define REBUILD_FIELD_SWITCH_CASE2(field, setter, fieldName) \
case CONSTEXPR_RAW_PROPS_KEY_HASH(fieldName): { \
yogaStyle.setter(getFieldValue(context, value, ygDefaults.field())); \
return; \
}
#define REBUILD_FIELD_SWITCH_CASE_YSP(field, setter) \
REBUILD_FIELD_SWITCH_CASE2(field, setter, #field)
#define REBUILD_YG_FIELD_SWITCH_CASE_INDEXED(field, setter, index, fieldName) \
case CONSTEXPR_RAW_PROPS_KEY_HASH(fieldName): { \
yogaStyle.setter( \
index, getFieldValue(context, value, ygDefaults.field(index))); \
return; \
}
#define REBUILD_FIELD_YG_DIMENSION(field, setter, widthStr, heightStr) \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Dimension::Width, widthStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Dimension::Height, heightStr);
#define REBUILD_FIELD_YG_GUTTER( \
field, setter, rowGapStr, columnGapStr, gapStr) \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Gutter::Row, rowGapStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Gutter::Column, columnGapStr); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Gutter::All, gapStr);
#define REBUILD_FIELD_YG_EDGES(field, setter, prefix, suffix) \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Left, prefix "Left" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Top, prefix "Top" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Right, prefix "Right" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Bottom, prefix "Bottom" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Start, prefix "Start" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::End, prefix "End" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Horizontal, prefix "Horizontal" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::Vertical, prefix "Vertical" suffix); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
field, setter, yoga::Edge::All, prefix "" suffix);
#define REBUILD_FIELD_YG_EDGES_POSITION() \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Left, "left"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Top, "top"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Right, "right"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Bottom, "bottom"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Start, "start"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::End, "end"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Horizontal, "insetInline"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::Vertical, "insetBlock"); \
REBUILD_YG_FIELD_SWITCH_CASE_INDEXED( \
position, setPosition, yoga::Edge::All, "inset");
void YogaStylableProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
static const auto ygDefaults = yoga::Style{};
static const auto defaults = YogaStylableProps{};
Props::setProp(context, hash, propName, value);
switch (hash) {
REBUILD_FIELD_SWITCH_CASE_YSP(direction, setDirection);
REBUILD_FIELD_SWITCH_CASE_YSP(flexDirection, setFlexDirection);
REBUILD_FIELD_SWITCH_CASE_YSP(justifyContent, setJustifyContent);
REBUILD_FIELD_SWITCH_CASE_YSP(alignContent, setAlignContent);
REBUILD_FIELD_SWITCH_CASE_YSP(alignItems, setAlignItems);
REBUILD_FIELD_SWITCH_CASE_YSP(alignSelf, setAlignSelf);
REBUILD_FIELD_SWITCH_CASE_YSP(flexWrap, setFlexWrap);
REBUILD_FIELD_SWITCH_CASE_YSP(overflow, setOverflow);
REBUILD_FIELD_SWITCH_CASE_YSP(display, setDisplay);
REBUILD_FIELD_SWITCH_CASE_YSP(flex, setFlex);
REBUILD_FIELD_SWITCH_CASE_YSP(flexGrow, setFlexGrow);
REBUILD_FIELD_SWITCH_CASE_YSP(flexShrink, setFlexShrink);
REBUILD_FIELD_SWITCH_CASE_YSP(flexBasis, setFlexBasis);
REBUILD_FIELD_SWITCH_CASE2(positionType, setPositionType, "position");
REBUILD_FIELD_YG_GUTTER(gap, setGap, "rowGap", "columnGap", "gap");
REBUILD_FIELD_SWITCH_CASE_YSP(aspectRatio, setAspectRatio);
REBUILD_FIELD_SWITCH_CASE_YSP(boxSizing, setBoxSizing);
REBUILD_FIELD_YG_DIMENSION(dimension, setDimension, "width", "height");
REBUILD_FIELD_YG_DIMENSION(
minDimension, setMinDimension, "minWidth", "minHeight");
REBUILD_FIELD_YG_DIMENSION(
maxDimension, setMaxDimension, "maxWidth", "maxHeight");
REBUILD_FIELD_YG_EDGES_POSITION();
REBUILD_FIELD_YG_EDGES(margin, setMargin, "margin", "");
REBUILD_FIELD_YG_EDGES(padding, setPadding, "padding", "");
REBUILD_FIELD_YG_EDGES(border, setBorder, "border", "Width");
// Aliases
RAW_SET_PROP_SWITCH_CASE(insetBlockEnd, "insetBlockEnd");
RAW_SET_PROP_SWITCH_CASE(insetBlockStart, "insetBlockStart");
RAW_SET_PROP_SWITCH_CASE(insetInlineEnd, "insetInlineEnd");
RAW_SET_PROP_SWITCH_CASE(insetInlineStart, "insetInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInline, "marginInline");
RAW_SET_PROP_SWITCH_CASE(marginInlineStart, "marginInlineStart");
RAW_SET_PROP_SWITCH_CASE(marginInlineEnd, "marginInlineEnd");
RAW_SET_PROP_SWITCH_CASE(marginBlock, "marginBlock");
RAW_SET_PROP_SWITCH_CASE(marginBlockStart, "marginBlockStart");
RAW_SET_PROP_SWITCH_CASE(marginBlockEnd, "marginBlockEnd");
RAW_SET_PROP_SWITCH_CASE(paddingInline, "paddingInline");
RAW_SET_PROP_SWITCH_CASE(paddingInlineStart, "paddingInlineStart");
RAW_SET_PROP_SWITCH_CASE(paddingInlineEnd, "paddingInlineEnd");
RAW_SET_PROP_SWITCH_CASE(paddingBlock, "paddingBlock");
RAW_SET_PROP_SWITCH_CASE(paddingBlockStart, "paddingBlockStart");
RAW_SET_PROP_SWITCH_CASE(paddingBlockEnd, "paddingBlockEnd");
}
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList YogaStylableProps::getDebugProps() const {
const auto defaultYogaStyle = yoga::Style{};
return Props::getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem(
"direction", yogaStyle.direction(), defaultYogaStyle.direction()),
debugStringConvertibleItem(
"flexDirection",
yogaStyle.flexDirection(),
defaultYogaStyle.flexDirection()),
debugStringConvertibleItem(
"justifyContent",
yogaStyle.justifyContent(),
defaultYogaStyle.justifyContent()),
debugStringConvertibleItem(
"alignContent",
yogaStyle.alignContent(),
defaultYogaStyle.alignContent()),
debugStringConvertibleItem(
"alignItems",
yogaStyle.alignItems(),
defaultYogaStyle.alignItems()),
debugStringConvertibleItem(
"alignSelf", yogaStyle.alignSelf(), defaultYogaStyle.alignSelf()),
debugStringConvertibleItem(
"positionType",
yogaStyle.positionType(),
defaultYogaStyle.positionType()),
debugStringConvertibleItem(
"flexWrap", yogaStyle.flexWrap(), defaultYogaStyle.flexWrap()),
debugStringConvertibleItem(
"overflow", yogaStyle.overflow(), defaultYogaStyle.overflow()),
debugStringConvertibleItem(
"display", yogaStyle.display(), defaultYogaStyle.display()),
debugStringConvertibleItem(
"flex", yogaStyle.flex(), defaultYogaStyle.flex()),
debugStringConvertibleItem(
"flexGrow", yogaStyle.flexGrow(), defaultYogaStyle.flexGrow()),
debugStringConvertibleItem(
"rowGap",
yogaStyle.gap(yoga::Gutter::Row),
defaultYogaStyle.gap(yoga::Gutter::Row)),
debugStringConvertibleItem(
"columnGap",
yogaStyle.gap(yoga::Gutter::Column),
defaultYogaStyle.gap(yoga::Gutter::Column)),
debugStringConvertibleItem(
"gap",
yogaStyle.gap(yoga::Gutter::All),
defaultYogaStyle.gap(yoga::Gutter::All)),
debugStringConvertibleItem(
"flexShrink",
yogaStyle.flexShrink(),
defaultYogaStyle.flexShrink()),
debugStringConvertibleItem(
"flexBasis", yogaStyle.flexBasis(), defaultYogaStyle.flexBasis()),
debugStringConvertibleItem(
"marginLeft",
yogaStyle.margin(yoga::Edge::Left),
defaultYogaStyle.margin(yoga::Edge::Left)),
debugStringConvertibleItem(
"marginTop",
yogaStyle.margin(yoga::Edge::Top),
defaultYogaStyle.margin(yoga::Edge::Top)),
debugStringConvertibleItem(
"marginRight",
yogaStyle.margin(yoga::Edge::Right),
defaultYogaStyle.margin(yoga::Edge::Right)),
debugStringConvertibleItem(
"marginBottom",
yogaStyle.margin(yoga::Edge::Bottom),
defaultYogaStyle.margin(yoga::Edge::Bottom)),
debugStringConvertibleItem(
"marginStart",
yogaStyle.margin(yoga::Edge::Start),
defaultYogaStyle.margin(yoga::Edge::Start)),
debugStringConvertibleItem(
"marginEnd",
yogaStyle.margin(yoga::Edge::End),
defaultYogaStyle.margin(yoga::Edge::End)),
debugStringConvertibleItem(
"marginHorizontal",
yogaStyle.margin(yoga::Edge::Horizontal),
defaultYogaStyle.margin(yoga::Edge::Horizontal)),
debugStringConvertibleItem(
"marginVertical",
yogaStyle.margin(yoga::Edge::Vertical),
defaultYogaStyle.margin(yoga::Edge::Vertical)),
debugStringConvertibleItem(
"margin",
yogaStyle.margin(yoga::Edge::All),
defaultYogaStyle.margin(yoga::Edge::All)),
debugStringConvertibleItem(
"left",
yogaStyle.position(yoga::Edge::Left),
defaultYogaStyle.position(yoga::Edge::Left)),
debugStringConvertibleItem(
"top",
yogaStyle.position(yoga::Edge::Top),
defaultYogaStyle.position(yoga::Edge::Top)),
debugStringConvertibleItem(
"right",
yogaStyle.position(yoga::Edge::Right),
defaultYogaStyle.position(yoga::Edge::Right)),
debugStringConvertibleItem(
"bottom",
yogaStyle.position(yoga::Edge::Bottom),
defaultYogaStyle.position(yoga::Edge::Bottom)),
debugStringConvertibleItem(
"start",
yogaStyle.position(yoga::Edge::Start),
defaultYogaStyle.position(yoga::Edge::Start)),
debugStringConvertibleItem(
"end",
yogaStyle.position(yoga::Edge::End),
defaultYogaStyle.position(yoga::Edge::End)),
debugStringConvertibleItem(
"inseInline",
yogaStyle.position(yoga::Edge::Horizontal),
defaultYogaStyle.position(yoga::Edge::Horizontal)),
debugStringConvertibleItem(
"insetBlock",
yogaStyle.position(yoga::Edge::Vertical),
defaultYogaStyle.position(yoga::Edge::Vertical)),
debugStringConvertibleItem(
"inset",
yogaStyle.position(yoga::Edge::All),
defaultYogaStyle.position(yoga::Edge::All)),
debugStringConvertibleItem(
"paddingLeft",
yogaStyle.padding(yoga::Edge::Left),
defaultYogaStyle.padding(yoga::Edge::Left)),
debugStringConvertibleItem(
"paddingTop",
yogaStyle.padding(yoga::Edge::Top),
defaultYogaStyle.padding(yoga::Edge::Top)),
debugStringConvertibleItem(
"paddingRight",
yogaStyle.padding(yoga::Edge::Right),
defaultYogaStyle.padding(yoga::Edge::Right)),
debugStringConvertibleItem(
"paddingBottom",
yogaStyle.padding(yoga::Edge::Bottom),
defaultYogaStyle.padding(yoga::Edge::Bottom)),
debugStringConvertibleItem(
"paddingStart",
yogaStyle.padding(yoga::Edge::Start),
defaultYogaStyle.padding(yoga::Edge::Start)),
debugStringConvertibleItem(
"paddingEnd",
yogaStyle.padding(yoga::Edge::End),
defaultYogaStyle.padding(yoga::Edge::End)),
debugStringConvertibleItem(
"paddingHorizontal",
yogaStyle.padding(yoga::Edge::Horizontal),
defaultYogaStyle.padding(yoga::Edge::Horizontal)),
debugStringConvertibleItem(
"paddingVertical",
yogaStyle.padding(yoga::Edge::Vertical),
defaultYogaStyle.padding(yoga::Edge::Vertical)),
debugStringConvertibleItem(
"padding",
yogaStyle.padding(yoga::Edge::All),
defaultYogaStyle.padding(yoga::Edge::All)),
debugStringConvertibleItem(
"borderLeftWidth",
yogaStyle.border(yoga::Edge::Left),
defaultYogaStyle.border(yoga::Edge::Left)),
debugStringConvertibleItem(
"borderTopWidth",
yogaStyle.border(yoga::Edge::Top),
defaultYogaStyle.border(yoga::Edge::Top)),
debugStringConvertibleItem(
"borderRightWidth",
yogaStyle.border(yoga::Edge::Right),
defaultYogaStyle.border(yoga::Edge::Right)),
debugStringConvertibleItem(
"borderBottomWidth",
yogaStyle.border(yoga::Edge::Bottom),
defaultYogaStyle.border(yoga::Edge::Bottom)),
debugStringConvertibleItem(
"borderStartWidth",
yogaStyle.border(yoga::Edge::Start),
defaultYogaStyle.border(yoga::Edge::Start)),
debugStringConvertibleItem(
"borderEndWidth",
yogaStyle.border(yoga::Edge::End),
defaultYogaStyle.border(yoga::Edge::End)),
debugStringConvertibleItem(
"borderHorizontalWidth",
yogaStyle.border(yoga::Edge::Horizontal),
defaultYogaStyle.border(yoga::Edge::Horizontal)),
debugStringConvertibleItem(
"borderVerticalWidth",
yogaStyle.border(yoga::Edge::Vertical),
defaultYogaStyle.border(yoga::Edge::Vertical)),
debugStringConvertibleItem(
"borderWidth",
yogaStyle.border(yoga::Edge::All),
defaultYogaStyle.border(yoga::Edge::All)),
debugStringConvertibleItem(
"width",
yogaStyle.dimension(yoga::Dimension::Width),
defaultYogaStyle.dimension(yoga::Dimension::Width)),
debugStringConvertibleItem(
"height",
yogaStyle.dimension(yoga::Dimension::Height),
defaultYogaStyle.dimension(yoga::Dimension::Height)),
debugStringConvertibleItem(
"minWidth",
yogaStyle.minDimension(yoga::Dimension::Width),
defaultYogaStyle.minDimension(yoga::Dimension::Width)),
debugStringConvertibleItem(
"minHeight",
yogaStyle.minDimension(yoga::Dimension::Height),
defaultYogaStyle.minDimension(yoga::Dimension::Height)),
debugStringConvertibleItem(
"maxWidth",
yogaStyle.maxDimension(yoga::Dimension::Width),
defaultYogaStyle.maxDimension(yoga::Dimension::Width)),
debugStringConvertibleItem(
"maxHeight",
yogaStyle.maxDimension(yoga::Dimension::Height),
defaultYogaStyle.maxDimension(yoga::Dimension::Height)),
debugStringConvertibleItem(
"aspectRatio",
yogaStyle.aspectRatio(),
defaultYogaStyle.aspectRatio()),
};
}
#endif
void YogaStylableProps::convertRawPropAliases(
const PropsParserContext& context,
const YogaStylableProps& sourceProps,
const RawProps& rawProps) {
insetBlockEnd = convertRawProp(
context,
rawProps,
"insetBlockEnd",
sourceProps.insetBlockEnd,
yoga::StyleLength::undefined());
insetBlockStart = convertRawProp(
context,
rawProps,
"insetBlockStart",
sourceProps.insetBlockStart,
yoga::StyleLength::undefined());
insetInlineEnd = convertRawProp(
context,
rawProps,
"insetInlineEnd",
sourceProps.insetInlineEnd,
yoga::StyleLength::undefined());
insetInlineStart = convertRawProp(
context,
rawProps,
"insetInlineStart",
sourceProps.insetInlineStart,
yoga::StyleLength::undefined());
marginInline = convertRawProp(
context,
rawProps,
"marginInline",
sourceProps.marginInline,
yoga::StyleLength::undefined());
marginInlineStart = convertRawProp(
context,
rawProps,
"marginInlineStart",
sourceProps.marginInlineStart,
yoga::StyleLength::undefined());
marginInlineEnd = convertRawProp(
context,
rawProps,
"marginInlineEnd",
sourceProps.marginInlineEnd,
yoga::StyleLength::undefined());
marginBlock = convertRawProp(
context,
rawProps,
"marginBlock",
sourceProps.marginBlock,
yoga::StyleLength::undefined());
marginBlockStart = convertRawProp(
context,
rawProps,
"marginBlockStart",
sourceProps.marginBlockStart,
yoga::StyleLength::undefined());
marginBlockEnd = convertRawProp(
context,
rawProps,
"marginBlockEnd",
sourceProps.marginBlockEnd,
yoga::StyleLength::undefined());
paddingInline = convertRawProp(
context,
rawProps,
"paddingInline",
sourceProps.paddingInline,
yoga::StyleLength::undefined());
paddingInlineStart = convertRawProp(
context,
rawProps,
"paddingInlineStart",
sourceProps.paddingInlineStart,
yoga::StyleLength::undefined());
paddingInlineEnd = convertRawProp(
context,
rawProps,
"paddingInlineEnd",
sourceProps.paddingInlineEnd,
yoga::StyleLength::undefined());
paddingBlock = convertRawProp(
context,
rawProps,
"paddingBlock",
sourceProps.paddingBlock,
yoga::StyleLength::undefined());
paddingBlockStart = convertRawProp(
context,
rawProps,
"paddingBlockStart",
sourceProps.paddingBlockStart,
yoga::StyleLength::undefined());
paddingBlockEnd = convertRawProp(
context,
rawProps,
"paddingBlockEnd",
sourceProps.paddingBlockEnd,
yoga::StyleLength::undefined());
}
} // namespace facebook::react

View File

@@ -0,0 +1,75 @@
/*
* 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 <yoga/style/Style.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/debug/DebugStringConvertible.h>
namespace facebook::react {
class YogaStylableProps : public Props {
public:
YogaStylableProps() = default;
YogaStylableProps(
const PropsParserContext &context,
const YogaStylableProps &sourceProps,
const RawProps &rawProps,
const std::function<bool(const std::string &)> &filterObjectKeys = nullptr);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
yoga::Style yogaStyle{};
// Duplicates of existing properties with different names, taking
// precedence. E.g. "marginBlock" instead of "marginVertical"
yoga::Style::Length insetInlineStart;
yoga::Style::Length insetInlineEnd;
yoga::Style::Length marginInline;
yoga::Style::Length marginInlineStart;
yoga::Style::Length marginInlineEnd;
yoga::Style::Length marginBlock;
yoga::Style::Length paddingInline;
yoga::Style::Length paddingInlineStart;
yoga::Style::Length paddingInlineEnd;
yoga::Style::Length paddingBlock;
// BlockEnd/BlockStart map to top/bottom (no writing mode), but we preserve
// Yoga's precedence and prefer specific edges (e.g. top) to ones which are
// flow relative (e.g. blockStart).
yoga::Style::Length insetBlockStart;
yoga::Style::Length insetBlockEnd;
yoga::Style::Length marginBlockStart;
yoga::Style::Length marginBlockEnd;
yoga::Style::Length paddingBlockStart;
yoga::Style::Length paddingBlockEnd;
#if RN_DEBUG_STRING_CONVERTIBLE
#pragma mark - DebugStringConvertible (Partial)
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
private:
void convertRawPropAliases(
const PropsParserContext &context,
const YogaStylableProps &sourceProps,
const RawProps &rawProps);
};
} // namespace facebook::react

View File

@@ -0,0 +1,828 @@
/*
* 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 <folly/dynamic.h>
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/debug/react_native_expect.h>
#include <react/renderer/components/view/AccessibilityPrimitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <unordered_map>
namespace facebook::react {
inline void fromString(const std::string &string, AccessibilityTraits &result)
{
if (string == "none") {
result = AccessibilityTraits::None;
return;
}
if (string == "button" || string == "togglebutton") {
result = AccessibilityTraits::Button;
return;
}
if (string == "link") {
result = AccessibilityTraits::Link;
return;
}
if (string == "image" || string == "img") {
result = AccessibilityTraits::Image;
return;
}
if (string == "selected") {
result = AccessibilityTraits::Selected;
return;
}
if (string == "plays") {
result = AccessibilityTraits::PlaysSound;
return;
}
if (string == "keyboardkey" || string == "key") {
result = AccessibilityTraits::KeyboardKey;
return;
}
if (string == "text") {
result = AccessibilityTraits::StaticText;
return;
}
if (string == "disabled") {
result = AccessibilityTraits::NotEnabled;
return;
}
if (string == "frequentUpdates") {
result = AccessibilityTraits::UpdatesFrequently;
return;
}
if (string == "search") {
result = AccessibilityTraits::SearchField;
return;
}
if (string == "startsMedia") {
result = AccessibilityTraits::StartsMediaSession;
return;
}
if (string == "adjustable") {
result = AccessibilityTraits::Adjustable;
return;
}
if (string == "allowsDirectInteraction") {
result = AccessibilityTraits::AllowsDirectInteraction;
return;
}
if (string == "pageTurn") {
result = AccessibilityTraits::CausesPageTurn;
return;
}
if (string == "header" || string == "heading") {
result = AccessibilityTraits::Header;
return;
}
if (string == "imagebutton") {
result = AccessibilityTraits::Image | AccessibilityTraits::Button;
return;
}
if (string == "summary") {
result = AccessibilityTraits::SummaryElement;
return;
}
if (string == "switch") {
result = AccessibilityTraits::Switch;
return;
}
if (string == "tabbar") {
result = AccessibilityTraits::TabBar;
return;
}
if (string == "progressbar") {
result = AccessibilityTraits::UpdatesFrequently;
return;
}
result = AccessibilityTraits::None;
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityTraits &result)
{
if (value.hasType<std::string>()) {
fromString((std::string)value, result);
return;
}
result = {};
react_native_expect(value.hasType<std::vector<std::string>>());
if (value.hasType<std::vector<std::string>>()) {
auto items = (std::vector<std::string>)value;
for (auto &item : items) {
AccessibilityTraits itemAccessibilityTraits;
fromString(item, itemAccessibilityTraits);
result = result | itemAccessibilityTraits;
}
} else {
LOG(ERROR) << "AccessibilityTraits parsing: unsupported type";
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityState &result)
{
auto map = (std::unordered_map<std::string, RawValue>)value;
auto selected = map.find("selected");
if (selected != map.end()) {
fromRawValue(context, selected->second, result.selected);
}
auto disabled = map.find("disabled");
if (disabled != map.end()) {
fromRawValue(context, disabled->second, result.disabled);
}
auto checked = map.find("checked");
if (checked != map.end()) {
if (checked->second.hasType<std::string>()) {
if ((std::string)checked->second == "mixed") {
result.checked = AccessibilityState::Mixed;
} else {
result.checked = AccessibilityState::None;
}
} else if (checked->second.hasType<bool>()) {
if ((bool)checked->second == true) {
result.checked = AccessibilityState::Checked;
} else {
result.checked = AccessibilityState::Unchecked;
}
} else {
result.checked = AccessibilityState::None;
}
}
auto busy = map.find("busy");
if (busy != map.end()) {
fromRawValue(context, busy->second, result.busy);
}
auto expanded = map.find("expanded");
if (expanded != map.end()) {
fromRawValue(context, expanded->second, result.expanded);
}
}
inline std::string toString(const ImportantForAccessibility &importantForAccessibility)
{
switch (importantForAccessibility) {
case ImportantForAccessibility::Auto:
return "auto";
case ImportantForAccessibility::Yes:
return "yes";
case ImportantForAccessibility::No:
return "no";
case ImportantForAccessibility::NoHideDescendants:
return "no-hide-descendants";
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, ImportantForAccessibility &result)
{
result = ImportantForAccessibility::Auto;
react_native_expect(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "auto") {
result = ImportantForAccessibility::Auto;
} else if (string == "yes") {
result = ImportantForAccessibility::Yes;
} else if (string == "no") {
result = ImportantForAccessibility::No;
} else if (string == "no-hide-descendants") {
result = ImportantForAccessibility::NoHideDescendants;
} else {
LOG(ERROR) << "Unsupported ImportantForAccessibility value: " << string;
react_native_expect(false);
}
} else {
LOG(ERROR) << "Unsupported ImportantForAccessibility type";
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityAction &result)
{
auto map = (std::unordered_map<std::string, RawValue>)value;
auto name = map.find("name");
react_native_assert(name != map.end() && name->second.hasType<std::string>());
if (name != map.end()) {
fromRawValue(context, name->second, result.name);
}
auto label = map.find("label");
if (label != map.end()) {
if (label->second.hasType<std::string>()) {
result.label = (std::string)label->second;
}
}
}
inline void fromRawValue(const PropsParserContext & /*unused*/, const RawValue &value, AccessibilityValue &result)
{
auto map = (std::unordered_map<std::string, RawValue>)value;
auto min = map.find("min");
if (min != map.end()) {
if (min->second.hasType<int>()) {
result.min = (int)min->second;
}
}
auto max = map.find("max");
if (max != map.end()) {
if (max->second.hasType<int>()) {
result.max = (int)max->second;
}
}
auto now = map.find("now");
if (now != map.end()) {
if (now->second.hasType<int>()) {
result.now = (int)now->second;
}
}
auto text = map.find("text");
if (text != map.end()) {
if (text->second.hasType<std::string>()) {
result.text = (std::string)text->second;
}
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityLabelledBy &result)
{
if (value.hasType<std::string>()) {
result.value.push_back((std::string)value);
} else if (value.hasType<std::vector<std::string>>()) {
result.value = (std::vector<std::string>)value;
}
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityLiveRegion &result)
{
result = AccessibilityLiveRegion::None;
react_native_expect(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "none") {
result = AccessibilityLiveRegion::None;
} else if (string == "polite") {
result = AccessibilityLiveRegion::Polite;
} else if (string == "assertive") {
result = AccessibilityLiveRegion::Assertive;
} else {
LOG(ERROR) << "Unsupported AccessibilityLiveRegion value: " << string;
react_native_expect(false);
}
} else {
LOG(ERROR) << "Unsupported AccessibilityLiveRegion type";
}
}
inline std::string toString(const AccessibilityRole &accessibilityRole)
{
switch (accessibilityRole) {
case AccessibilityRole::None:
return "none";
case AccessibilityRole::Button:
return "button";
case AccessibilityRole::Dropdownlist:
return "dropdownlist";
case AccessibilityRole::Togglebutton:
return "togglebutton";
case AccessibilityRole::Link:
return "link";
case AccessibilityRole::Search:
return "search";
case AccessibilityRole::Image:
return "image";
case AccessibilityRole::Keyboardkey:
return "keyboardkey";
case AccessibilityRole::Text:
return "text";
case AccessibilityRole::Adjustable:
return "adjustable";
case AccessibilityRole::Imagebutton:
return "imagebutton";
case AccessibilityRole::Header:
return "header";
case AccessibilityRole::Summary:
return "summary";
case AccessibilityRole::Alert:
return "alert";
case AccessibilityRole::Checkbox:
return "checkbox";
case AccessibilityRole::Combobox:
return "combobox";
case AccessibilityRole::Menu:
return "menu";
case AccessibilityRole::Menubar:
return "menubar";
case AccessibilityRole::Menuitem:
return "menuitem";
case AccessibilityRole::Progressbar:
return "progressbar";
case AccessibilityRole::Radio:
return "radio";
case AccessibilityRole::Radiogroup:
return "radiogroup";
case AccessibilityRole::Scrollbar:
return "scrollbar";
case AccessibilityRole::Spinbutton:
return "spinbutton";
case AccessibilityRole::Switch:
return "switch";
case AccessibilityRole::Tab:
return "tab";
case AccessibilityRole::Tabbar:
return "tabbar";
case AccessibilityRole::Tablist:
return "tablist";
case AccessibilityRole::Timer:
return "timer";
case AccessibilityRole::List:
return "timer";
case AccessibilityRole::Toolbar:
return "toolbar";
case AccessibilityRole::Grid:
return "grid";
case AccessibilityRole::Pager:
return "pager";
case AccessibilityRole::Scrollview:
return "scrollview";
case AccessibilityRole::Horizontalscrollview:
return "horizontalscrollview";
case AccessibilityRole::Viewgroup:
return "viewgroup";
case AccessibilityRole::Webview:
return "webview";
case AccessibilityRole::Drawerlayout:
return "drawerlayout";
case AccessibilityRole::Slidingdrawer:
return "slidingdrawer";
case AccessibilityRole::Iconmenu:
return "iconmenu";
}
LOG(ERROR) << "Unsupported AccessibilityRole value";
react_native_expect(false);
// sane default for prod
return "none";
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AccessibilityRole &result)
{
react_native_expect(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "none") {
result = AccessibilityRole::None;
} else if (string == "button") {
result = AccessibilityRole::Button;
} else if (string == "dropdownlist") {
result = AccessibilityRole::Dropdownlist;
} else if (string == "togglebutton") {
result = AccessibilityRole::Togglebutton;
} else if (string == "link") {
result = AccessibilityRole::Link;
} else if (string == "search") {
result = AccessibilityRole::Search;
} else if (string == "image") {
result = AccessibilityRole::Image;
} else if (string == "keyboardkey") {
result = AccessibilityRole::Keyboardkey;
} else if (string == "text") {
result = AccessibilityRole::Text;
} else if (string == "adjustable") {
result = AccessibilityRole::Adjustable;
} else if (string == "imagebutton") {
result = AccessibilityRole::Imagebutton;
} else if (string == "header") {
result = AccessibilityRole::Header;
} else if (string == "summary") {
result = AccessibilityRole::Summary;
} else if (string == "alert") {
result = AccessibilityRole::Alert;
} else if (string == "checkbox") {
result = AccessibilityRole::Checkbox;
} else if (string == "combobox") {
result = AccessibilityRole::Combobox;
} else if (string == "menu") {
result = AccessibilityRole::Menu;
} else if (string == "menubar") {
result = AccessibilityRole::Menubar;
} else if (string == "menuitem") {
result = AccessibilityRole::Menuitem;
} else if (string == "progressbar") {
result = AccessibilityRole::Progressbar;
} else if (string == "radio") {
result = AccessibilityRole::Radio;
} else if (string == "radiogroup") {
result = AccessibilityRole::Radiogroup;
} else if (string == "scrollbar") {
result = AccessibilityRole::Scrollbar;
} else if (string == "spinbutton") {
result = AccessibilityRole::Spinbutton;
} else if (string == "switch") {
result = AccessibilityRole::Switch;
} else if (string == "tab") {
result = AccessibilityRole::Tab;
} else if (string == "tabbar") {
result = AccessibilityRole::Tabbar;
} else if (string == "tablist") {
result = AccessibilityRole::Tablist;
} else if (string == "timer") {
result = AccessibilityRole::Timer;
} else if (string == "toolbar") {
result = AccessibilityRole::Toolbar;
} else if (string == "grid") {
result = AccessibilityRole::Grid;
} else if (string == "pager") {
result = AccessibilityRole::Pager;
} else if (string == "scrollview") {
result = AccessibilityRole::Scrollview;
} else if (string == "horizontalscrollview") {
result = AccessibilityRole::Horizontalscrollview;
} else if (string == "viewgroup") {
result = AccessibilityRole::Viewgroup;
} else if (string == "webview") {
result = AccessibilityRole::Webview;
} else if (string == "drawerlayout") {
result = AccessibilityRole::Drawerlayout;
} else if (string == "slidingdrawer") {
result = AccessibilityRole::Slidingdrawer;
} else if (string == "iconmenu") {
result = AccessibilityRole::Iconmenu;
} else {
LOG(ERROR) << "Unsupported AccessibilityRole value: " << string;
react_native_expect(false);
// sane default for prod
result = AccessibilityRole::None;
}
return;
}
LOG(ERROR) << "Unsupported AccessibilityRole type";
react_native_expect(false);
// sane default for prod
result = AccessibilityRole::None;
}
inline std::string toString(const Role &role)
{
switch (role) {
case Role::Alert:
return "alert";
case Role::Alertdialog:
return "alertdialog";
case Role::Application:
return "application";
case Role::Article:
return "article";
case Role::Banner:
return "banner";
case Role::Button:
return "button";
case Role::Cell:
return "cell";
case Role::Checkbox:
return "checkbox";
case Role::Columnheader:
return "columnheader";
case Role::Combobox:
return "combobox";
case Role::Complementary:
return "complementary";
case Role::Contentinfo:
return "contentinfo";
case Role::Definition:
return "definition";
case Role::Dialog:
return "dialog";
case Role::Directory:
return "directory";
case Role::Document:
return "document";
case Role::Feed:
return "feed";
case Role::Figure:
return "figure";
case Role::Form:
return "form";
case Role::Grid:
return "grid";
case Role::Group:
return "group";
case Role::Heading:
return "heading";
case Role::Img:
return "img";
case Role::Link:
return "link";
case Role::List:
return "list";
case Role::Listitem:
return "listitem";
case Role::Log:
return "log";
case Role::Main:
return "main";
case Role::Marquee:
return "marquee";
case Role::Math:
return "math";
case Role::Menu:
return "menu";
case Role::Menubar:
return "menubar";
case Role::Menuitem:
return "menuitem";
case Role::Meter:
return "meter";
case Role::Navigation:
return "navigation";
case Role::None:
return "none";
case Role::Note:
return "note";
case Role::Option:
return "option";
case Role::Presentation:
return "presentation";
case Role::Progressbar:
return "progressbar";
case Role::Radio:
return "radio";
case Role::Radiogroup:
return "radiogroup";
case Role::Region:
return "region";
case Role::Row:
return "row";
case Role::Rowgroup:
return "rowgroup";
case Role::Rowheader:
return "rowheader";
case Role::Scrollbar:
return "scrollbar";
case Role::Searchbox:
return "searchbox";
case Role::Separator:
return "separator";
case Role::Slider:
return "slider";
case Role::Spinbutton:
return "spinbutton";
case Role::Status:
return "status";
case Role::Summary:
return "summary";
case Role::Switch:
return "switch";
case Role::Tab:
return "tab";
case Role::Table:
return "table";
case Role::Tablist:
return "tablist";
case Role::Tabpanel:
return "tabpanel";
case Role::Term:
return "term";
case Role::Timer:
return "timer";
case Role::Toolbar:
return "toolbar";
case Role::Tooltip:
return "tooltip";
case Role::Tree:
return "tree";
case Role::Treegrid:
return "treegrid";
case Role::Treeitem:
return "treeitem";
}
LOG(ERROR) << "Unsupported Role value";
react_native_expect(false);
// sane default for prod
return "none";
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, Role &result)
{
react_native_expect(value.hasType<std::string>());
if (value.hasType<std::string>()) {
auto string = (std::string)value;
if (string == "alert") {
result = Role::Alert;
} else if (string == "alertdialog") {
result = Role::Alertdialog;
} else if (string == "application") {
result = Role::Application;
} else if (string == "article") {
result = Role::Article;
} else if (string == "banner") {
result = Role::Banner;
} else if (string == "button") {
result = Role::Button;
} else if (string == "cell") {
result = Role::Cell;
} else if (string == "checkbox") {
result = Role::Checkbox;
} else if (string == "columnheader") {
result = Role::Columnheader;
} else if (string == "combobox") {
result = Role::Combobox;
} else if (string == "complementary") {
result = Role::Complementary;
} else if (string == "contentinfo") {
result = Role::Contentinfo;
} else if (string == "definition") {
result = Role::Definition;
} else if (string == "dialog") {
result = Role::Dialog;
} else if (string == "directory") {
result = Role::Directory;
} else if (string == "document") {
result = Role::Document;
} else if (string == "feed") {
result = Role::Feed;
} else if (string == "figure") {
result = Role::Figure;
} else if (string == "form") {
result = Role::Form;
} else if (string == "grid") {
result = Role::Grid;
} else if (string == "group") {
result = Role::Group;
} else if (string == "heading") {
result = Role::Heading;
} else if (string == "img") {
result = Role::Img;
} else if (string == "link") {
result = Role::Link;
} else if (string == "list") {
result = Role::List;
} else if (string == "listitem") {
result = Role::Listitem;
} else if (string == "log") {
result = Role::Log;
} else if (string == "main") {
result = Role::Main;
} else if (string == "marquee") {
result = Role::Marquee;
} else if (string == "math") {
result = Role::Math;
} else if (string == "menu") {
result = Role::Menu;
} else if (string == "menubar") {
result = Role::Menubar;
} else if (string == "menuitem") {
result = Role::Menuitem;
} else if (string == "meter") {
result = Role::Meter;
} else if (string == "navigation") {
result = Role::Navigation;
} else if (string == "none") {
result = Role::None;
} else if (string == "note") {
result = Role::Note;
} else if (string == "option") {
result = Role::Option;
} else if (string == "presentation") {
result = Role::Presentation;
} else if (string == "progressbar") {
result = Role::Progressbar;
} else if (string == "radio") {
result = Role::Radio;
} else if (string == "radiogroup") {
result = Role::Radiogroup;
} else if (string == "region") {
result = Role::Region;
} else if (string == "row") {
result = Role::Row;
} else if (string == "rowgroup") {
result = Role::Rowgroup;
} else if (string == "rowheader") {
result = Role::Rowheader;
} else if (string == "scrollbar") {
result = Role::Scrollbar;
} else if (string == "searchbox") {
result = Role::Searchbox;
} else if (string == "separator") {
result = Role::Separator;
} else if (string == "slider") {
result = Role::Slider;
} else if (string == "spinbutton") {
result = Role::Spinbutton;
} else if (string == "status") {
result = Role::Status;
} else if (string == "summary") {
result = Role::Summary;
} else if (string == "switch") {
result = Role::Switch;
} else if (string == "tab") {
result = Role::Tab;
} else if (string == "table") {
result = Role::Table;
} else if (string == "tablist") {
result = Role::Tablist;
} else if (string == "tabpanel") {
result = Role::Tabpanel;
} else if (string == "term") {
result = Role::Term;
} else if (string == "timer") {
result = Role::Timer;
} else if (string == "toolbar") {
result = Role::Toolbar;
} else if (string == "tooltip") {
result = Role::Tooltip;
} else if (string == "tree") {
result = Role::Tree;
} else if (string == "treegrid") {
result = Role::Treegrid;
} else if (string == "treeitem") {
result = Role::Treeitem;
} else {
LOG(ERROR) << "Unsupported Role value: " << string;
react_native_expect(false);
// sane default for prod
result = Role::None;
}
return;
}
LOG(ERROR) << "Unsupported Role type";
react_native_expect(false);
// sane default for prod
result = Role::None;
}
inline std::string toString(AccessibilityLiveRegion accessibilityLiveRegion)
{
switch (accessibilityLiveRegion) {
case AccessibilityLiveRegion::None:
return "none";
case AccessibilityLiveRegion::Polite:
return "polite";
case AccessibilityLiveRegion::Assertive:
return "assertive";
}
}
#if RN_DEBUG_STRING_CONVERTIBLE
inline std::string toString(AccessibilityState::CheckedState state)
{
switch (state) {
case AccessibilityState::Unchecked:
return "Unchecked";
case AccessibilityState::Checked:
return "Checked";
case AccessibilityState::Mixed:
return "Mixed";
case AccessibilityState::None:
return "None";
}
}
inline std::string toString(const AccessibilityAction &accessibilityAction)
{
std::string result = accessibilityAction.name;
if (accessibilityAction.label.has_value()) {
result += ": '" + accessibilityAction.label.value() + "'";
}
return result;
}
inline std::string toString(std::vector<AccessibilityAction> accessibilityActions)
{
std::string result = "[";
for (size_t i = 0; i < accessibilityActions.size(); i++) {
result += toString(accessibilityActions[i]);
if (i < accessibilityActions.size() - 1) {
result += ", ";
}
}
result += "]";
return result;
}
inline std::string toString(const AccessibilityState &accessibilityState)
{
return "{disabled:" + toString(accessibilityState.disabled) + ",selected:" + toString(accessibilityState.selected) +
",checked:" + toString(accessibilityState.checked) + ",busy:" + toString(accessibilityState.busy) +
",expanded:" + toString(accessibilityState.expanded) + "}";
}
#endif
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

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.
*/
#pragma once
#include <react/renderer/components/view/BaseTouch.h>
namespace facebook::react {
using HostPlatformTouch = BaseTouch;
} // namespace facebook::react

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.
*/
#pragma once
#include <react/renderer/components/view/BaseViewEventEmitter.h>
namespace facebook::react {
using HostPlatformViewEventEmitter = BaseViewEventEmitter;
} // 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/components/view/BaseViewProps.h>
#include <react/renderer/components/view/NativeDrawable.h>
#include <react/renderer/components/view/primitives.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Transform.h>
#include <optional>
namespace facebook::react {
class HostPlatformViewProps : public BaseViewProps {
public:
HostPlatformViewProps() = default;
HostPlatformViewProps(
const PropsParserContext &context,
const HostPlatformViewProps &sourceProps,
const RawProps &rawProps,
const std::function<bool(const std::string &)> &filterObjectKeys = nullptr);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
#pragma mark - Props
Float elevation{};
std::optional<NativeDrawable> nativeBackground{};
std::optional<NativeDrawable> nativeForeground{};
bool focusable{false};
bool hasTVPreferredFocus{false};
bool needsOffscreenAlphaCompositing{false};
bool renderToHardwareTextureAndroid{false};
bool screenReaderFocusable{false};
std::optional<int> nextFocusDown{};
std::optional<int> nextFocusForward{};
std::optional<int> nextFocusLeft{};
std::optional<int> nextFocusRight{};
std::optional<int> nextFocusUp{};
#pragma mark - Convenience Methods
bool getProbablyMoreHorizontalThanVertical_DEPRECATED() const;
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
#ifdef RN_SERIALIZABLE_STATE
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props *prevProps) const override;
#endif
};
} // namespace facebook::react

View File

@@ -0,0 +1,32 @@
/*
* 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/components/view/ViewProps.h>
#include <react/renderer/core/ShadowNodeTraits.h>
namespace facebook::react::HostPlatformViewTraitsInitializer {
inline bool formsStackingContext(const ViewProps &viewProps)
{
return viewProps.elevation != 0;
}
inline bool formsView(const ViewProps &viewProps)
{
return viewProps.nativeBackground.has_value() || viewProps.nativeForeground.has_value() || viewProps.focusable ||
viewProps.hasTVPreferredFocus || viewProps.needsOffscreenAlphaCompositing ||
viewProps.renderToHardwareTextureAndroid || viewProps.screenReaderFocusable;
}
inline bool isKeyboardFocusable(const ViewProps &viewProps)
{
return (viewProps.focusable || viewProps.hasTVPreferredFocus);
}
} // namespace facebook::react::HostPlatformViewTraitsInitializer

View File

@@ -0,0 +1,101 @@
/*
* 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 <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Float.h>
#include <unordered_map>
namespace facebook::react {
struct NativeDrawable {
enum class Kind : uint8_t {
Ripple,
ThemeAttr,
};
struct Ripple {
std::optional<int32_t> color{};
std::optional<Float> rippleRadius{};
bool borderless{false};
bool operator==(const Ripple &rhs) const
{
return std::tie(this->color, this->borderless, this->rippleRadius) ==
std::tie(rhs.color, rhs.borderless, rhs.rippleRadius);
}
};
std::string themeAttr;
Ripple ripple;
Kind kind;
bool operator==(const NativeDrawable &rhs) const
{
if (this->kind != rhs.kind) {
return false;
}
switch (this->kind) {
case Kind::ThemeAttr:
return this->themeAttr == rhs.themeAttr;
case Kind::Ripple:
return this->ripple == rhs.ripple;
}
}
bool operator!=(const NativeDrawable &rhs) const
{
return !(*this == rhs);
}
~NativeDrawable() = default;
};
static inline void
fromRawValue(const PropsParserContext & /*context*/, const RawValue &rawValue, NativeDrawable &result)
{
auto map = (std::unordered_map<std::string, RawValue>)rawValue;
auto typeIterator = map.find("type");
react_native_expect(typeIterator != map.end() && typeIterator->second.hasType<std::string>());
std::string type = (std::string)typeIterator->second;
if (type == "ThemeAttrAndroid") {
auto attrIterator = map.find("attribute");
react_native_expect(attrIterator != map.end() && attrIterator->second.hasType<std::string>());
result = NativeDrawable{
(std::string)attrIterator->second,
{},
NativeDrawable::Kind::ThemeAttr,
};
} else if (type == "RippleAndroid") {
auto color = map.find("color");
auto borderless = map.find("borderless");
auto rippleRadius = map.find("rippleRadius");
result = NativeDrawable{
std::string{},
NativeDrawable::Ripple{
color != map.end() && color->second.hasType<int32_t>() ? (int32_t)color->second : std::optional<int32_t>{},
rippleRadius != map.end() && rippleRadius->second.hasType<Float>() ? (Float)rippleRadius->second
: std::optional<Float>{},
borderless != map.end() && borderless->second.hasType<bool>() ? (bool)borderless->second : false,
},
NativeDrawable::Kind::Ripple,
};
} else {
LOG(ERROR) << "Unknown native drawable type: " << type;
react_native_expect(false);
}
}
} // namespace facebook::react

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.
*/
#pragma once
#include <react/renderer/components/view/BaseTouch.h>
namespace facebook::react {
using HostPlatformTouch = BaseTouch;
} // namespace facebook::react

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.
*/
#pragma once
#include <react/renderer/components/view/BaseViewEventEmitter.h>
namespace facebook::react {
using HostPlatformViewEventEmitter = BaseViewEventEmitter;
} // namespace facebook::react

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.
*/
#pragma once
#include <react/renderer/components/view/BaseViewProps.h>
namespace facebook::react {
using HostPlatformViewProps = BaseViewProps;
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* 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/components/view/ViewProps.h>
#include <react/renderer/core/ShadowNodeTraits.h>
namespace facebook::react::HostPlatformViewTraitsInitializer {
inline bool formsStackingContext(const ViewProps &props)
{
return false;
}
inline bool formsView(const ViewProps &props)
{
return false;
}
inline bool isKeyboardFocusable(const ViewProps & /*props*/)
{
return false;
}
} // namespace facebook::react::HostPlatformViewTraitsInitializer

View File

@@ -0,0 +1,334 @@
/*
* 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/RectangleCorners.h>
#include <react/renderer/graphics/RectangleEdges.h>
#include <react/renderer/graphics/ValueUnit.h>
#include <array>
#include <bitset>
#include <cmath>
#include <optional>
namespace facebook::react {
enum class PointerEventsMode : uint8_t { Auto, None, BoxNone, BoxOnly };
struct ViewEvents {
std::bitset<64> bits{};
enum class Offset : std::size_t {
// Pointer events
PointerEnter = 0,
PointerMove = 1,
PointerLeave = 2,
// PanResponder callbacks
MoveShouldSetResponder = 3,
MoveShouldSetResponderCapture = 4,
StartShouldSetResponder = 5,
StartShouldSetResponderCapture = 6,
ResponderGrant = 7,
ResponderReject = 8,
ResponderStart = 9,
ResponderEnd = 10,
ResponderRelease = 11,
ResponderMove = 12,
ResponderTerminate = 13,
ResponderTerminationRequest = 14,
ShouldBlockNativeResponder = 15,
// Touch events
TouchStart = 16,
TouchMove = 17,
TouchEnd = 18,
TouchCancel = 19,
// W3C Pointer Events
PointerEnterCapture = 23,
PointerLeaveCapture = 24,
PointerMoveCapture = 25,
PointerOver = 26,
PointerOut = 27,
PointerOverCapture = 28,
PointerOutCapture = 29,
Click = 30,
ClickCapture = 31,
GotPointerCapture = 32,
LostPointerCapture = 33,
PointerDown = 34,
PointerDownCapture = 35,
PointerUp = 36,
PointerUpCapture = 37,
};
constexpr bool operator[](const Offset offset) const
{
return bits[static_cast<std::size_t>(offset)];
}
std::bitset<64>::reference operator[](const Offset offset)
{
return bits[static_cast<std::size_t>(offset)];
}
};
inline static bool operator==(const ViewEvents &lhs, const ViewEvents &rhs)
{
return lhs.bits == rhs.bits;
}
inline static bool operator!=(const ViewEvents &lhs, const ViewEvents &rhs)
{
return lhs.bits != rhs.bits;
}
enum class BackfaceVisibility : uint8_t { Auto, Visible, Hidden };
enum class BorderCurve : uint8_t { Circular, Continuous };
enum class BorderStyle : uint8_t { Solid, Dotted, Dashed };
enum class OutlineStyle : uint8_t { Solid, Dotted, Dashed };
struct CornerRadii {
float vertical{0.0f};
float horizontal{0.0f};
bool operator==(const CornerRadii &other) const = default;
};
enum class Cursor : uint8_t {
Auto,
Alias,
AllScroll,
Cell,
ColResize,
ContextMenu,
Copy,
Crosshair,
Default,
EResize,
EWResize,
Grab,
Grabbing,
Help,
Move,
NEResize,
NESWResize,
NResize,
NSResize,
NWResize,
NWSEResize,
NoDrop,
None,
NotAllowed,
Pointer,
Progress,
RowResize,
SResize,
SEResize,
SWResize,
Text,
Url,
WResize,
Wait,
ZoomIn,
ZoomOut,
};
enum class LayoutConformance : uint8_t { Strict, Compatibility };
template <typename T>
struct CascadedRectangleEdges {
using Counterpart = RectangleEdges<T>;
using OptionalT = std::optional<T>;
OptionalT left{};
OptionalT top{};
OptionalT right{};
OptionalT bottom{};
OptionalT start{};
OptionalT end{};
OptionalT horizontal{};
OptionalT vertical{};
OptionalT all{};
OptionalT block{};
OptionalT blockStart{};
OptionalT blockEnd{};
Counterpart resolve(bool isRTL, T defaults) const
{
const auto leadingEdge = isRTL ? end : start;
const auto trailingEdge = isRTL ? start : end;
const auto horizontalOrAllOrDefault = horizontal.value_or(all.value_or(defaults));
const auto verticalOrAllOrDefault = vertical.value_or(all.value_or(defaults));
return {
/* .left = */
left.value_or(leadingEdge.value_or(horizontalOrAllOrDefault)),
/* .top = */
blockStart.value_or(block.value_or(top.value_or(verticalOrAllOrDefault))),
/* .right = */
right.value_or(trailingEdge.value_or(horizontalOrAllOrDefault)),
/* .bottom = */
blockEnd.value_or(block.value_or(bottom.value_or(verticalOrAllOrDefault))),
};
}
bool operator==(const CascadedRectangleEdges<T> &rhs) const
{
return std::tie(
this->left,
this->top,
this->right,
this->bottom,
this->start,
this->end,
this->horizontal,
this->vertical,
this->all,
this->block,
this->blockStart,
this->blockEnd) ==
std::tie(
rhs.left,
rhs.top,
rhs.right,
rhs.bottom,
rhs.start,
rhs.end,
rhs.horizontal,
rhs.vertical,
rhs.all,
rhs.block,
rhs.blockStart,
rhs.blockEnd);
}
bool operator!=(const CascadedRectangleEdges<T> &rhs) const
{
return !(*this == rhs);
}
};
template <typename T>
struct CascadedRectangleCorners {
using Counterpart = RectangleCorners<T>;
using OptionalT = std::optional<T>;
OptionalT topLeft{};
OptionalT topRight{};
OptionalT bottomLeft{};
OptionalT bottomRight{};
OptionalT topStart{};
OptionalT topEnd{};
OptionalT bottomStart{};
OptionalT bottomEnd{};
OptionalT all{};
OptionalT endEnd{};
OptionalT endStart{};
OptionalT startEnd{};
OptionalT startStart{};
Counterpart resolve(bool isRTL, T defaults) const
{
const auto logicalTopStart = topStart ? topStart : startStart;
const auto logicalTopEnd = topEnd ? topEnd : startEnd;
const auto logicalBottomStart = bottomStart ? bottomStart : endStart;
const auto logicalBottomEnd = bottomEnd ? bottomEnd : endEnd;
const auto topLeading = isRTL ? logicalTopEnd : logicalTopStart;
const auto topTrailing = isRTL ? logicalTopStart : logicalTopEnd;
const auto bottomLeading = isRTL ? logicalBottomEnd : logicalBottomStart;
const auto bottomTrailing = isRTL ? logicalBottomStart : logicalBottomEnd;
return {
/* .topLeft = */ topLeft.value_or(topLeading.value_or(all.value_or(defaults))),
/* .topRight = */
topRight.value_or(topTrailing.value_or(all.value_or(defaults))),
/* .bottomLeft = */
bottomLeft.value_or(bottomLeading.value_or(all.value_or(defaults))),
/* .bottomRight = */
bottomRight.value_or(bottomTrailing.value_or(all.value_or(defaults))),
};
}
bool operator==(const CascadedRectangleCorners<T> &rhs) const
{
return std::tie(
this->topLeft,
this->topRight,
this->bottomLeft,
this->bottomRight,
this->topStart,
this->topEnd,
this->bottomStart,
this->bottomEnd,
this->all,
this->endEnd,
this->endStart,
this->startEnd,
this->startStart) ==
std::tie(
rhs.topLeft,
rhs.topRight,
rhs.bottomLeft,
rhs.bottomRight,
rhs.topStart,
rhs.topEnd,
rhs.bottomStart,
rhs.bottomEnd,
rhs.all,
rhs.endEnd,
rhs.endStart,
rhs.startEnd,
rhs.startStart);
}
bool operator!=(const CascadedRectangleCorners<T> &rhs) const
{
return !(*this == rhs);
}
};
using BorderWidths = RectangleEdges<Float>;
using BorderCurves = RectangleCorners<BorderCurve>;
using BorderStyles = RectangleEdges<BorderStyle>;
using BorderColors = RectangleEdges<SharedColor>;
using BorderRadii = RectangleCorners<CornerRadii>;
using CascadedBorderWidths = CascadedRectangleEdges<Float>;
using CascadedBorderCurves = CascadedRectangleCorners<BorderCurve>;
using CascadedBorderStyles = CascadedRectangleEdges<BorderStyle>;
using CascadedBorderColors = CascadedRectangleEdges<SharedColor>;
using CascadedBorderRadii = CascadedRectangleCorners<ValueUnit>;
struct BorderMetrics {
BorderColors borderColors{};
BorderWidths borderWidths{};
BorderRadii borderRadii{};
BorderCurves borderCurves{};
BorderStyles borderStyles{};
bool operator==(const BorderMetrics &rhs) const
{
return std::tie(
this->borderColors, this->borderWidths, this->borderRadii, this->borderCurves, this->borderStyles) ==
std::tie(rhs.borderColors, rhs.borderWidths, rhs.borderRadii, rhs.borderCurves, rhs.borderStyles);
}
bool operator!=(const BorderMetrics &rhs) const
{
return !(*this == rhs);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,593 @@
/*
* 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/components/view/conversions.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
// Nearly this entire file can be deleted when iterator-style Prop parsing
// ships fully for View
static inline yoga::Style
convertRawProp(const PropsParserContext &context, const RawProps &rawProps, const yoga::Style &sourceValue)
{
yoga::Style yogaStyle{};
yogaStyle.setDirection(
convertRawProp(context, rawProps, "direction", sourceValue.direction(), yogaStyle.direction()));
yogaStyle.setFlexDirection(
convertRawProp(context, rawProps, "flexDirection", sourceValue.flexDirection(), yogaStyle.flexDirection()));
yogaStyle.setJustifyContent(
convertRawProp(context, rawProps, "justifyContent", sourceValue.justifyContent(), yogaStyle.justifyContent()));
yogaStyle.setAlignContent(
convertRawProp(context, rawProps, "alignContent", sourceValue.alignContent(), yogaStyle.alignContent()));
yogaStyle.setAlignItems(
convertRawProp(context, rawProps, "alignItems", sourceValue.alignItems(), yogaStyle.alignItems()));
yogaStyle.setAlignSelf(
convertRawProp(context, rawProps, "alignSelf", sourceValue.alignSelf(), yogaStyle.alignSelf()));
yogaStyle.setPositionType(
convertRawProp(context, rawProps, "position", sourceValue.positionType(), yogaStyle.positionType()));
yogaStyle.setFlexWrap(convertRawProp(context, rawProps, "flexWrap", sourceValue.flexWrap(), yogaStyle.flexWrap()));
yogaStyle.setOverflow(convertRawProp(context, rawProps, "overflow", sourceValue.overflow(), yogaStyle.overflow()));
yogaStyle.setDisplay(convertRawProp(context, rawProps, "display", sourceValue.display(), yogaStyle.display()));
yogaStyle.setFlex(convertRawProp(context, rawProps, "flex", sourceValue.flex(), yogaStyle.flex()));
yogaStyle.setFlexGrow(convertRawProp(context, rawProps, "flexGrow", sourceValue.flexGrow(), yogaStyle.flexGrow()));
yogaStyle.setFlexShrink(
convertRawProp(context, rawProps, "flexShrink", sourceValue.flexShrink(), yogaStyle.flexShrink()));
yogaStyle.setFlexBasis(
convertRawProp(context, rawProps, "flexBasis", sourceValue.flexBasis(), yogaStyle.flexBasis()));
yogaStyle.setMargin(
yoga::Edge::Left,
convertRawProp(
context, rawProps, "marginLeft", sourceValue.margin(yoga::Edge::Left), yogaStyle.margin(yoga::Edge::Left)));
yogaStyle.setMargin(
yoga::Edge::Top,
convertRawProp(
context, rawProps, "marginTop", sourceValue.margin(yoga::Edge::Top), yogaStyle.margin(yoga::Edge::Top)));
yogaStyle.setMargin(
yoga::Edge::Right,
convertRawProp(
context,
rawProps,
"marginRight",
sourceValue.margin(yoga::Edge::Right),
yogaStyle.margin(yoga::Edge::Right)));
yogaStyle.setMargin(
yoga::Edge::Bottom,
convertRawProp(
context,
rawProps,
"marginBottom",
sourceValue.margin(yoga::Edge::Bottom),
yogaStyle.margin(yoga::Edge::Bottom)));
yogaStyle.setMargin(
yoga::Edge::Start,
convertRawProp(
context,
rawProps,
"marginStart",
sourceValue.margin(yoga::Edge::Start),
yogaStyle.margin(yoga::Edge::Start)));
yogaStyle.setMargin(
yoga::Edge::End,
convertRawProp(
context, rawProps, "marginEnd", sourceValue.margin(yoga::Edge::End), yogaStyle.margin(yoga::Edge::End)));
yogaStyle.setMargin(
yoga::Edge::Horizontal,
convertRawProp(
context,
rawProps,
"marginHorizontal",
sourceValue.margin(yoga::Edge::Horizontal),
yogaStyle.margin(yoga::Edge::Horizontal)));
yogaStyle.setMargin(
yoga::Edge::Vertical,
convertRawProp(
context,
rawProps,
"marginVertical",
sourceValue.margin(yoga::Edge::Vertical),
yogaStyle.margin(yoga::Edge::Vertical)));
yogaStyle.setMargin(
yoga::Edge::All,
convertRawProp(
context, rawProps, "margin", sourceValue.margin(yoga::Edge::All), yogaStyle.margin(yoga::Edge::All)));
yogaStyle.setPosition(
yoga::Edge::Left,
convertRawProp(
context, rawProps, "left", sourceValue.position(yoga::Edge::Left), yogaStyle.position(yoga::Edge::Left)));
yogaStyle.setPosition(
yoga::Edge::Top,
convertRawProp(
context, rawProps, "top", sourceValue.position(yoga::Edge::Top), yogaStyle.position(yoga::Edge::Top)));
yogaStyle.setPosition(
yoga::Edge::Right,
convertRawProp(
context, rawProps, "right", sourceValue.position(yoga::Edge::Right), yogaStyle.position(yoga::Edge::Right)));
yogaStyle.setPosition(
yoga::Edge::Bottom,
convertRawProp(
context,
rawProps,
"bottom",
sourceValue.position(yoga::Edge::Bottom),
yogaStyle.position(yoga::Edge::Bottom)));
yogaStyle.setPosition(
yoga::Edge::Start,
convertRawProp(
context, rawProps, "start", sourceValue.position(yoga::Edge::Start), yogaStyle.position(yoga::Edge::Start)));
yogaStyle.setPosition(
yoga::Edge::End,
convertRawProp(
context, rawProps, "end", sourceValue.position(yoga::Edge::End), yogaStyle.position(yoga::Edge::End)));
yogaStyle.setPosition(
yoga::Edge::Horizontal,
convertRawProp(
context,
rawProps,
"insetInline",
sourceValue.position(yoga::Edge::Horizontal),
yogaStyle.position(yoga::Edge::Horizontal)));
yogaStyle.setPosition(
yoga::Edge::Vertical,
convertRawProp(
context,
rawProps,
"insetBlock",
sourceValue.position(yoga::Edge::Vertical),
yogaStyle.position(yoga::Edge::Vertical)));
yogaStyle.setPosition(
yoga::Edge::All,
convertRawProp(
context, rawProps, "inset", sourceValue.position(yoga::Edge::All), yogaStyle.position(yoga::Edge::All)));
yogaStyle.setPadding(
yoga::Edge::Left,
convertRawProp(
context,
rawProps,
"paddingLeft",
sourceValue.padding(yoga::Edge::Left),
yogaStyle.padding(yoga::Edge::Left)));
yogaStyle.setPadding(
yoga::Edge::Top,
convertRawProp(
context, rawProps, "paddingTop", sourceValue.padding(yoga::Edge::Top), yogaStyle.padding(yoga::Edge::Top)));
yogaStyle.setPadding(
yoga::Edge::Right,
convertRawProp(
context,
rawProps,
"paddingRight",
sourceValue.padding(yoga::Edge::Right),
yogaStyle.padding(yoga::Edge::Right)));
yogaStyle.setPadding(
yoga::Edge::Bottom,
convertRawProp(
context,
rawProps,
"paddingBottom",
sourceValue.padding(yoga::Edge::Bottom),
yogaStyle.padding(yoga::Edge::Bottom)));
yogaStyle.setPadding(
yoga::Edge::Start,
convertRawProp(
context,
rawProps,
"paddingStart",
sourceValue.padding(yoga::Edge::Start),
yogaStyle.padding(yoga::Edge::Start)));
yogaStyle.setPadding(
yoga::Edge::End,
convertRawProp(
context, rawProps, "paddingEnd", sourceValue.padding(yoga::Edge::End), yogaStyle.padding(yoga::Edge::End)));
yogaStyle.setPadding(
yoga::Edge::Horizontal,
convertRawProp(
context,
rawProps,
"paddingHorizontal",
sourceValue.padding(yoga::Edge::Horizontal),
yogaStyle.padding(yoga::Edge::Horizontal)));
yogaStyle.setPadding(
yoga::Edge::Vertical,
convertRawProp(
context,
rawProps,
"paddingVertical",
sourceValue.padding(yoga::Edge::Vertical),
yogaStyle.padding(yoga::Edge::Vertical)));
yogaStyle.setPadding(
yoga::Edge::All,
convertRawProp(
context, rawProps, "padding", sourceValue.padding(yoga::Edge::All), yogaStyle.padding(yoga::Edge::All)));
yogaStyle.setGap(
yoga::Gutter::Row,
convertRawProp(
context, rawProps, "rowGap", sourceValue.gap(yoga::Gutter::Row), yogaStyle.gap(yoga::Gutter::Row)));
yogaStyle.setGap(
yoga::Gutter::Column,
convertRawProp(
context, rawProps, "columnGap", sourceValue.gap(yoga::Gutter::Column), yogaStyle.gap(yoga::Gutter::Column)));
yogaStyle.setGap(
yoga::Gutter::All,
convertRawProp(context, rawProps, "gap", sourceValue.gap(yoga::Gutter::All), yogaStyle.gap(yoga::Gutter::All)));
yogaStyle.setBorder(
yoga::Edge::Left,
convertRawProp(
context,
rawProps,
"borderLeftWidth",
sourceValue.border(yoga::Edge::Left),
yogaStyle.border(yoga::Edge::Left)));
yogaStyle.setBorder(
yoga::Edge::Top,
convertRawProp(
context, rawProps, "borderTopWidth", sourceValue.border(yoga::Edge::Top), yogaStyle.border(yoga::Edge::Top)));
yogaStyle.setBorder(
yoga::Edge::Right,
convertRawProp(
context,
rawProps,
"borderRightWidth",
sourceValue.border(yoga::Edge::Right),
yogaStyle.border(yoga::Edge::Right)));
yogaStyle.setBorder(
yoga::Edge::Bottom,
convertRawProp(
context,
rawProps,
"borderBottomWidth",
sourceValue.border(yoga::Edge::Bottom),
yogaStyle.border(yoga::Edge::Bottom)));
yogaStyle.setBorder(
yoga::Edge::Start,
convertRawProp(
context,
rawProps,
"borderStartWidth",
sourceValue.border(yoga::Edge::Start),
yogaStyle.border(yoga::Edge::Start)));
yogaStyle.setBorder(
yoga::Edge::End,
convertRawProp(
context, rawProps, "borderEndWidth", sourceValue.border(yoga::Edge::End), yogaStyle.border(yoga::Edge::End)));
yogaStyle.setBorder(
yoga::Edge::Horizontal,
convertRawProp(
context,
rawProps,
"borderHorizontalWidth",
sourceValue.border(yoga::Edge::Horizontal),
yogaStyle.border(yoga::Edge::Horizontal)));
yogaStyle.setBorder(
yoga::Edge::Vertical,
convertRawProp(
context,
rawProps,
"borderVerticalWidth",
sourceValue.border(yoga::Edge::Vertical),
yogaStyle.border(yoga::Edge::Vertical)));
yogaStyle.setBorder(
yoga::Edge::All,
convertRawProp(
context, rawProps, "borderWidth", sourceValue.border(yoga::Edge::All), yogaStyle.border(yoga::Edge::All)));
yogaStyle.setDimension(
yoga::Dimension::Width,
convertRawProp(context, rawProps, "width", sourceValue.dimension(yoga::Dimension::Width), {}));
yogaStyle.setDimension(
yoga::Dimension::Height,
convertRawProp(context, rawProps, "height", sourceValue.dimension(yoga::Dimension::Height), {}));
yogaStyle.setMinDimension(
yoga::Dimension::Width,
convertRawProp(context, rawProps, "minWidth", sourceValue.minDimension(yoga::Dimension::Width), {}));
yogaStyle.setMinDimension(
yoga::Dimension::Height,
convertRawProp(context, rawProps, "minHeight", sourceValue.minDimension(yoga::Dimension::Height), {}));
yogaStyle.setMaxDimension(
yoga::Dimension::Width,
convertRawProp(context, rawProps, "maxWidth", sourceValue.maxDimension(yoga::Dimension::Width), {}));
yogaStyle.setMaxDimension(
yoga::Dimension::Height,
convertRawProp(context, rawProps, "maxHeight", sourceValue.maxDimension(yoga::Dimension::Height), {}));
yogaStyle.setAspectRatio(
convertRawProp(context, rawProps, "aspectRatio", sourceValue.aspectRatio(), yogaStyle.aspectRatio()));
yogaStyle.setBoxSizing(
convertRawProp(context, rawProps, "boxSizing", sourceValue.boxSizing(), yogaStyle.boxSizing()));
return yogaStyle;
}
// This can be deleted when non-iterator ViewProp parsing is deleted
template <typename T>
static inline CascadedRectangleCorners<T> convertRawProp(
const PropsParserContext &context,
const RawProps &rawProps,
const char *prefix,
const char *suffix,
const CascadedRectangleCorners<T> &sourceValue,
const CascadedRectangleCorners<T> &defaultValue)
{
CascadedRectangleCorners<T> result;
result.topLeft =
convertRawProp(context, rawProps, "TopLeft", sourceValue.topLeft, defaultValue.topLeft, prefix, suffix);
result.topRight =
convertRawProp(context, rawProps, "TopRight", sourceValue.topRight, defaultValue.topRight, prefix, suffix);
result.bottomLeft =
convertRawProp(context, rawProps, "BottomLeft", sourceValue.bottomLeft, defaultValue.bottomLeft, prefix, suffix);
result.bottomRight = convertRawProp(
context, rawProps, "BottomRight", sourceValue.bottomRight, defaultValue.bottomRight, prefix, suffix);
result.topStart =
convertRawProp(context, rawProps, "TopStart", sourceValue.topStart, defaultValue.topStart, prefix, suffix);
result.topEnd = convertRawProp(context, rawProps, "TopEnd", sourceValue.topEnd, defaultValue.topEnd, prefix, suffix);
result.bottomStart = convertRawProp(
context, rawProps, "BottomStart", sourceValue.bottomStart, defaultValue.bottomStart, prefix, suffix);
result.bottomEnd =
convertRawProp(context, rawProps, "BottomEnd", sourceValue.bottomEnd, defaultValue.bottomEnd, prefix, suffix);
result.endEnd = convertRawProp(context, rawProps, "EndEnd", sourceValue.endEnd, defaultValue.endEnd, prefix, suffix);
result.endStart =
convertRawProp(context, rawProps, "EndStart", sourceValue.endStart, defaultValue.endStart, prefix, suffix);
result.startEnd =
convertRawProp(context, rawProps, "StartEnd", sourceValue.startEnd, defaultValue.startEnd, prefix, suffix);
result.startStart =
convertRawProp(context, rawProps, "StartStart", sourceValue.startStart, defaultValue.startStart, prefix, suffix);
result.all = convertRawProp(context, rawProps, "", sourceValue.all, defaultValue.all, prefix, suffix);
return result;
}
template <typename T>
static inline CascadedRectangleEdges<T> convertRawProp(
const PropsParserContext &context,
const RawProps &rawProps,
const char *prefix,
const char *suffix,
const CascadedRectangleEdges<T> &sourceValue,
const CascadedRectangleEdges<T> &defaultValue)
{
CascadedRectangleEdges<T> result;
result.left = convertRawProp(context, rawProps, "Left", sourceValue.left, defaultValue.left, prefix, suffix);
result.right = convertRawProp(context, rawProps, "Right", sourceValue.right, defaultValue.right, prefix, suffix);
result.top = convertRawProp(context, rawProps, "Top", sourceValue.top, defaultValue.top, prefix, suffix);
result.bottom = convertRawProp(context, rawProps, "Bottom", sourceValue.bottom, defaultValue.bottom, prefix, suffix);
result.start = convertRawProp(context, rawProps, "Start", sourceValue.start, defaultValue.start, prefix, suffix);
result.end = convertRawProp(context, rawProps, "End", sourceValue.end, defaultValue.end, prefix, suffix);
result.horizontal =
convertRawProp(context, rawProps, "Horizontal", sourceValue.horizontal, defaultValue.horizontal, prefix, suffix);
result.vertical =
convertRawProp(context, rawProps, "Vertical", sourceValue.vertical, defaultValue.vertical, prefix, suffix);
result.block = convertRawProp(context, rawProps, "Block", sourceValue.block, defaultValue.block, prefix, suffix);
result.blockEnd =
convertRawProp(context, rawProps, "BlockEnd", sourceValue.blockEnd, defaultValue.blockEnd, prefix, suffix);
result.blockStart =
convertRawProp(context, rawProps, "BlockStart", sourceValue.blockStart, defaultValue.blockStart, prefix, suffix);
result.all = convertRawProp(context, rawProps, "", sourceValue.all, defaultValue.all, prefix, suffix);
return result;
}
// This can be deleted when non-iterator ViewProp parsing is deleted
static inline ViewEvents convertRawProp(
const PropsParserContext &context,
const RawProps &rawProps,
const ViewEvents &sourceValue,
const ViewEvents &defaultValue)
{
ViewEvents result{};
using Offset = ViewEvents::Offset;
result[Offset::PointerEnter] = convertRawProp(
context, rawProps, "onPointerEnter", sourceValue[Offset::PointerEnter], defaultValue[Offset::PointerEnter]);
result[Offset::PointerMove] = convertRawProp(
context, rawProps, "onPointerMove", sourceValue[Offset::PointerMove], defaultValue[Offset::PointerMove]);
result[Offset::PointerLeave] = convertRawProp(
context, rawProps, "onPointerLeave", sourceValue[Offset::PointerLeave], defaultValue[Offset::PointerLeave]);
// Experimental W3C Pointer callbacks
result[Offset::PointerEnterCapture] = convertRawProp(
context,
rawProps,
"onPointerEnterCapture",
sourceValue[Offset::PointerEnterCapture],
defaultValue[Offset::PointerEnterCapture]);
result[Offset::PointerMoveCapture] = convertRawProp(
context,
rawProps,
"onPointerMoveCapture",
sourceValue[Offset::PointerMoveCapture],
defaultValue[Offset::PointerMoveCapture]);
result[Offset::PointerLeaveCapture] = convertRawProp(
context,
rawProps,
"onPointerLeaveCapture",
sourceValue[Offset::PointerLeaveCapture],
defaultValue[Offset::PointerLeaveCapture]);
result[Offset::PointerOver] = convertRawProp(
context, rawProps, "onPointerOver", sourceValue[Offset::PointerOver], defaultValue[Offset::PointerOver]);
result[Offset::PointerOverCapture] = convertRawProp(
context,
rawProps,
"onPointerOverCapture",
sourceValue[Offset::PointerOverCapture],
defaultValue[Offset::PointerOverCapture]);
result[Offset::PointerOut] = convertRawProp(
context, rawProps, "onPointerOut", sourceValue[Offset::PointerOut], defaultValue[Offset::PointerOut]);
result[Offset::PointerOutCapture] = convertRawProp(
context,
rawProps,
"onPointerOutCapture",
sourceValue[Offset::PointerOutCapture],
defaultValue[Offset::PointerOutCapture]);
result[Offset::Click] =
convertRawProp(context, rawProps, "onClick", sourceValue[Offset::Click], defaultValue[Offset::Click]);
result[Offset::ClickCapture] = convertRawProp(
context, rawProps, "onClickCapture", sourceValue[Offset::ClickCapture], defaultValue[Offset::ClickCapture]);
result[Offset::PointerDown] = convertRawProp(
context, rawProps, "onPointerDown", sourceValue[Offset::PointerDown], defaultValue[Offset::PointerDown]);
result[Offset::PointerDownCapture] = convertRawProp(
context,
rawProps,
"onPointerDownCapture",
sourceValue[Offset::PointerDownCapture],
defaultValue[Offset::PointerDownCapture]);
result[Offset::PointerUp] =
convertRawProp(context, rawProps, "onPointerUp", sourceValue[Offset::PointerUp], defaultValue[Offset::PointerUp]);
result[Offset::PointerUpCapture] = convertRawProp(
context,
rawProps,
"onPointerUpCapture",
sourceValue[Offset::PointerUpCapture],
defaultValue[Offset::PointerUpCapture]);
// TODO: gotPointerCapture & lostPointerCapture
// PanResponder callbacks
result[Offset::MoveShouldSetResponder] = convertRawProp(
context,
rawProps,
"onMoveShouldSetResponder",
sourceValue[Offset::MoveShouldSetResponder],
defaultValue[Offset::MoveShouldSetResponder]);
result[Offset::MoveShouldSetResponderCapture] = convertRawProp(
context,
rawProps,
"onMoveShouldSetResponderCapture",
sourceValue[Offset::MoveShouldSetResponderCapture],
defaultValue[Offset::MoveShouldSetResponderCapture]);
result[Offset::StartShouldSetResponder] = convertRawProp(
context,
rawProps,
"onStartShouldSetResponder",
sourceValue[Offset::StartShouldSetResponder],
defaultValue[Offset::StartShouldSetResponder]);
result[Offset::StartShouldSetResponderCapture] = convertRawProp(
context,
rawProps,
"onStartShouldSetResponderCapture",
sourceValue[Offset::StartShouldSetResponderCapture],
defaultValue[Offset::StartShouldSetResponderCapture]);
result[Offset::ResponderGrant] = convertRawProp(
context, rawProps, "onResponderGrant", sourceValue[Offset::ResponderGrant], defaultValue[Offset::ResponderGrant]);
result[Offset::ResponderReject] = convertRawProp(
context,
rawProps,
"onResponderReject",
sourceValue[Offset::ResponderReject],
defaultValue[Offset::ResponderReject]);
result[Offset::ResponderStart] = convertRawProp(
context, rawProps, "onResponderStart", sourceValue[Offset::ResponderStart], defaultValue[Offset::ResponderStart]);
result[Offset::ResponderEnd] = convertRawProp(
context, rawProps, "onResponderEnd", sourceValue[Offset::ResponderEnd], defaultValue[Offset::ResponderEnd]);
result[Offset::ResponderRelease] = convertRawProp(
context,
rawProps,
"onResponderRelease",
sourceValue[Offset::ResponderRelease],
defaultValue[Offset::ResponderRelease]);
result[Offset::ResponderMove] = convertRawProp(
context, rawProps, "onResponderMove", sourceValue[Offset::ResponderMove], defaultValue[Offset::ResponderMove]);
result[Offset::ResponderTerminate] = convertRawProp(
context,
rawProps,
"onResponderTerminate",
sourceValue[Offset::ResponderTerminate],
defaultValue[Offset::ResponderTerminate]);
result[Offset::ResponderTerminationRequest] = convertRawProp(
context,
rawProps,
"onResponderTerminationRequest",
sourceValue[Offset::ResponderTerminationRequest],
defaultValue[Offset::ResponderTerminationRequest]);
result[Offset::ShouldBlockNativeResponder] = convertRawProp(
context,
rawProps,
"onShouldBlockNativeResponder",
sourceValue[Offset::ShouldBlockNativeResponder],
defaultValue[Offset::ShouldBlockNativeResponder]);
// Touch events
result[Offset::TouchStart] = convertRawProp(
context, rawProps, "onTouchStart", sourceValue[Offset::TouchStart], defaultValue[Offset::TouchStart]);
result[Offset::TouchMove] =
convertRawProp(context, rawProps, "onTouchMove", sourceValue[Offset::TouchMove], defaultValue[Offset::TouchMove]);
result[Offset::TouchEnd] =
convertRawProp(context, rawProps, "onTouchEnd", sourceValue[Offset::TouchEnd], defaultValue[Offset::TouchEnd]);
result[Offset::TouchCancel] = convertRawProp(
context, rawProps, "onTouchCancel", sourceValue[Offset::TouchCancel], defaultValue[Offset::TouchCancel]);
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,242 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/components/view/BoxShadowPropsConversions.h>
#include <react/renderer/components/view/FilterPropsConversions.h>
namespace facebook::react {
TEST(ConversionsTest, unprocessed_box_shadow_string) {
RawValue value{
folly::dynamic("10px 2px 0 5px #fff, inset 20px 10px 5px 0 #000")};
std::vector<BoxShadow> boxShadows;
parseUnprocessedBoxShadow(
PropsParserContext{-1, ContextContainer{}}, value, boxShadows);
EXPECT_EQ(boxShadows.size(), 2);
EXPECT_EQ(boxShadows[0].offsetX, 10);
EXPECT_EQ(boxShadows[0].offsetY, 2);
EXPECT_EQ(boxShadows[0].blurRadius, 0);
EXPECT_EQ(boxShadows[0].spreadDistance, 5);
EXPECT_EQ(boxShadows[0].color, colorFromRGBA(255, 255, 255, 255));
EXPECT_FALSE(boxShadows[0].inset);
EXPECT_EQ(boxShadows[1].offsetX, 20);
EXPECT_EQ(boxShadows[1].offsetY, 10);
EXPECT_EQ(boxShadows[1].blurRadius, 5);
EXPECT_EQ(boxShadows[1].spreadDistance, 0);
EXPECT_EQ(boxShadows[1].color, colorFromRGBA(0, 0, 0, 255));
EXPECT_TRUE(boxShadows[1].inset);
}
TEST(ConversionsTest, unprocessed_box_shadow_objects) {
RawValue value{folly::dynamic::array(
folly::dynamic::object("offsetX", 10)("offsetY", 2)("blurRadius", 3)(
"spreadDistance", 5),
folly::dynamic::object("offsetX", 20)("offsetY", 10)("spreadDistance", 2)(
"color", "#fff")("inset", true))};
std::vector<BoxShadow> boxShadows;
parseUnprocessedBoxShadow(
PropsParserContext{-1, ContextContainer{}}, value, boxShadows);
EXPECT_EQ(boxShadows.size(), 2);
EXPECT_EQ(boxShadows[0].offsetX, 10);
EXPECT_EQ(boxShadows[0].offsetY, 2);
EXPECT_EQ(boxShadows[0].blurRadius, 3);
EXPECT_EQ(boxShadows[0].spreadDistance, 5);
EXPECT_EQ(boxShadows[0].color, SharedColor{});
EXPECT_FALSE(boxShadows[0].inset);
EXPECT_EQ(boxShadows[1].offsetX, 20);
EXPECT_EQ(boxShadows[1].offsetY, 10);
EXPECT_EQ(boxShadows[1].blurRadius, 0);
EXPECT_EQ(boxShadows[1].spreadDistance, 2);
EXPECT_EQ(boxShadows[1].color, colorFromRGBA(255, 255, 255, 255));
EXPECT_TRUE(boxShadows[1].inset);
}
TEST(ConversionsTest, unprocessed_box_object_invalid_color) {
RawValue value{folly::dynamic::array(
folly::dynamic::object("offsetX", 10)("offsetY", 2)("blurRadius", 3)(
"spreadDistance", 5)("color", "hello"))};
std::vector<BoxShadow> boxShadows;
parseUnprocessedBoxShadow(
PropsParserContext{-1, ContextContainer{}}, value, boxShadows);
EXPECT_TRUE(boxShadows.empty());
}
TEST(ConversionsTest, unprocessed_box_object_negative_blur) {
RawValue value{folly::dynamic::array(
folly::dynamic::object("offsetX", 10)("offsetY", 2)("blurRadius", -3)(
"spreadDistance", 5))};
std::vector<BoxShadow> boxShadows;
parseUnprocessedBoxShadow(
PropsParserContext{-1, ContextContainer{}}, value, boxShadows);
EXPECT_TRUE(boxShadows.empty());
}
TEST(ConversionsTest, unprocessed_filter_string) {
RawValue value{folly::dynamic(
"drop-shadow(10px -2px 0.5px #fff) blur(5px) hue-rotate(90deg) saturate(2) brightness(50%)")};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_EQ(filters.size(), 5);
EXPECT_EQ(filters[0].type, FilterType::DropShadow);
EXPECT_TRUE(std::holds_alternative<DropShadowParams>(filters[0].parameters));
EXPECT_EQ(std::get<DropShadowParams>(filters[0].parameters).offsetX, 10);
EXPECT_EQ(std::get<DropShadowParams>(filters[0].parameters).offsetY, -2);
EXPECT_EQ(
std::get<DropShadowParams>(filters[0].parameters).standardDeviation, 0.5);
EXPECT_EQ(
std::get<DropShadowParams>(filters[0].parameters).color,
colorFromRGBA(255, 255, 255, 255));
EXPECT_EQ(filters[1].type, FilterType::Blur);
EXPECT_TRUE(std::holds_alternative<Float>(filters[1].parameters));
EXPECT_EQ(std::get<Float>(filters[1].parameters), 5.0f);
EXPECT_EQ(filters[2].type, FilterType::HueRotate);
EXPECT_TRUE(std::holds_alternative<Float>(filters[2].parameters));
EXPECT_EQ(std::get<Float>(filters[2].parameters), 90.0f);
EXPECT_EQ(filters[3].type, FilterType::Saturate);
EXPECT_TRUE(std::holds_alternative<Float>(filters[3].parameters));
EXPECT_EQ(std::get<Float>(filters[3].parameters), 2.0f);
EXPECT_EQ(filters[4].type, FilterType::Brightness);
EXPECT_TRUE(std::holds_alternative<Float>(filters[4].parameters));
EXPECT_EQ(std::get<Float>(filters[4].parameters), 0.5f);
}
TEST(ConversionsTest, unprocessed_filter_objects) {
RawValue value{folly::dynamic::array(
folly::dynamic::object(
"drop-shadow",
folly::dynamic::object("offsetX", 10)("offsetY", "-2px")(
"standardDeviation", 0.5)),
folly::dynamic::object("drop-shadow", "2px 0 0.5px #fff"),
folly::dynamic::object("blur", 5),
folly::dynamic::object("hue-rotate", "90deg"),
folly::dynamic::object("saturate", 2),
folly::dynamic::object("brightness", "50%"))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_EQ(filters.size(), 6);
EXPECT_EQ(filters[0].type, FilterType::DropShadow);
EXPECT_TRUE(std::holds_alternative<DropShadowParams>(filters[0].parameters));
EXPECT_EQ(std::get<DropShadowParams>(filters[0].parameters).offsetX, 10);
EXPECT_EQ(std::get<DropShadowParams>(filters[0].parameters).offsetY, -2);
EXPECT_EQ(
std::get<DropShadowParams>(filters[0].parameters).standardDeviation, 0.5);
EXPECT_EQ(
std::get<DropShadowParams>(filters[0].parameters).color, SharedColor{});
EXPECT_EQ(filters[1].type, FilterType::DropShadow);
EXPECT_TRUE(std::holds_alternative<DropShadowParams>(filters[1].parameters));
EXPECT_EQ(std::get<DropShadowParams>(filters[1].parameters).offsetX, 2);
EXPECT_EQ(std::get<DropShadowParams>(filters[1].parameters).offsetY, 0);
EXPECT_EQ(
std::get<DropShadowParams>(filters[1].parameters).standardDeviation, 0.5);
EXPECT_EQ(
std::get<DropShadowParams>(filters[1].parameters).color,
colorFromRGBA(255, 255, 255, 255));
EXPECT_EQ(filters[2].type, FilterType::Blur);
EXPECT_TRUE(std::holds_alternative<Float>(filters[2].parameters));
EXPECT_EQ(std::get<Float>(filters[2].parameters), 5.0f);
EXPECT_EQ(filters[3].type, FilterType::HueRotate);
EXPECT_TRUE(std::holds_alternative<Float>(filters[3].parameters));
EXPECT_EQ(std::get<Float>(filters[3].parameters), 90.0f);
EXPECT_EQ(filters[4].type, FilterType::Saturate);
EXPECT_TRUE(std::holds_alternative<Float>(filters[4].parameters));
EXPECT_EQ(std::get<Float>(filters[4].parameters), 2.0f);
EXPECT_EQ(filters[5].type, FilterType::Brightness);
EXPECT_TRUE(std::holds_alternative<Float>(filters[5].parameters));
EXPECT_EQ(std::get<Float>(filters[5].parameters), 0.5f);
}
TEST(ConversionsTest, unprocessed_filter_objects_negative_shadow_blur) {
RawValue value{folly::dynamic::array(
folly::dynamic::object(
"drop-shadow",
folly::dynamic::object("offsetX", 10)("offsetY", "-2px")(
"standardDeviation", -0.5)))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_TRUE(filters.empty());
}
TEST(ConversionsTest, unprocessed_filter_objects_negative_blur) {
RawValue value{folly::dynamic::array(folly::dynamic::object("blur", -5))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_TRUE(filters.empty());
}
TEST(ConversionsTest, unprocessed_filter_objects_negative_contrast) {
RawValue value{
folly::dynamic::array(folly::dynamic::object("constrast", -5))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_TRUE(filters.empty());
}
TEST(ConversionsTest, unprocessed_filter_objects_negative_hue_rotate) {
RawValue value{
folly::dynamic::array(folly::dynamic::object("hue-rotate", -5))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_EQ(filters.size(), 1);
EXPECT_EQ(filters[0].type, FilterType::HueRotate);
EXPECT_TRUE(std::holds_alternative<Float>(filters[0].parameters));
EXPECT_EQ(std::get<Float>(filters[0].parameters), -5.0f);
}
TEST(ConversionsTest, unprocessed_filter_objects_multiple_objects) {
RawValue value{folly::dynamic::array(
folly::dynamic::object("blur", 5)("hue-rotate", "90deg"))};
std::vector<FilterFunction> filters;
parseUnprocessedFilter(
PropsParserContext{-1, ContextContainer{}}, value, filters);
EXPECT_TRUE(filters.empty());
}
} // namespace facebook::react

View File

@@ -0,0 +1,447 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
namespace facebook::react {
// Note: the (x, y) origin is always relative to the parent node. You may use
// P482342650 to re-create this test case in playground.
// *******************************************************┌─ABCD:────┐****
// *******************************************************│ {70,-50} │****
// *******************************************************│ {30,60} │****
// *******************************************************│ │****
// *******************************************************│ │****
// *******************┌─A: {0,0}{50,50}──┐****************│ │****
// *******************│ │****************│ │****
// *******************│ ┌─AB:──────┐ │****************│ │****
// *******************│ │ {10,10}{30,90}****************│ │****
// *******************│ │ ┌─ABC: {10,10}{110,20}──────┤ ├───┐
// *******************│ │ │ │ │ │
// *******************│ │ │ └──────────┘ │
// *******************│ │ └──────┬───┬───────────────────────────────┘
// *******************│ │ │ │********************************
// *******************└───┤ ├───┘********************************
// ***********************│ │************************************
// ***********************│ │************************************
// ┌─ABE: {-60,50}{70,20}─┴───┐ │************************************
// │ │ │************************************
// │ │ │************************************
// │ │ │************************************
// │ │ │************************************
// └──────────────────────┬───┘ │************************************
// ***********************│ │************************************
// ***********************└──────────┘************************************
enum TestCase {
AS_IS,
CLIPPING,
HIT_SLOP,
HIT_SLOP_TRANSFORM_TRANSLATE,
TRANSFORM_SCALE,
TRANSFORM_TRANSLATE,
};
class LayoutTest : public ::testing::Test {
protected:
ComponentBuilder builder_;
std::shared_ptr<RootShadowNode> rootShadowNode_;
std::shared_ptr<ViewShadowNode> viewShadowNodeA_;
std::shared_ptr<ViewShadowNode> viewShadowNodeAB_;
std::shared_ptr<ViewShadowNode> viewShadowNodeABC_;
std::shared_ptr<ViewShadowNode> viewShadowNodeABCD_;
std::shared_ptr<ViewShadowNode> viewShadowNodeABE_;
LayoutTest() : builder_(simpleComponentBuilder()) {}
void initialize(TestCase testCase) {
// clang-format off
auto element =
Element<RootShadowNode>()
.reference(rootShadowNode_)
.tag(1)
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
props.layoutConstraints = LayoutConstraints{.minimumSize={.width=0,.height=0}, .maximumSize={.width=500, .height=500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.reference(viewShadowNodeA_)
.tag(2)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(50));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(50));
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.reference(viewShadowNodeAB_)
.tag(3)
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(10));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(10));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(30));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(90));
if (testCase == TRANSFORM_SCALE) {
props.transform = props.transform * Transform::Scale(2, 2, 1);
}
if (testCase == TRANSFORM_TRANSLATE || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
props.transform = props.transform * Transform::Translate(10, 10, 0);
}
if (testCase == HIT_SLOP || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
props.hitSlop = EdgeInsets{50, 50, 50, 50};
}
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.reference(viewShadowNodeABC_)
.tag(4)
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
if (testCase == CLIPPING) {
yogaStyle.setOverflow(yoga::Overflow::Hidden);
}
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(10));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(10));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(110));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(20));
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.reference(viewShadowNodeABCD_)
.tag(5)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(70));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(-50));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(30));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(60));
return sharedProps;
})
}),
Element<ViewShadowNode>()
.reference(viewShadowNodeABE_)
.tag(6)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(-60));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(50));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(70));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(20));
return sharedProps;
})
})
})
});
// clang-format on
builder_.build(element);
rootShadowNode_->layoutIfNeeded();
}
};
// Test the layout as described above with no extra changes
TEST_F(LayoutTest, overflowInsetTest) {
initialize(AS_IS);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
EXPECT_EQ(layoutMetricsA.overflowInset.top, -30);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -80);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -50);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
// Test when box ABC has clipping (aka overflow hidden)
TEST_F(LayoutTest, overflowInsetWithClippingTest) {
initialize(CLIPPING);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
EXPECT_EQ(layoutMetricsA.overflowInset.top, 0);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -80);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -50);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
// Test when box AB translate (10, 10, 0) in transform. The parent node's
// overflowInset will be affected, but the transformed node and its child nodes
// are not affected. Here is an example:
//
// ┌────────────────┐ ┌────────────────┐
// │Original Layout │ │ Translate AB │
// └────────────────┘ └────────────────┘
// ─────▶
// ┌ ─ ─ ─ ┬──────────┐─ ─ ─ ─ ┐ ┌ ─ ─ ─ ┬──────────┐─ ─ ─ ─ ─ ┐
// │ A │ │ A │
// │ │ │ │ │ │ │ │
// ─ ─ ─ ─│─ ─ ─┌───┐┼ ─ ─ ─ ─ │ │
// │ │ │AB ││ │ │ ┌ ─ ─ ┼ ─ ─ ─ ┬──┴┬ ─ ─ ─ ─ ┤
// └─────┤ ├┘ └───────┤AB │
// │ │┌──┴─────────┤ │ │ │ │ │
// ││ABC │ │┌──┴─────────┐
// │ │└──┬─────────┤ │ │ │ ││ABC │
// ┌───ABD───────┴─┐ │ │ │└──┬─────────┘
// ├─────────────┬─┘ │ │ │ │ ├───ABD───────┴─┐ │ │
// ─ ─ ─ ─ ─ ─ ─└───┘─ ─ ─ ─ ─ ▼ └─────────────┬─┘ │
// └ ┴ ─ ─ ─ ─ ─ ─ ┴───┴ ─ ─ ─ ─ ┘
TEST_F(LayoutTest, overflowInsetTransformTranslateTest) {
initialize(TRANSFORM_TRANSLATE);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
// Change on parent node
// The top/left values are NOT changing as overflowInset is union of before
// and after transform layout. In this case, we move to the right and bottom,
// so the left and top is not changing, while right and bottom values are
// increased.
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
EXPECT_EQ(layoutMetricsA.overflowInset.top, -30);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -60);
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
// No change on self node with translate transform
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
// No change on child node
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
// Test when box AB scaled 2X in transform. The parent node's overflowInset will
// be affected. However, the transformed node and its child nodes only appears
// to be affected (dashed arrow). Since all transform is cosmetic only, the
// actual values are NOT changed. It will be converted later when mapping the
// values to pixels during rendering. Here is an example:
//
// ┌────────────────┐ ┌────────────────┐
// │Original Layout │ │ Scale AB │
// └────────────────┘ └────────────────┘
// ─────▶
// ┌ ─ ─ ─ ┬──────────┐─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ─ ┬──────────┐─ ─ ─ ─ ─ ┐
// │ A │ │ A │
// │ │ │ │ ├ ─ ─ ─ ─ ─ ┼ ─ ─┌─────┤─ ─ ─ ─ ─ ┤
// ─ ─ ─ ─│─ ─ ─┌───┐┼ ─ ─ ─ ─ │ │AB │ ─ ─ ─▶
// │ │ │AB ││ │ │ │ │ │ │
// └─────┤ ├┘ └────┤ │
// │ │┌──┴─────────┤ │ │ ┌───┴──────────┤
// ││ABC │ │ │ABC │
// │ │└──┬─────────┤ │ │ │ │ │
// ┌───ABD───────┴─┐ │ │ │ └───┬──────────┘
// ├─────────────┬─┘ │ │ │ ├────────────────┴──┐ │ │
// ─ ─ ─ ─ ─ ─ ─└───┘─ ─ ─ ─ ─ ▼ │ ABD │ │
// ├────────────────┬──┘ │ │
// ─ ─ ─ ─ ─ ─ ─ ─ ┴─────┴ ─ ─ ─ ─ ─
TEST_F(LayoutTest, overflowInsetTransformScaleTest) {
initialize(TRANSFORM_SCALE);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
// Change on parent node when a child view scale up
// Note that AB scale up from its center point. The numbers are calculated
// assuming AB's center point is not moving.
EXPECT_EQ(layoutMetricsA.overflowInset.left, -125);
EXPECT_EQ(layoutMetricsA.overflowInset.top, -115);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -185);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -95);
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
// The frame of box AB won't actually scale up. The transform matrix is
// purely cosmetic and should apply later in mounting phase.
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
// No change on self node with scale transform. This may sound a bit
// surprising, but the overflowInset values will be scaled up via pixel
// density ratio along with width/height of the view. When we do hit-testing,
// the overflowInset value will appears to be doubled as expected.
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
// The frame of box ABC won't actually scale up. The transform matrix is
// purely cosmatic and should apply later in mounting phase.
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
// The overflowInset of ABC won't change either. This may sound a bit
// surprising, but the overflowInset values will be scaled up via pixel
// density ratio along with width/height of the view. When we do hit-testing,
// the overflowInset value will appears to be doubled as expected.
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
TEST_F(LayoutTest, overflowInsetHitSlopTest) {
initialize(HIT_SLOP);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
// Change on parent node
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -80);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -100);
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
// No change on self node
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
// No change on child node
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
TEST_F(LayoutTest, overflowInsetHitSlopTransformTranslateTest) {
initialize(HIT_SLOP_TRANSFORM_TRANSLATE);
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
// Change on parent node
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsA.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -110);
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
// No change on self node
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
// No change on child node
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
}
} // namespace facebook::react

View File

@@ -0,0 +1,377 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/components/view/BaseViewProps.h>
namespace facebook::react {
namespace {
// For transforms involving rotations, use this helper to fix floating point
// accuracies
void expectTransformsEqual(const Transform& t1, const Transform& t2) {
for (int i = 0; i < 16; i++) {
EXPECT_NEAR(t1.matrix[i], t2.matrix[i], 0.0001);
}
}
} // namespace
class ResolveTransformTest : public ::testing::Test {
protected:
TransformOrigin createTransformOriginPoints(float x, float y, float z = 0) {
TransformOrigin origin;
origin.xy[0] = ValueUnit(x, UnitType::Point);
origin.xy[1] = ValueUnit(y, UnitType::Point);
origin.z = z;
return origin;
}
TransformOrigin createTransformOriginPercent(float x, float y, float z = 0) {
TransformOrigin origin;
origin.xy[0] = ValueUnit(x, UnitType::Percent);
origin.xy[1] = ValueUnit(y, UnitType::Percent);
origin.z = z;
return origin;
}
};
TEST_F(ResolveTransformTest, EmptyFrameNoTransformOrigin) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::Translate(10.0, 20.0, 0.0);
TransformOrigin transformOrigin; // Default (not set)
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// With empty frame size and no transform origin, should just apply the
// transform directly
EXPECT_EQ(result.matrix, transform.matrix);
}
TEST_F(ResolveTransformTest, EmptyFrameTransformOriginPoints) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::Translate(10.0, 20.0, 0.0);
TransformOrigin transformOrigin = createTransformOriginPoints(5, 8);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Should handle transform origin even with empty frame size
EXPECT_EQ(result.matrix, Transform::Translate(10.0, 20.0, 0.0).matrix);
}
TEST_F(ResolveTransformTest, EmptyFrameTransformOriginPercent) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::Translate(10.0, 20.0, 0.0);
TransformOrigin transformOrigin = createTransformOriginPercent(50, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Transform origin does not affect translate transform
EXPECT_EQ(result.matrix, Transform::Translate(10.0, 20.0, 0.0).matrix);
}
TEST_F(ResolveTransformTest, NonEmptyFrameNoTransformOrigin) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Translate(10.0, 20.0, 0.0);
TransformOrigin transformOrigin; // Default (not set)
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Transform origin does not affect translate transform
EXPECT_EQ(result.matrix, Transform::Translate(10.0, 20.0, 0.0).matrix);
}
TEST_F(ResolveTransformTest, NonEmptyFrameTransformOriginPoints) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Scale(2.0, 1.5, 0.);
TransformOrigin transformOrigin = createTransformOriginPoints(25, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
auto expected = Transform::Translate(25.0, 25.0, 0.0) * transform;
EXPECT_EQ(result.matrix, expected.matrix);
}
TEST_F(ResolveTransformTest, NonEmptyFrameTransformOriginPercent) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Scale(2.0, 1.5, 0.);
TransformOrigin transformOrigin =
createTransformOriginPercent(25, 75); // 25% width, 75% height
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Should resolve percentages: 25% of 100 = 25, 75% of 200 = 150
auto expected = Transform::Translate(25.0, -25.0, 0.0) * transform;
EXPECT_EQ(result.matrix, expected.matrix);
}
TEST_F(ResolveTransformTest, IdentityTransformWithOrigin) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Identity();
TransformOrigin transformOrigin = createTransformOriginPoints(25, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Even with identity transform, transform origin should still apply
// translations but they should cancel out, resulting in identity
EXPECT_EQ(result.matrix, transform.matrix);
}
TEST_F(ResolveTransformTest, MultipleTransformOperations) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Identity();
transform = transform * Transform::Translate(10.0, 20.0, 0.0);
transform = transform * Transform::Scale(2.0, 1.5, 0.0);
TransformOrigin transformOrigin = createTransformOriginPercent(50, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
EXPECT_EQ(result.matrix, transform.matrix);
}
TEST_F(ResolveTransformTest, VariousTransformOriginPositions) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Scale(2.0, 2.0, 0.);
// Test origin at top-left (0, 0)
TransformOrigin topLeft = createTransformOriginPoints(0, 0);
auto resultTopLeft =
BaseViewProps::resolveTransform(frameSize, transform, topLeft);
auto expected = Transform::Translate(50.0, 100.0, 0.0) * transform;
EXPECT_EQ(resultTopLeft.matrix, expected.matrix);
// Test origin at center (50%, 50%)
TransformOrigin center = createTransformOriginPercent(50, 50);
auto resultCenter =
BaseViewProps::resolveTransform(frameSize, transform, center);
EXPECT_EQ(resultCenter.matrix, transform.matrix);
// Test origin at bottom-right (100%, 100%)
TransformOrigin bottomRight = createTransformOriginPercent(100, 100);
auto resultBottomRight =
BaseViewProps::resolveTransform(frameSize, transform, bottomRight);
expected = Transform::Translate(-50.0, -100.0, 0.0) * transform;
EXPECT_EQ(resultBottomRight.matrix, expected.matrix);
}
// Test with z-component in transform origin
TEST_F(ResolveTransformTest, TransformOriginWithZComponent) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::Scale(1.5, 1.5, 0.);
TransformOrigin transformOrigin;
transformOrigin.xy[0] = ValueUnit(50, UnitType::Point);
transformOrigin.xy[1] = ValueUnit(100, UnitType::Point);
transformOrigin.z = 10.0f;
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
auto expected = Transform::Translate(0.0, 0.0, 10.0) * transform;
EXPECT_EQ(result.matrix, expected.matrix);
}
TEST_F(ResolveTransformTest, ArbitraryTransformMatrix) {
Size frameSize{.width = 100, .height = 200};
Transform transform;
transform.operations.push_back({
.type = TransformOperationType::Arbitrary,
.x = ValueUnit(0, UnitType::Point),
.y = ValueUnit(0, UnitType::Point),
.z = ValueUnit(0, UnitType::Point),
});
// Set custom matrix
transform.matrix = {{2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 10, 20, 0, 1}};
TransformOrigin transformOrigin = createTransformOriginPoints(25, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
auto expected = Transform::Translate(25.0, 50.0, 0.0) * transform;
EXPECT_EQ(result.matrix, expected.matrix);
}
// Test rotation with empty frame size and no transform origin
TEST_F(ResolveTransformTest, RotationEmptyFrameNoTransformOrigin) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::RotateZ(M_PI / 4.0); // 45 degrees
TransformOrigin transformOrigin; // Default (not set)
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// With empty frame size and no transform origin, should just apply the
// rotation directly
expectTransformsEqual(result, transform);
}
// Test rotation with empty frame size and transform origin in points
TEST_F(ResolveTransformTest, RotationEmptyFrameTransformOriginPoints) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::RotateZ(M_PI / 4.0); // 45 degrees
TransformOrigin transformOrigin = createTransformOriginPoints(10, 20);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// With empty frame size, center is (0, 0), so origin offset is (10, 20)
auto expected = Transform::Translate(10.0, 20.0, 0.0) * transform *
Transform::Translate(-10.0, -20.0, 0.0);
expectTransformsEqual(result, expected);
}
// Test rotation with empty frame size and transform origin in percentages
TEST_F(ResolveTransformTest, RotationEmptyFrameTransformOriginPercent) {
Size frameSize{.width = 0, .height = 0};
Transform transform = Transform::RotateZ(M_PI / 6.0); // 30 degrees
TransformOrigin transformOrigin = createTransformOriginPercent(50, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// With 0 frame size, percentages resolve to 0, so no origin offset
expectTransformsEqual(result, transform);
}
// Test rotation with non-empty frame size and no transform origin
TEST_F(ResolveTransformTest, RotationNonEmptyFrameNoTransformOrigin) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::RotateZ(M_PI / 3.0); // 60 degrees
TransformOrigin transformOrigin; // Default (not set)
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Without transform origin, rotation should happen around default center
expectTransformsEqual(result, transform);
}
// Test rotation with non-empty frame size and transform origin in points
TEST_F(ResolveTransformTest, RotationNonEmptyFrameTransformOriginPoints) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::RotateZ(M_PI / 4.0); // 45 degrees
TransformOrigin transformOrigin = createTransformOriginPoints(25, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Center of 100x200 frame is (50, 100), origin at (25, 50) means offset of
// (-25, -50)
auto expected = Transform::Translate(-25.0, -50.0, 0.0) * transform *
Transform::Translate(25.0, 50.0, 0.0);
expectTransformsEqual(result, expected);
}
// Test rotation with non-empty frame size and transform origin in percentages
TEST_F(ResolveTransformTest, RotationNonEmptyFrameTransformOriginPercent) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::RotateZ(M_PI / 2.0); // 90 degrees
TransformOrigin transformOrigin =
createTransformOriginPercent(25, 75); // 25% width, 75% height
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Should resolve percentages: 25% of 100 = 25, 75% of 200 = 150
// Center is (50, 100), so origin offset is (25-50, 150-100) = (-25, 50)
auto expected = Transform::Translate(-25.0, 50.0, 0.0) * transform *
Transform::Translate(25.0, -50.0, 0.0);
expectTransformsEqual(result, expected);
}
// Test rotation with mixed transform origin units
TEST_F(ResolveTransformTest, RotationMixedTransformOriginUnits) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::RotateZ(M_PI); // 180 degrees
TransformOrigin transformOrigin;
transformOrigin.xy[0] = ValueUnit(30, UnitType::Point); // 30 points
transformOrigin.xy[1] = ValueUnit(25, UnitType::Percent); // 25% of 200 = 50
transformOrigin.z = 0;
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Center is (50, 100), origin is (30, 50), so offset is (-20, -50)
auto expected = Transform::Translate(-20.0, -50.0, 0.0) * transform *
Transform::Translate(20.0, 50.0, 0.0);
expectTransformsEqual(result, expected);
}
// Test multiple rotations (RotateX, RotateY, RotateZ)
TEST_F(ResolveTransformTest, MultipleRotationsWithTransformOrigin) {
Size frameSize{.width = 100, .height = 100};
Transform transform = Transform::Rotate(M_PI / 6.0, M_PI / 4.0, M_PI / 3.0);
TransformOrigin transformOrigin = createTransformOriginPercent(50, 50);
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
expectTransformsEqual(result, transform);
}
// Test rotation with z-component in transform origin
TEST_F(ResolveTransformTest, RotationWithZTransformOrigin) {
Size frameSize{.width = 100, .height = 200};
Transform transform = Transform::RotateZ(M_PI / 4.0); // 45 degrees
TransformOrigin transformOrigin;
transformOrigin.xy[0] = ValueUnit(50, UnitType::Point);
transformOrigin.xy[1] = ValueUnit(100, UnitType::Point);
transformOrigin.z = 15.0f;
auto result =
BaseViewProps::resolveTransform(frameSize, transform, transformOrigin);
// Center is (50, 100), origin is (50, 100, 15), so offset is (0, 0, 15)
auto expected = Transform::Translate(0.0, 0.0, 15.0) * transform *
Transform::Translate(0.0, 0.0, -15.0);
expectTransformsEqual(result, expected);
}
// Test rotation at different origin positions (corners vs center)
TEST_F(ResolveTransformTest, RotationDifferentOriginPositions) {
Size frameSize{.width = 100, .height = 100};
Transform transform = Transform::RotateZ(M_PI / 2.0); // 90 degrees
// Test rotation around top-left corner (0, 0)
TransformOrigin topLeft = createTransformOriginPoints(0, 0);
auto resultTopLeft =
BaseViewProps::resolveTransform(frameSize, transform, topLeft);
auto expectedTopLeft = Transform::Translate(-50.0, -50.0, 0.0) * transform *
Transform::Translate(50.0, 50.0, 0.0);
expectTransformsEqual(resultTopLeft, expectedTopLeft);
// Test rotation around center (50%, 50%)
TransformOrigin center = createTransformOriginPercent(50, 50);
auto resultCenter =
BaseViewProps::resolveTransform(frameSize, transform, center);
expectTransformsEqual(resultCenter, transform);
// Test rotation around bottom-right corner (100%, 100%)
TransformOrigin bottomRight = createTransformOriginPercent(100, 100);
auto resultBottomRight =
BaseViewProps::resolveTransform(frameSize, transform, bottomRight);
auto expectedBottomRight = Transform::Translate(50.0, 50.0, 0.0) * transform *
Transform::Translate(-50.0, -50.0, 0.0);
expectTransformsEqual(resultBottomRight, expectedBottomRight);
}
} // namespace facebook::react

View File

@@ -0,0 +1,232 @@
/*
* 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 <algorithm>
#include <memory>
#include <gtest/gtest.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
namespace facebook::react {
class YogaDirtyFlagTest : public ::testing::Test {
protected:
ComponentBuilder builder_;
std::shared_ptr<RootShadowNode> rootShadowNode_;
std::shared_ptr<ViewShadowNode> innerShadowNode_;
std::shared_ptr<ScrollViewShadowNode> scrollViewShadowNode_;
YogaDirtyFlagTest() : builder_(simpleComponentBuilder()) {
// clang-format off
auto element =
Element<RootShadowNode>()
.reference(rootShadowNode_)
.tag(1)
.children({
Element<ViewShadowNode>()
.tag(2),
Element<ViewShadowNode>()
.tag(3)
.reference(innerShadowNode_)
.children({
Element<ViewShadowNode>()
.tag(4)
.props([] {
/*
* Some non-default props.
*/
auto mutableViewProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *mutableViewProps;
props.nativeId = "native Id";
props.opacity = 0.5;
props.yogaStyle.setAlignContent(yoga::Align::Baseline);
props.yogaStyle.setFlexDirection(yoga::FlexDirection::RowReverse);
return mutableViewProps;
}),
Element<ViewShadowNode>()
.tag(5),
Element<ViewShadowNode>()
.tag(6),
Element<ScrollViewShadowNode>()
.reference(scrollViewShadowNode_)
.tag(7)
.children({
Element<ViewShadowNode>()
.tag(8)
})
})
});
// clang-format on
builder_.build(element);
/*
* Yoga nodes are dirty right after creation.
*/
EXPECT_TRUE(rootShadowNode_->layoutIfNeeded());
/*
* Yoga nodes are clean (not dirty) right after layout pass.
*/
EXPECT_FALSE(rootShadowNode_->layoutIfNeeded());
}
};
TEST_F(YogaDirtyFlagTest, cloningPropsWithoutChangingThem) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
/*
* Cloning props without changing them must *not* dirty a Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [&](const ShadowNode& oldShadowNode) {
auto& componentDescriptor = oldShadowNode.getComponentDescriptor();
auto props = componentDescriptor.cloneProps(
parserContext, oldShadowNode.getProps(), RawProps());
return oldShadowNode.clone(ShadowNodeFragment{.props = props});
});
EXPECT_FALSE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, changingNonLayoutSubPropsMustNotDirtyYogaNode) {
/*
* Changing *non-layout* sub-props must *not* dirty a Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
auto viewProps = std::make_shared<ViewShadowNodeProps>();
auto& props = *viewProps;
props.nativeId = "some new native Id";
props.backgroundColor = blackColor();
props.opacity = props.opacity + 0.042;
props.zIndex = props.zIndex.value_or(0) + 42;
props.shouldRasterize = !props.shouldRasterize;
props.collapsable = !props.collapsable;
return oldShadowNode.clone(ShadowNodeFragment{.props = viewProps});
});
EXPECT_FALSE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, changingLayoutSubPropsMustDirtyYogaNode) {
/*
* Changing *layout* sub-props *must* dirty a Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
auto viewProps = std::make_shared<ViewShadowNodeProps>();
auto& props = *viewProps;
props.yogaStyle.setAlignContent(yoga::Align::Baseline);
props.yogaStyle.setDisplay(yoga::Display::None);
return oldShadowNode.clone(ShadowNodeFragment{.props = viewProps});
});
EXPECT_TRUE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, removingAllChildrenMustDirtyYogaNode) {
/*
* Removing all children *must* dirty a Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
return oldShadowNode.clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children = ShadowNode::emptySharedShadowNodeSharedList()});
});
EXPECT_TRUE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, removingLastChildMustDirtyYogaNode) {
/*
* Removing the last child *must* dirty the Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
auto children = oldShadowNode.getChildren();
children.pop_back();
std::reverse(children.begin(), children.end());
return oldShadowNode.clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children = std::make_shared<
const std::vector<std::shared_ptr<const ShadowNode>>>(
children)});
});
EXPECT_TRUE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, reversingListOfChildrenMustDirtyYogaNode) {
/*
* Reversing a list of children *must* dirty a Yoga node.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
innerShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
auto children = oldShadowNode.getChildren();
std::reverse(children.begin(), children.end());
return oldShadowNode.clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children = std::make_shared<
const std::vector<std::shared_ptr<const ShadowNode>>>(
children)});
});
EXPECT_TRUE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
TEST_F(YogaDirtyFlagTest, updatingStateForScrollViewMistNotDirtyYogaNode) {
/*
* Updating a state for *some* (not all!) components must *not* dirty Yoga
* nodes.
*/
auto newRootShadowNode = rootShadowNode_->cloneTree(
scrollViewShadowNode_->getFamily(), [](const ShadowNode& oldShadowNode) {
auto state = ScrollViewState{};
state.contentOffset = Point{.x = 42, .y = 9000};
auto& componentDescriptor = oldShadowNode.getComponentDescriptor();
auto newState = componentDescriptor.createState(
oldShadowNode.getFamily(),
std::make_shared<ScrollViewState>(state));
return oldShadowNode.clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children = ShadowNodeFragment::childrenPlaceholder(),
.state = newState});
});
EXPECT_FALSE(
static_cast<RootShadowNode&>(*newRootShadowNode).layoutIfNeeded());
}
} // namespace facebook::react