feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,195 @@
---
InheritParentConfig: true
Checks: '>
clang-diagnostic-*,
modernize-avoid-bind,
modernize-avoid-c-arrays,
modernize-concat-nested-namespaces,
modernize-deprecated-headers,
modernize-deprecated-ios-base-aliases,
modernize-loop-convert,
modernize-make-shared,
modernize-redundant-void-arg,
modernize-return-braced-init-list,
modernize-use-auto,
modernize-make-unique,
modernize-pass-by-value,
modernize-raw-string-literal,
modernize-replace-auto-ptr,
modernize-use-bool-literals,
modernize-replace-random-shuffle,
modernize-unary-static-assert,
modernize-use-emplace,
modernize-shrink-to-fit,
modernize-use-equals-default,
modernize-use-default-member-init,
modernize-use-nullptr,
modernize-use-noexcept,
modernize-use-equals-delete,
modernize-use-override,
modernize-use-using,
modernize-use-transparent-functors,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-algorithm,
performance-inefficient-string-concatenation,
performance-inefficient-vector-operation,
performance-move-const-arg,
performance-move-constructor-init,
performance-noexcept-move-constructor,
performance-type-promotion-in-math-fn,
performance-unnecessary-copy-initialization,
performance-unnecessary-value-param,
cppcoreguidelines-macro-usage,
cppcoreguidelines-narrowing-conversions,
cppcoreguidelines-no-malloc,
cppcoreguidelines-pro-bounds-pointer-arithmetic,
cppcoreguidelines-pro-type-const-cast,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-reinterpret-cast,
cppcoreguidelines-pro-type-union-access,
cppcoreguidelines-pro-type-vararg,
cppcoreguidelines-slicing,
cppcoreguidelines-special-member-functions,
readability-avoid-const-params-in-decls,
readability-braces-around-statements,
readability-const-return-type,
readability-container-size-empty,
readability-deleted-default,
readability-delete-null-pointer,
readability-implicit-bool-conversion,
readability-inconsistent-declaration-parameter-name,
readability-isolate-declaration,
readability-misplaced-array-index,
readability-named-parameter,
readability-non-const-parameter,
readability-redundant-control-flow,
readability-redundant-declaration,
readability-redundant-function-ptr-dereference,
readability-redundant-preprocessor,
readability-redundant-smartptr-get,
readability-redundant-string-cstr,
readability-redundant-string-init,
readability-simplify-boolean-expr,
readability-simplify-subscript-expr,
readability-static-accessed-through-instance,
readability-static-definition-in-anonymous-namespace,
readability-string-compare,
readability-uniqueptr-delete-release,
misc-definitions-in-headers,
misc-new-delete-overloads,
misc-non-copyable-objects,
misc-static-assert,
misc-throw-by-value-catch-by-reference,
misc-unconventional-assign-operator,
misc-uniqueptr-reset-release,
misc-unused-alias-decls,
misc-unused-parameters,
misc-unused-using-decls,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bool-pointer-implicit-conversion,
bugprone-copy-constructor-init,
bugprone-dangling-handle,
bugprone-exception-escape,
bugprone-fold-init-type,
bugprone-forward-declaration-namespace,
bugprone-forwarding-reference-overload,
bugprone-inaccurate-erase,
bugprone-incorrect-roundings,
bugprone-integer-division,
bugprone-macro-parentheses,
bugprone-macro-repeated-side-effects,
bugprone-misplaced-operator-in-strlen-in-alloc,
bugprone-misplaced-widening-cast,
bugprone-move-forwarding-reference,
bugprone-multiple-statement-macro,
bugprone-parent-virtual-call,
bugprone-sizeof-container,
bugprone-sizeof-expression,
bugprone-string-constructor,
bugprone-string-integer-assignment,
bugprone-string-literal-with-embedded-nul,
bugprone-suspicious-enum-usage,
bugprone-suspicious-memset-usage,
bugprone-suspicious-missing-comma,
bugprone-suspicious-semicolon,
bugprone-suspicious-string-compare,
bugprone-swapped-arguments,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unused-return-value,
bugprone-use-after-move,
bugprone-virtual-near-miss,
clang-analyzer-apiModeling.StdCLibraryFunctions,
clang-analyzer-apiModeling.TrustNonnull,
clang-analyzer-apiModeling.google.GTest,
clang-analyzer-core.CallAndMessage,
clang-analyzer-core.DivideZero,
clang-analyzer-core.DynamicTypePropagation,
clang-analyzer-core.NonNullParamChecker,
clang-analyzer-core.NonnilStringConstants,
clang-analyzer-core.NullDereference,
clang-analyzer-core.StackAddressEscape,
clang-analyzer-core.UndefinedBinaryOperatorResult,
clang-analyzer-core.VLASize,
clang-analyzer-core.builtin.BuiltinFunctions,
clang-analyzer-core.builtin.NoReturnFunctions,
clang-analyzer-core.uninitialized.ArraySubscript,
clang-analyzer-core.uninitialized.Assign,
clang-analyzer-core.uninitialized.Branch,
clang-analyzer-core.uninitialized.CapturedBlockVariable,
clang-analyzer-core.uninitialized.UndefReturn,
clang-analyzer-cplusplus.InnerPointer,
clang-analyzer-cplusplus.Move,
clang-analyzer-cplusplus.NewDelete,
clang-analyzer-cplusplus.NewDeleteLeaks,
clang-analyzer-cplusplus.SelfAssignment,
clang-analyzer-deadcode.DeadStores,
clang-analyzer-optin.cplusplus.VirtualCall,
clang-analyzer-optin.mpi.MPI-Checker,
clang-analyzer-optin.performance.GCDAntipattern,
clang-analyzer-optin.performance.Padding,
clang-analyzer-optin.portability.UnixAPI,
clang-analyzer-nullability.NullPassedToNonnull,
clang-analyzer-nullability.NullReturnedFromNonnull,
clang-analyzer-nullability.NullableDereferenced,
clang-analyzer-nullability.NullablePassedToNonnull,
clang-analyzer-nullability.NullableReturnedFromNonnull,
clang-analyzer-security.FloatLoopCounter,
clang-analyzer-security.insecureAPI.UncheckedReturn,
clang-analyzer-security.insecureAPI.bcmp,
clang-analyzer-security.insecureAPI.bcopy,
clang-analyzer-security.insecureAPI.bzero,
clang-analyzer-security.insecureAPI.getpw,
clang-analyzer-security.insecureAPI.gets,
clang-analyzer-security.insecureAPI.mkstemp,
clang-analyzer-security.insecureAPI.mktemp,
clang-analyzer-security.insecureAPI.rand,
clang-analyzer-security.insecureAPI.strcpy,
clang-analyzer-security.insecureAPI.vfork,
clang-analyzer-unix.API,
clang-analyzer-unix.Malloc,
clang-analyzer-unix.MallocSizeof,
clang-analyzer-unix.MismatchedDeallocator,
clang-analyzer-unix.Vfork,
clang-analyzer-unix.cstring.BadSizeArg,
clang-analyzer-unix.cstring.NullArg,
clang-analyzer-valist.CopyToSelf,
clang-analyzer-valist.Uninitialized,
clang-analyzer-valist.Unterminated,
google-build-using-namespace,
'
CheckOptions:
- key: performance-unnecessary-value-param.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$;'
- key: performance-unnecessary-copy-initialization.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$'
...

View File

@@ -0,0 +1,38 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_animations_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_animations STATIC ${react_render_animations_SRC})
target_include_directories(react_render_animations PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_animations
folly_runtime
glog
glog_init
jsi
react_config
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_mounting
react_render_uimanager
rrc_view
runtimeexecutor
yoga
)

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 <jsi/jsi.h>
#include <memory>
namespace facebook::react {
class LayoutAnimationCallbackWrapper {
public:
LayoutAnimationCallbackWrapper(jsi::Function&& callback)
: callback_(std::make_shared<jsi::Function>(std::move(callback))) {}
LayoutAnimationCallbackWrapper() : callback_(nullptr) {}
void call(jsi::Runtime& runtime) const {
if (callback_) {
callback_->call(runtime);
callback_.reset();
}
}
private:
mutable std::shared_ptr<jsi::Function> callback_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,109 @@
/*
* 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 "LayoutAnimationDriver.h"
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/animations/utils.h>
#include <algorithm>
namespace facebook::react {
void LayoutAnimationDriver::animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const {
for (auto& animation : inflightAnimations_) {
if (animation.surfaceId != surfaceId) {
continue;
}
if (animation.completed) {
continue;
}
int incompleteAnimations = 0;
for (auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
const auto& baselineShadowView = keyframe.viewStart;
const auto& finalShadowView = keyframe.viewEnd;
// The contract with the "keyframes generation" phase is that any animated
// node will have a valid configuration.
const auto layoutAnimationConfig = animation.layoutAnimationConfig;
const auto& mutationConfig =
(keyframe.type == AnimationConfigurationType::Delete
? layoutAnimationConfig.deleteConfig
: (keyframe.type == AnimationConfigurationType::Create
? layoutAnimationConfig.createConfig
: layoutAnimationConfig.updateConfig));
// Interpolate
auto progress =
calculateAnimationProgress(now, animation, mutationConfig);
auto animationTimeProgressLinear = progress.first;
auto animationInterpolationFactor = progress.second;
auto mutatedShadowView = createInterpolatedShadowView(
animationInterpolationFactor, baselineShadowView, finalShadowView);
// Create the mutation instruction
mutationsList.emplace_back(ShadowViewMutation::UpdateMutation(
keyframe.viewPrev, mutatedShadowView, keyframe.parentView));
PrintMutationInstruction("Animation Progress:", mutationsList.back());
keyframe.viewPrev = std::move(mutatedShadowView);
if (animationTimeProgressLinear < 1) {
incompleteAnimations++;
}
}
// Are there no ongoing mutations left in this animation?
if (incompleteAnimations == 0) {
animation.completed = true;
}
}
// Clear out finished animations
for (auto it = inflightAnimations_.begin();
it != inflightAnimations_.end();) {
const auto& animation = *it;
if (animation.completed) {
callCallback(animation.successCallback);
// Queue up "final" mutations for all keyframes in the completed animation
for (const auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
queueFinalMutationsForCompletedKeyFrame(
keyframe,
mutationsList,
false,
"LayoutAnimationDriver: Animation Completed");
}
it = inflightAnimations_.erase(it);
} else {
it++;
}
}
// Final step: make sure that all operations execute in the proper order.
// REMOVE operations with highest indices must operate first.
std::stable_sort(
mutationsList.begin(),
mutationsList.end(),
&shouldFirstComeBeforeSecondMutation);
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* 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/animations/LayoutAnimationKeyFrameManager.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
namespace facebook::react {
class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager {
public:
LayoutAnimationDriver(
RuntimeExecutor runtimeExecutor,
ContextContainer::Shared& contextContainer,
LayoutAnimationStatusDelegate* delegate)
: LayoutAnimationKeyFrameManager(
runtimeExecutor,
contextContainer,
delegate) {}
protected:
virtual void animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,185 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/animations/LayoutAnimationCallbackWrapper.h>
#include <react/renderer/animations/primitives.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/debug/flags.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/uimanager/LayoutAnimationStatusDelegate.h>
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <optional>
#include <unordered_set>
namespace facebook::react {
#ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
void PrintMutationInstruction(
std::string message,
const ShadowViewMutation& mutation);
void PrintMutationInstructionRelative(
std::string message,
const ShadowViewMutation& mutation,
const ShadowViewMutation& relativeMutation);
#else
#define PrintMutationInstruction(a, b)
#define PrintMutationInstructionRelative(a, b, c)
#endif
class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
public MountingOverrideDelegate {
public:
LayoutAnimationKeyFrameManager(
RuntimeExecutor runtimeExecutor,
ContextContainer::Shared& contextContainer,
LayoutAnimationStatusDelegate* delegate);
#pragma mark - UIManagerAnimationDelegate methods
void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime& runtime,
const RawValue& config,
const jsi::Value& successCallbackValue,
const jsi::Value& failureCallbackValue) const override;
void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry&
componentDescriptorRegistry) override;
// TODO: add SurfaceId to this API as well
bool shouldAnimateFrame() const override;
void stopSurface(SurfaceId surfaceId) override;
#pragma mark - MountingOverrideDelegate methods
bool shouldOverridePullTransaction() const override;
// This is used to "hijack" the diffing process to figure out which mutations
// should be animated. The mutations returned by this function will be
// executed immediately.
std::optional<MountingTransaction> pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number number,
const TransactionTelemetry& telemetry,
ShadowViewMutationList mutations) const override;
// Exposed for testing.
void uiManagerDidConfigureNextLayoutAnimation(
LayoutAnimation layoutAnimation) const;
// LayoutAnimationStatusDelegate - this is for the platform to get
// signal when animations start and complete. Setting and resetting this
// delegate is protected by a mutex; ALL method calls into this delegate are
// also protected by the mutex! The only way to set this without a mutex is
// via a constructor.
void setLayoutAnimationStatusDelegate(
LayoutAnimationStatusDelegate* delegate) const;
void setClockNow(std::function<uint64_t()> now);
protected:
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
mutable std::optional<LayoutAnimation> currentAnimation_{};
mutable std::mutex currentAnimationMutex_;
/**
* All mutations of inflightAnimations_ are thread-safe as long as
* we keep the contract of: only mutate it within the context of
* `pullTransaction`. If that contract is held, this is implicitly protected
* by the MountingCoordinator's mutex.
*/
mutable std::vector<LayoutAnimation> inflightAnimations_{};
bool hasComponentDescriptorForShadowView(const ShadowView& shadowView) const;
const ComponentDescriptor& getComponentDescriptorForShadowView(
const ShadowView& shadowView) const;
/**
* Given a `progress` between 0 and 1, a mutation and LayoutAnimation config,
* return a ShadowView with mutated props and/or LayoutMetrics.
*
* @param progress
* @param layoutAnimation
* @param animatedMutation
* @return
*/
ShadowView createInterpolatedShadowView(
Float progress,
const ShadowView& startingView,
const ShadowView& finalView) const;
void callCallback(const LayoutAnimationCallbackWrapper& callback) const;
virtual void animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const = 0;
/**
* Queue (and potentially synthesize) final mutations for a finished keyframe.
* Keyframe animation may have timed-out, or be canceled due to a conflict.
*/
void queueFinalMutationsForCompletedKeyFrame(
const AnimationKeyFrame& keyframe,
ShadowViewMutation::List& mutationsList,
bool interrupted,
const std::string& logPrefix) const;
private:
RuntimeExecutor runtimeExecutor_;
ContextContainer::Shared contextContainer_;
mutable std::mutex layoutAnimationStatusDelegateMutex_;
mutable LayoutAnimationStatusDelegate* layoutAnimationStatusDelegate_{};
mutable std::mutex surfaceIdsToStopMutex_;
mutable std::unordered_set<SurfaceId> surfaceIdsToStop_{};
// Function that returns current time in milliseconds
std::function<uint64_t()> now_;
void adjustImmediateMutationIndicesForDelayedMutations(
SurfaceId surfaceId,
ShadowViewMutation& mutation,
bool skipLastAnimation = false,
bool lastAnimationOnly = false) const;
void adjustDelayedMutationIndicesForMutation(
SurfaceId surfaceId,
const ShadowViewMutation& mutation,
bool skipLastAnimation = false) const;
void getAndEraseConflictingAnimations(
SurfaceId surfaceId,
const ShadowViewMutationList& mutations,
std::vector<AnimationKeyFrame>& conflictingAnimations) const;
/*
* Removes animations from `inflightAnimations_` for stopped surfaces.
*/
void deleteAnimationsForStoppedSurfaces() const;
void simulateImagePropsMemoryAccess(
const ShadowViewMutationList& mutations) const;
/**
* Interpolates the props values.
*/
Props::Shared interpolateProps(
const ComponentDescriptor& componentDescriptor,
const PropsParserContext& context,
Float animationProgress,
const Props::Shared& props,
const Props::Shared& newProps) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,210 @@
/*
* 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 <glog/logging.h>
#include <react/renderer/animations/primitives.h>
#include <optional>
namespace facebook::react {
static inline std::optional<AnimationType> parseAnimationType(
std::string param) {
if (param == "spring") {
return AnimationType::Spring;
}
if (param == "linear") {
return AnimationType::Linear;
}
if (param == "easeInEaseOut") {
return AnimationType::EaseInEaseOut;
}
if (param == "easeIn") {
return AnimationType::EaseIn;
}
if (param == "easeOut") {
return AnimationType::EaseOut;
}
if (param == "keyboard") {
return AnimationType::Keyboard;
}
LOG(ERROR) << "Error parsing animation type: " << param;
return {};
}
static inline std::optional<AnimationProperty> parseAnimationProperty(
std::string param) {
if (param == "opacity") {
return AnimationProperty::Opacity;
}
if (param == "scaleX") {
return AnimationProperty::ScaleX;
}
if (param == "scaleY") {
return AnimationProperty::ScaleY;
}
if (param == "scaleXY") {
return AnimationProperty::ScaleXY;
}
LOG(ERROR) << "Error parsing animation property: " << param;
return {};
}
static inline std::optional<AnimationConfig> parseAnimationConfig(
const folly::dynamic& config,
double defaultDuration,
bool parsePropertyType) {
if (config.empty() || !config.isObject()) {
return AnimationConfig{
AnimationType::Linear,
AnimationProperty::NotApplicable,
defaultDuration,
0,
0,
0};
}
const auto typeIt = config.find("type");
if (typeIt == config.items().end()) {
LOG(ERROR) << "Error parsing animation config: could not find field `type`";
return {};
}
const auto animationTypeParam = typeIt->second;
if (animationTypeParam.empty() || !animationTypeParam.isString()) {
LOG(ERROR)
<< "Error parsing animation config: could not unwrap field `type`";
return {};
}
const auto animationType = parseAnimationType(animationTypeParam.asString());
if (!animationType) {
LOG(ERROR)
<< "Error parsing animation config: could not parse field `type`";
return {};
}
AnimationProperty animationProperty = AnimationProperty::NotApplicable;
if (parsePropertyType) {
const auto propertyIt = config.find("property");
if (propertyIt == config.items().end()) {
LOG(ERROR)
<< "Error parsing animation config: could not find field `property`";
return {};
}
const auto animationPropertyParam = propertyIt->second;
if (animationPropertyParam.empty() || !animationPropertyParam.isString()) {
LOG(ERROR)
<< "Error parsing animation config: could not unwrap field `property`";
return {};
}
const auto animationPropertyParsed =
parseAnimationProperty(animationPropertyParam.asString());
if (!animationPropertyParsed) {
LOG(ERROR)
<< "Error parsing animation config: could not parse field `property`";
return {};
}
animationProperty = *animationPropertyParsed;
}
double duration = defaultDuration;
double delay = 0;
Float springDamping = 0.5;
Float initialVelocity = 0;
const auto durationIt = config.find("duration");
if (durationIt != config.items().end()) {
if (durationIt->second.isDouble()) {
duration = durationIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `duration` must be a number";
return {};
}
}
const auto delayIt = config.find("delay");
if (delayIt != config.items().end()) {
if (delayIt->second.isDouble()) {
delay = delayIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `delay` must be a number";
return {};
}
}
const auto springDampingIt = config.find("springDamping");
if (springDampingIt != config.items().end() &&
springDampingIt->second.isDouble()) {
if (springDampingIt->second.isDouble()) {
springDamping = (Float)springDampingIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `springDamping` must be a number";
return {};
}
}
const auto initialVelocityIt = config.find("initialVelocity");
if (initialVelocityIt != config.items().end()) {
if (initialVelocityIt->second.isDouble()) {
initialVelocity = (Float)initialVelocityIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `initialVelocity` must be a number";
return {};
}
}
return std::optional<AnimationConfig>(AnimationConfig{
*animationType,
animationProperty,
duration,
delay,
springDamping,
initialVelocity});
}
// Parse animation config from JS
static inline std::optional<LayoutAnimationConfig> parseLayoutAnimationConfig(
const folly::dynamic& config) {
if (config.empty() || !config.isObject()) {
return {};
}
const auto durationIt = config.find("duration");
if (durationIt == config.items().end() || !durationIt->second.isDouble()) {
return {};
}
const double duration = durationIt->second.asDouble();
const auto createConfigIt = config.find("create");
const auto createConfig = createConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(createConfigIt->second, duration, true);
const auto updateConfigIt = config.find("update");
const auto updateConfig = updateConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(updateConfigIt->second, duration, false);
const auto deleteConfigIt = config.find("delete");
const auto deleteConfig = deleteConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(deleteConfigIt->second, duration, true);
if (!createConfig || !updateConfig || !deleteConfig) {
return {};
}
return LayoutAnimationConfig{
duration, *createConfig, *updateConfig, *deleteConfig};
}
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* 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/animations/LayoutAnimationCallbackWrapper.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/mounting/ShadowView.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <vector>
namespace facebook::react {
// This corresponds exactly with JS.
enum class AnimationType {
None = 0,
Spring = 1,
Linear = 2,
EaseInEaseOut = 4,
EaseIn = 8,
EaseOut = 16,
Keyboard = 32
};
enum class AnimationProperty {
NotApplicable = 0,
Opacity = 1,
ScaleX = 2,
ScaleY = 4,
ScaleXY = 8
};
// This corresponds exactly with JS.
struct AnimationConfig {
AnimationType animationType = AnimationType::None;
AnimationProperty animationProperty = AnimationProperty::NotApplicable;
double duration =
0; // these are perhaps better represented as uint64_t, but they
// come from JS as doubles
double delay = 0;
Float springDamping = 0;
Float initialVelocity = 0;
};
// This corresponds exactly with JS.
struct LayoutAnimationConfig {
double duration; // ms
AnimationConfig createConfig;
AnimationConfig updateConfig;
AnimationConfig deleteConfig;
};
enum class AnimationConfigurationType { Create = 1, Update = 2, Delete = 4 };
struct AnimationKeyFrame {
// The mutation(s) that should be executed once the animation completes.
// This maybe empty.
// For CREATE/INSERT this will contain CREATE, INSERT in that order.
// For REMOVE/DELETE, same.
std::vector<ShadowViewMutation> finalMutationsForKeyFrame;
// The type of animation this is (for configuration purposes)
AnimationConfigurationType type;
// Tag representing the node being animated.
Tag tag;
ShadowView parentView;
// ShadowView representing the start and end points of this animation.
ShadowView viewStart;
ShadowView viewEnd;
// ShadowView representing the previous frame of the animation.
ShadowView viewPrev;
// If an animation interrupts an existing one, the starting state may actually
// be halfway through the intended transition.
double initialProgress;
bool invalidated{false};
// In the case where some mutation conflicts with this keyframe,
// should we generate final synthetic UPDATE mutations for this keyframe?
bool generateFinalSyntheticMutations{true};
};
struct LayoutAnimation {
SurfaceId surfaceId;
uint64_t startTime;
bool completed = false;
LayoutAnimationConfig layoutAnimationConfig;
LayoutAnimationCallbackWrapper successCallback;
LayoutAnimationCallbackWrapper failureCallback;
std::vector<AnimationKeyFrame> keyFrames;
};
} // namespace facebook::react

View File

@@ -0,0 +1,541 @@
/*
* 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 <vector>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/animations/LayoutAnimationDriver.h>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs.h>
#include <react/test_utils/Entropy.h>
#include <react/test_utils/MockClock.h>
#include <react/test_utils/shadowTreeGeneration.h>
// Uncomment when random test blocks are uncommented below.
// #include <algorithm>
// #include <random>
MockClock::time_point MockClock::time_ = {};
namespace facebook::react {
static void testShadowNodeTreeLifeCycleLayoutAnimations(
uint_fast32_t seed,
int treeSize,
int repeats,
int stages,
int animation_duration,
int animation_frames,
int delay_ms_between_frames,
int delay_ms_between_stages,
int delay_ms_between_repeats,
bool commits_conflicting_mutations = false,
int final_animation_delay = 0) {
auto entropy = seed == 0 ? Entropy() : Entropy(seed);
auto eventDispatcher = EventDispatcher::Shared{};
auto contextContainer = std::make_shared<const ContextContainer>();
auto componentDescriptorParameters =
ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr};
auto viewComponentDescriptor =
ViewComponentDescriptor(componentDescriptorParameters);
auto rootComponentDescriptor =
RootComponentDescriptor(componentDescriptorParameters);
PropsParserContext parserContext{-1, *contextContainer};
// Create a RuntimeExecutor
RuntimeExecutor runtimeExecutor =
[](const std::function<void(jsi::Runtime&)>& /*unused*/) {};
// Create component descriptor registry for animation driver
auto providerRegistry =
std::make_shared<ComponentDescriptorProviderRegistry>();
auto componentDescriptorRegistry =
providerRegistry->createComponentDescriptorRegistry(
componentDescriptorParameters);
providerRegistry->add(
concreteComponentDescriptorProvider<ViewComponentDescriptor>());
// Create Animation Driver
auto animationDriver = std::make_shared<LayoutAnimationDriver>(
runtimeExecutor, contextContainer, nullptr);
animationDriver->setComponentDescriptorRegistry(componentDescriptorRegistry);
// Mock animation timers
animationDriver->setClockNow([]() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
MockClock::now().time_since_epoch())
.count();
});
auto allNodes = std::vector<ShadowNode::Shared>{};
for (int i = 0; i < repeats; i++) {
allNodes.clear();
int surfaceIdInt = 1;
auto surfaceId = SurfaceId(surfaceIdInt);
auto family = rootComponentDescriptor.createFamily(
{Tag(surfaceIdInt), surfaceId, nullptr});
// Creating an initial root shadow node.
auto emptyRootNode = std::const_pointer_cast<RootShadowNode>(
std::static_pointer_cast<const RootShadowNode>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
family)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
parserContext,
LayoutConstraints{
Size{512, 0}, Size{512, std::numeric_limits<Float>::infinity()}},
LayoutContext{});
// Generation of a random tree.
auto singleRootChildNode =
generateShadowNodeTree(entropy, viewComponentDescriptor, treeSize);
// Injecting a tree into the root node.
auto currentRootNode = std::static_pointer_cast<const RootShadowNode>(
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
ShadowNodeFragment::propsPlaceholder(),
std::make_shared<ShadowNode::ListOfShared>(
ShadowNode::ListOfShared{singleRootChildNode})}));
// Building an initial view hierarchy.
auto viewTree = buildStubViewTreeWithoutUsingDifferentiator(*emptyRootNode);
viewTree.mutate(
calculateShadowViewMutations(*emptyRootNode, *currentRootNode));
for (int j = 0; j < stages; j++) {
auto nextRootNode = currentRootNode;
// Mutating the tree.
alterShadowTree(
entropy,
nextRootNode,
{
&messWithChildren,
&messWithYogaStyles,
&messWithLayoutableOnlyFlag,
});
std::vector<const LayoutableShadowNode*> affectedLayoutableNodes{};
affectedLayoutableNodes.reserve(1024);
// Laying out the tree.
std::const_pointer_cast<RootShadowNode>(nextRootNode)
->layoutIfNeeded(&affectedLayoutableNodes);
nextRootNode->sealRecursive();
allNodes.push_back(nextRootNode);
// Calculating mutations.
auto originalMutations =
calculateShadowViewMutations(*currentRootNode, *nextRootNode);
// If tree randomization produced no changes in the form of mutations,
// don't bother trying to animate because this violates a bunch of our
// assumptions in this test
if (originalMutations.empty()) {
continue;
}
// If we only mutated the root... also don't bother
if (originalMutations.size() == 1 &&
(originalMutations[0].oldChildShadowView.tag == 1 ||
originalMutations[0].newChildShadowView.tag == 1)) {
continue;
}
// Configure animation
animationDriver->uiManagerDidConfigureNextLayoutAnimation(
{surfaceId,
0,
false,
{(double)animation_duration,
{/* Create */ AnimationType::EaseInEaseOut,
AnimationProperty::Opacity,
(double)animation_duration,
0,
0,
0},
{/* Update */ AnimationType::EaseInEaseOut,
AnimationProperty::ScaleXY,
(double)animation_duration,
0,
0,
0},
{/* Delete */ AnimationType::EaseInEaseOut,
AnimationProperty::Opacity,
(double)animation_duration,
0,
0,
0}},
{},
{},
{}});
// Get mutations for each frame
for (int k = 0; k < animation_frames + 2; k++) {
auto mutationsInput = ShadowViewMutation::List{};
if (k == 0) {
mutationsInput = originalMutations;
}
if (k != (animation_frames + 1)) {
EXPECT_TRUE(animationDriver->shouldOverridePullTransaction());
} else if (!commits_conflicting_mutations) {
EXPECT_FALSE(animationDriver->shouldOverridePullTransaction());
}
auto telemetry = TransactionTelemetry{};
telemetry.willLayout();
telemetry.willCommit();
telemetry.willDiff();
auto transaction = animationDriver->pullTransaction(
surfaceId, 0, telemetry, mutationsInput);
EXPECT_TRUE(transaction.has_value() || k == animation_frames);
// We have something to validate.
if (transaction.has_value()) {
auto mutations = transaction->getMutations();
// Mutating the view tree.
viewTree.mutate(mutations);
// We don't do any validation on this until all animations are
// finished!
}
MockClock::advance_by(
std::chrono::milliseconds(delay_ms_between_frames));
}
// After the animation is completed...
// Build a view tree to compare with.
// After all the synthetic mutations, at the end of the animation,
// the mutated and newly-constructed trees should be identical.
if (!commits_conflicting_mutations) {
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*nextRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR)
<< "Entropy seed: " << entropy.getSeed()
<< ". To see why trees are different, define STUB_VIEW_TREE_VERBOSE and see logging in StubViewTree.cpp.\n";
EXPECT_TRUE(false);
}
}
currentRootNode = nextRootNode;
MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_stages));
}
// Flush all remaining animations before validating trees
if (final_animation_delay > 0) {
MockClock::advance_by(std::chrono::milliseconds(final_animation_delay));
auto telemetry = TransactionTelemetry{};
telemetry.willLayout();
telemetry.willCommit();
telemetry.willDiff();
auto transaction =
animationDriver->pullTransaction(surfaceId, 0, telemetry, {});
// We have something to validate.
if (transaction.has_value()) {
auto mutations = transaction->getMutations();
// Mutating the view tree.
viewTree.mutate(mutations);
// We don't do any validation on this until all animations are
// finished!
}
}
// After all animations are completed...
// Build a view tree to compare with.
// After all the synthetic mutations, at the end of the animation,
// the mutated and newly-constructed trees should be identical.
if (commits_conflicting_mutations) {
auto rebuiltViewTree =
buildStubViewTreeWithoutUsingDifferentiator(*currentRootNode);
// Comparing the newly built tree with the updated one.
if (rebuiltViewTree != viewTree) {
// Something went wrong.
LOG(ERROR)
<< "Entropy seed: " << entropy.getSeed()
<< ". To see why trees are different, define STUB_VIEW_TREE_VERBOSE and see logging in StubViewTree.cpp.\n";
EXPECT_TRUE(false);
}
}
MockClock::advance_by(std::chrono::milliseconds(delay_ms_between_repeats));
}
SUCCEED();
}
} // namespace facebook::react
using namespace facebook::react;
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_2029343357) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357, /* working seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_3619914559) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 3619914559, /* working seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_597132284) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_774986518) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 774986518, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_NonOverlapping_1450614414) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 1450614414, /* failing seed found 5-10-2021 */
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsFewStages_NonOverlapping) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
TEST(LayoutAnimationTest, stableBiggerTreeFewRepeatsManyStages_NonOverlapping) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 128,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000);
}
// You may uncomment this - locally only! - to generate failing seeds.
// TEST(LayoutAnimationTest, stableSmallerTreeFewRepeatsFewStages_Random) {
// std::random_device device;
// for (int i = 0; i < 10; i++) {
// uint_fast32_t seed = device();
// LOG(ERROR) << "Seed: " << seed;
// testShadowNodeTreeLifeCycleLayoutAnimations(
// /* seed */ seed,
// /* size */ 128,
// /* repeats */ 128,
// /* stages */ 10,
// /* animation_duration */ 1000,
// /* animation_frames*/ 10,
// /* delay_ms_between_frames */ 100,
// /* delay_ms_between_stages */ 100,
// /* delay_ms_between_repeats */ 2000);
// }
// // Fail if you want output to get seeds
// LOG(ERROR) << "ALL RUNS SUCCESSFUL";
// // react_native_assert(false);
// }
//
// These tests are "overlapping", meaning that mutations will be committed
// before the previous animation completes.
//
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_2029343357) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 9, // an animation completes in 10 frames, so this
// causes conflicts
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 10000 + 1);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_597132284) {
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284,
/* size */ 128,
/* repeats */ 128,
/* stages */ 10,
/* animation_duration */ 1000,
/* animation_frames*/ 9, // an animation completes in 10 frames, so this
// causes conflicts
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 10000 + 1);
}
TEST(
LayoutAnimationTest,
stableSmallerTreeFewRepeatsFewStages_Overlapping_ManyConflicts_597132284) {
GTEST_SKIP();
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 597132284,
/* size */ 128,
/* repeats */ 128,
/* stages */ 50,
/* animation_duration */ 1000,
/* animation_frames*/ 5, // an animation completes in 10 frames, so this
// causes conflicts. We only animate 5 frames,
// but have 50 stages, so conflicts stack up
// quickly.
/* delay_ms_between_frames */ 100,
/* delay_ms_between_stages */ 100,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ 50000 + 1);
}
TEST(
LayoutAnimationTest,
stableBiggerTreeFewRepeatsManyStages_Overlapping_ManyConflicts_2029343357) {
GTEST_SKIP();
testShadowNodeTreeLifeCycleLayoutAnimations(
/* seed */ 2029343357,
/* size */ 512,
/* repeats */ 32,
/* stages */ 128,
/* animation_duration */ 1000,
/* animation_frames*/ 10,
/* delay_ms_between_frames */ 10,
/* delay_ms_between_stages */ 10,
/* delay_ms_between_repeats */ 2000,
/* commits_conflicting_mutations */ true,
/* final_animation_delay */ (128 * 1000 + 100));
}
// You may uncomment this -
// locally only !-to generate failing seeds.
// TEST(
// LayoutAnimationTest,
// stableSmallerTreeFewRepeatsFewStages_Overlapping_Random) {
// std::random_device device;
// for (int i = 0; i < 10; i++) {
// uint_fast32_t seed = device();
// LOG(ERROR) << "Seed: " << seed;
// testShadowNodeTreeLifeCycleLayoutAnimations(
// /* seed */ seed,
// /* size */ 512,
// /* repeats */ 32,
// /* stages */ 128,
// /* animation_duration */ 1000,
// /* animation_frames*/ 10,
// /* delay_ms_between_frames */ 10,
// /* delay_ms_between_stages */ 10,
// /* delay_ms_between_repeats */ 2000,
// /* commits_conflicting_mutations */ true,
// /* final_animation_delay */ (128 * 1000 + 100));
// }
// // Fail if you want output to get seeds
// LOG(ERROR) << "ALL RUNS SUCCESSFUL";
// // react_native_assert(false);
// }

View File

@@ -0,0 +1,69 @@
/*
* 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 "utils.h"
#include <cmath>
namespace facebook::react {
std::pair<Float, Float> calculateAnimationProgress(
uint64_t now,
const LayoutAnimation& animation,
const AnimationConfig& mutationConfig) {
if (mutationConfig.animationType == AnimationType::None) {
return {1, 1};
}
uint64_t startTime = animation.startTime;
auto delay = (uint64_t)mutationConfig.delay;
uint64_t endTime = startTime + delay + (uint64_t)mutationConfig.duration;
if (now >= endTime) {
return {1, 1};
}
if (now < startTime + delay) {
return {0, 0};
}
double linearTimeProgression = 1 -
(double)(endTime - delay - now) / (double)(endTime - animation.startTime);
if (mutationConfig.animationType == AnimationType::Linear) {
return {linearTimeProgression, linearTimeProgression};
} else if (mutationConfig.animationType == AnimationType::EaseIn) {
// This is an accelerator-style interpolator.
// In the future, this parameter (2.0) could be adjusted. This has been the
// default for Classic RN forever.
return {linearTimeProgression, pow(linearTimeProgression, 2.0)};
} else if (mutationConfig.animationType == AnimationType::EaseOut) {
// This is an decelerator-style interpolator.
// In the future, this parameter (2.0) could be adjusted. This has been the
// default for Classic RN forever.
return {linearTimeProgression, 1.0 - pow(1 - linearTimeProgression, 2.0)};
} else if (mutationConfig.animationType == AnimationType::EaseInEaseOut) {
// This is a combination of accelerate+decelerate.
// The animation starts and ends slowly, and speeds up in the middle.
return {
linearTimeProgression,
cos((linearTimeProgression + 1.0) * M_PI) / 2 + 0.5};
} else if (mutationConfig.animationType == AnimationType::Spring) {
// Using mSpringDamping in this equation is not really the exact
// mathematical springDamping, but a good approximation We need to replace
// this equation with the right Factor that accounts for damping and
// friction
double damping = mutationConfig.springDamping;
return {
linearTimeProgression,
(1 +
pow(2, -10 * linearTimeProgression) *
sin((linearTimeProgression - damping / 4) * M_PI * 2 / damping))};
} else {
return {linearTimeProgression, linearTimeProgression};
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,79 @@
/*
* 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/animations/primitives.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
namespace facebook::react {
static inline bool shouldFirstComeBeforeSecondRemovesOnly(
const ShadowViewMutation& lhs,
const ShadowViewMutation& rhs) noexcept {
// Make sure that removes on the same level are sorted - highest indices must
// come first.
return (lhs.type == ShadowViewMutation::Type::Remove &&
lhs.type == rhs.type) &&
(lhs.parentShadowView.tag == rhs.parentShadowView.tag) &&
(lhs.index > rhs.index);
}
static inline bool shouldFirstComeBeforeSecondMutation(
const ShadowViewMutation& lhs,
const ShadowViewMutation& rhs) noexcept {
if (lhs.type != rhs.type) {
// Deletes always come last
if (lhs.type == ShadowViewMutation::Type::Delete) {
return false;
}
if (rhs.type == ShadowViewMutation::Type::Delete) {
return true;
}
// Remove comes before insert
if (lhs.type == ShadowViewMutation::Type::Remove &&
rhs.type == ShadowViewMutation::Type::Insert) {
return true;
}
if (rhs.type == ShadowViewMutation::Type::Remove &&
lhs.type == ShadowViewMutation::Type::Insert) {
return false;
}
// Create comes before insert
if (lhs.type == ShadowViewMutation::Type::Create &&
rhs.type == ShadowViewMutation::Type::Insert) {
return true;
}
if (rhs.type == ShadowViewMutation::Type::Create &&
lhs.type == ShadowViewMutation::Type::Insert) {
return false;
}
} else {
// Make sure that removes on the same level are sorted - highest indices
// must come first.
if (lhs.type == ShadowViewMutation::Type::Remove &&
lhs.parentShadowView.tag == rhs.parentShadowView.tag) {
if (lhs.index > rhs.index) {
return true;
} else {
return false;
}
}
}
return false;
}
std::pair<Float, Float> calculateAnimationProgress(
uint64_t now,
const LayoutAnimation& animation,
const AnimationConfig& mutationConfig);
} // namespace facebook::react

View File

@@ -0,0 +1,173 @@
/*
* 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 "AttributedString.h"
#include <react/renderer/debug/DebugStringConvertibleItem.h>
namespace facebook::react {
using Fragment = AttributedString::Fragment;
using Fragments = AttributedString::Fragments;
#pragma mark - Fragment
std::string Fragment::AttachmentCharacter() {
// C++20 makes char8_t a distinct type from char, and u8 string literals
// consist of char8_t instead of char, which in turn requires std::u8string,
// etc. Here we were assuming char was UTF-8 anyway, so just cast to that
// (which is valid because char* is allowed to alias anything).
return reinterpret_cast<const char*>(
u8"\uFFFC"); // Unicode `OBJECT REPLACEMENT CHARACTER`
}
bool Fragment::isAttachment() const {
return string == AttachmentCharacter();
}
bool Fragment::operator==(const Fragment& rhs) const {
return std::tie(
string,
textAttributes,
parentShadowView.tag,
parentShadowView.layoutMetrics) ==
std::tie(
rhs.string,
rhs.textAttributes,
rhs.parentShadowView.tag,
rhs.parentShadowView.layoutMetrics);
}
bool Fragment::isContentEqual(const Fragment& rhs) const {
return std::tie(string, textAttributes) ==
std::tie(rhs.string, rhs.textAttributes);
}
bool Fragment::operator!=(const Fragment& rhs) const {
return !(*this == rhs);
}
#pragma mark - AttributedString
void AttributedString::appendFragment(const Fragment& fragment) {
ensureUnsealed();
if (fragment.string.empty()) {
return;
}
fragments_.push_back(fragment);
}
void AttributedString::prependFragment(const Fragment& fragment) {
ensureUnsealed();
if (fragment.string.empty()) {
return;
}
fragments_.insert(fragments_.begin(), fragment);
}
void AttributedString::appendAttributedString(
const AttributedString& attributedString) {
ensureUnsealed();
fragments_.insert(
fragments_.end(),
attributedString.fragments_.begin(),
attributedString.fragments_.end());
}
void AttributedString::prependAttributedString(
const AttributedString& attributedString) {
ensureUnsealed();
fragments_.insert(
fragments_.begin(),
attributedString.fragments_.begin(),
attributedString.fragments_.end());
}
const Fragments& AttributedString::getFragments() const {
return fragments_;
}
Fragments& AttributedString::getFragments() {
return fragments_;
}
std::string AttributedString::getString() const {
auto string = std::string{};
for (const auto& fragment : fragments_) {
string += fragment.string;
}
return string;
}
bool AttributedString::isEmpty() const {
return fragments_.empty();
}
bool AttributedString::compareTextAttributesWithoutFrame(
const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (fragments_[i].textAttributes != rhs.fragments_[i].textAttributes ||
fragments_[i].string != rhs.fragments_[i].string) {
return false;
}
}
return true;
}
bool AttributedString::operator==(const AttributedString& rhs) const {
return fragments_ == rhs.fragments_;
}
bool AttributedString::operator!=(const AttributedString& rhs) const {
return !(*this == rhs);
}
bool AttributedString::isContentEqual(const AttributedString& rhs) const {
if (fragments_.size() != rhs.fragments_.size()) {
return false;
}
for (size_t i = 0; i < fragments_.size(); i++) {
if (!fragments_[i].isContentEqual(rhs.fragments_[i])) {
return false;
}
}
return true;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList AttributedString::getDebugChildren() const {
auto list = SharedDebugStringConvertibleList{};
for (auto&& fragment : fragments_) {
auto propsList =
fragment.textAttributes.DebugStringConvertible::getDebugProps();
list.push_back(std::make_shared<DebugStringConvertibleItem>(
"Fragment",
fragment.string,
SharedDebugStringConvertibleList(),
propsList));
}
return list;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,146 @@
/*
* 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 <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/core/Sealable.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/mounting/ShadowView.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class AttributedString;
using SharedAttributedString = std::shared_ptr<const AttributedString>;
/*
* Simple, cross-platform, React-specific implementation of attributed string
* (aka spanned string).
* `AttributedString` is basically a list of `Fragments` which have `string` and
* `textAttributes` + `shadowNode` associated with the `string`.
*/
class AttributedString : public Sealable, public DebugStringConvertible {
public:
class Fragment {
public:
static std::string AttachmentCharacter();
std::string string;
TextAttributes textAttributes;
ShadowView parentShadowView;
/*
* Returns true is the Fragment represents an attachment.
* Equivalent to `string == AttachmentCharacter()`.
*/
bool isAttachment() const;
/*
* Returns whether the underlying text and attributes are equal,
* disregarding layout or other information.
*/
bool isContentEqual(const Fragment& rhs) const;
bool operator==(const Fragment& rhs) const;
bool operator!=(const Fragment& rhs) const;
};
class Range {
public:
int location{0};
int length{0};
};
using Fragments = std::vector<Fragment>;
/*
* Appends and prepends a `fragment` to the string.
*/
void appendFragment(const Fragment& fragment);
void prependFragment(const Fragment& fragment);
/*
* Appends and prepends an `attributedString` (all its fragments) to
* the string.
*/
void appendAttributedString(const AttributedString& attributedString);
void prependAttributedString(const AttributedString& attributedString);
/*
* Returns a read-only reference to a list of fragments.
*/
const Fragments& getFragments() const;
/*
* Returns a reference to a list of fragments.
*/
Fragments& getFragments();
/*
* Returns a string constructed from all strings in all fragments.
*/
std::string getString() const;
/*
* Returns `true` if the string is empty (has no any fragments).
*/
bool isEmpty() const;
/**
* Compares equality of TextAttributes of all Fragments on both sides.
*/
bool compareTextAttributesWithoutFrame(const AttributedString& rhs) const;
bool isContentEqual(const AttributedString& rhs) const;
bool operator==(const AttributedString& rhs) const;
bool operator!=(const AttributedString& rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugChildren() const override;
#endif
private:
Fragments fragments_;
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::AttributedString::Fragment> {
size_t operator()(
const facebook::react::AttributedString::Fragment& fragment) const {
return facebook::react::hash_combine(
fragment.string,
fragment.textAttributes,
fragment.parentShadowView,
fragment.parentShadowView.layoutMetrics);
}
};
template <>
struct hash<facebook::react::AttributedString> {
size_t operator()(
const facebook::react::AttributedString& attributedString) const {
auto seed = size_t{0};
for (const auto& fragment : attributedString.getFragments()) {
facebook::react::hash_combine(seed, fragment);
}
return seed;
}
};
} // namespace std

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.
*/
#include "AttributedStringBox.h"
#include <react/debug/react_native_assert.h>
#include <utility>
namespace facebook::react {
AttributedStringBox::AttributedStringBox()
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(AttributedString{})),
opaquePointer_({}){};
AttributedStringBox::AttributedStringBox(const AttributedString& value)
: mode_(Mode::Value),
value_(std::make_shared<const AttributedString>(value)),
opaquePointer_({}){};
AttributedStringBox::AttributedStringBox(std::shared_ptr<void> opaquePointer)
: mode_(Mode::OpaquePointer),
value_({}),
opaquePointer_(std::move(opaquePointer)) {}
AttributedStringBox::AttributedStringBox(AttributedStringBox&& other) noexcept
: mode_(other.mode_),
value_(std::move(other.value_)),
opaquePointer_(std::move(other.opaquePointer_)) {
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
AttributedStringBox::Mode AttributedStringBox::getMode() const {
return mode_;
}
const AttributedString& AttributedStringBox::getValue() const {
react_native_assert(mode_ == AttributedStringBox::Mode::Value);
react_native_assert(value_);
return *value_;
}
std::shared_ptr<void> AttributedStringBox::getOpaquePointer() const {
react_native_assert(mode_ == AttributedStringBox::Mode::OpaquePointer);
react_native_assert(opaquePointer_);
return opaquePointer_;
}
AttributedStringBox& AttributedStringBox::operator=(
AttributedStringBox&& other) noexcept {
if (this != &other) {
mode_ = other.mode_;
value_ = std::move(other.value_);
opaquePointer_ = std::move(other.opaquePointer_);
other.mode_ = AttributedStringBox::Mode::Value;
other.value_ = std::make_shared<const AttributedString>(AttributedString{});
}
return *this;
}
bool operator==(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
if (lhs.getMode() != rhs.getMode()) {
return false;
}
switch (lhs.getMode()) {
case AttributedStringBox::Mode::Value:
return lhs.getValue() == rhs.getValue();
case AttributedStringBox::Mode::OpaquePointer:
return lhs.getOpaquePointer() == rhs.getOpaquePointer();
}
}
bool operator!=(
const AttributedStringBox& lhs,
const AttributedStringBox& rhs) {
return !(lhs == rhs);
}
} // namespace facebook::react

View File

@@ -0,0 +1,78 @@
/*
* 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 <react/renderer/attributedstring/AttributedString.h>
namespace facebook::react {
/*
* Represents an object storing a shared `AttributedString` or a shared pointer
* to some opaque platform-specific object that can be used as an attributed
* string. The class serves two main purposes:
* - Represent type-erased attributed string entity (which can be
* platform-specific or platform-independent);
* - Represent a container that can be copied with constant complexity.
*/
class AttributedStringBox final {
public:
enum class Mode { Value, OpaquePointer };
/*
* Default constructor constructs an empty string.
*/
AttributedStringBox();
/*
* Custom explicit constructors.
*/
explicit AttributedStringBox(const AttributedString& value);
explicit AttributedStringBox(std::shared_ptr<void> opaquePointer);
/*
* Movable, Copyable, Assignable.
*/
AttributedStringBox(const AttributedStringBox& other) = default;
AttributedStringBox(AttributedStringBox&& other) noexcept;
AttributedStringBox& operator=(const AttributedStringBox& other) = default;
AttributedStringBox& operator=(AttributedStringBox&& other) noexcept;
/*
* Getters.
*/
Mode getMode() const;
const AttributedString& getValue() const;
std::shared_ptr<void> getOpaquePointer() const;
private:
Mode mode_;
std::shared_ptr<const AttributedString> value_;
std::shared_ptr<void> opaquePointer_;
};
bool operator==(const AttributedStringBox& lhs, const AttributedStringBox& rhs);
bool operator!=(const AttributedStringBox& lhs, const AttributedStringBox& rhs);
} // namespace facebook::react
template <>
struct std::hash<facebook::react::AttributedStringBox> {
size_t operator()(
const facebook::react::AttributedStringBox& attributedStringBox) const {
switch (attributedStringBox.getMode()) {
case facebook::react::AttributedStringBox::Mode::Value:
return std::hash<facebook::react::AttributedString>()(
attributedStringBox.getValue());
case facebook::react::AttributedStringBox::Mode::OpaquePointer:
return std::hash<std::shared_ptr<void>>()(
attributedStringBox.getOpaquePointer());
}
}
};

View File

@@ -0,0 +1,35 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_attributedstring_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_attributedstring STATIC ${react_render_attributedstring_SRC})
target_include_directories(react_render_attributedstring PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_attributedstring
folly_runtime
glog
glog_init
react_debug
rrc_view
react_render_core
react_render_debug
react_render_graphics
react_render_mapbuffer
react_utils
rrc_view
yoga
)

View File

@@ -0,0 +1,57 @@
/*
* 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 "ParagraphAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
return std::tie(
maximumNumberOfLines,
ellipsizeMode,
textBreakStrategy,
adjustsFontSizeToFit,
includeFontPadding,
android_hyphenationFrequency) ==
std::tie(
rhs.maximumNumberOfLines,
rhs.ellipsizeMode,
rhs.textBreakStrategy,
rhs.adjustsFontSizeToFit,
rhs.includeFontPadding,
rhs.android_hyphenationFrequency) &&
floatEquality(minimumFontSize, rhs.minimumFontSize) &&
floatEquality(maximumFontSize, rhs.maximumFontSize);
}
bool ParagraphAttributes::operator!=(const ParagraphAttributes& rhs) const {
return !(*this == rhs);
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
return {
debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines),
debugStringConvertibleItem("ellipsizeMode", ellipsizeMode),
debugStringConvertibleItem("textBreakStrategy", textBreakStrategy),
debugStringConvertibleItem("adjustsFontSizeToFit", adjustsFontSizeToFit),
debugStringConvertibleItem("minimumFontSize", minimumFontSize),
debugStringConvertibleItem("maximumFontSize", maximumFontSize),
debugStringConvertibleItem("includeFontPadding", includeFontPadding),
debugStringConvertibleItem(
"android_hyphenationFrequency", android_hyphenationFrequency)};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <limits>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Float.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class ParagraphAttributes;
using SharedParagraphAttributes = std::shared_ptr<const ParagraphAttributes>;
/*
* Represents all visual attributes of a paragraph of text.
* Two data structures, ParagraphAttributes and AttributedText, should be
* enough to define visual representation of a piece of text on the screen.
*/
class ParagraphAttributes : public DebugStringConvertible {
public:
#pragma mark - Fields
/*
* Maximum number of lines which paragraph can take.
* Zero value represents "no limit".
*/
int maximumNumberOfLines{};
/*
* In case if a text cannot fit given boundaries, defines a place where
* an ellipsize should be placed.
*/
EllipsizeMode ellipsizeMode{};
/*
* (Android only) Break strategy for breaking paragraphs into lines.
*/
TextBreakStrategy textBreakStrategy{TextBreakStrategy::HighQuality};
/*
* Enables font size adjustment to fit constrained boundaries.
*/
bool adjustsFontSizeToFit{};
/*
* (Android only) Leaves enough room for ascenders and descenders instead of
* using the font ascent and descent strictly.
*/
bool includeFontPadding{true};
/*
* (Android only) Frequency of automatic hyphenation to use when determining
* word breaks.
*/
HyphenationFrequency android_hyphenationFrequency{};
/*
* In case of font size adjustment enabled, defines minimum and maximum
* font sizes.
*/
Float minimumFontSize{std::numeric_limits<Float>::quiet_NaN()};
Float maximumFontSize{std::numeric_limits<Float>::quiet_NaN()};
bool operator==(const ParagraphAttributes&) const;
bool operator!=(const ParagraphAttributes&) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::ParagraphAttributes> {
size_t operator()(
const facebook::react::ParagraphAttributes& attributes) const {
return facebook::react::hash_combine(
attributes.maximumNumberOfLines,
attributes.ellipsizeMode,
attributes.textBreakStrategy,
attributes.adjustsFontSizeToFit,
attributes.minimumFontSize,
attributes.maximumFontSize,
attributes.includeFontPadding,
attributes.android_hyphenationFrequency);
}
};
} // namespace std

View File

@@ -0,0 +1,235 @@
/*
* 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 "TextAttributes.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/utils/FloatComparison.h>
#include <cmath>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
namespace facebook::react {
void TextAttributes::apply(TextAttributes textAttributes) {
// Color
foregroundColor = textAttributes.foregroundColor
? textAttributes.foregroundColor
: foregroundColor;
backgroundColor = textAttributes.backgroundColor
? textAttributes.backgroundColor
: backgroundColor;
opacity =
!std::isnan(textAttributes.opacity) ? textAttributes.opacity : opacity;
// Font
fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily
: fontFamily;
fontSize =
!std::isnan(textAttributes.fontSize) ? textAttributes.fontSize : fontSize;
fontSizeMultiplier = !std::isnan(textAttributes.fontSizeMultiplier)
? textAttributes.fontSizeMultiplier
: fontSizeMultiplier;
fontWeight = textAttributes.fontWeight.has_value() ? textAttributes.fontWeight
: fontWeight;
fontStyle = textAttributes.fontStyle.has_value() ? textAttributes.fontStyle
: fontStyle;
fontVariant = textAttributes.fontVariant.has_value()
? textAttributes.fontVariant
: fontVariant;
allowFontScaling = textAttributes.allowFontScaling.has_value()
? textAttributes.allowFontScaling
: allowFontScaling;
dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value()
? textAttributes.dynamicTypeRamp
: dynamicTypeRamp;
letterSpacing = !std::isnan(textAttributes.letterSpacing)
? textAttributes.letterSpacing
: letterSpacing;
textTransform = textAttributes.textTransform.has_value()
? textAttributes.textTransform
: textTransform;
// Paragraph Styles
lineHeight = !std::isnan(textAttributes.lineHeight)
? textAttributes.lineHeight
: lineHeight;
alignment = textAttributes.alignment.has_value() ? textAttributes.alignment
: alignment;
baseWritingDirection = textAttributes.baseWritingDirection.has_value()
? textAttributes.baseWritingDirection
: baseWritingDirection;
lineBreakStrategy = textAttributes.lineBreakStrategy.has_value()
? textAttributes.lineBreakStrategy
: lineBreakStrategy;
// Decoration
textDecorationColor = textAttributes.textDecorationColor
? textAttributes.textDecorationColor
: textDecorationColor;
textDecorationLineType = textAttributes.textDecorationLineType.has_value()
? textAttributes.textDecorationLineType
: textDecorationLineType;
textDecorationStyle = textAttributes.textDecorationStyle.has_value()
? textAttributes.textDecorationStyle
: textDecorationStyle;
// Shadow
textShadowOffset = textAttributes.textShadowOffset.has_value()
? textAttributes.textShadowOffset.value()
: textShadowOffset;
textShadowRadius = !std::isnan(textAttributes.textShadowRadius)
? textAttributes.textShadowRadius
: textShadowRadius;
textShadowColor = textAttributes.textShadowColor
? textAttributes.textShadowColor
: textShadowColor;
// Special
isHighlighted = textAttributes.isHighlighted.has_value()
? textAttributes.isHighlighted
: isHighlighted;
// TextAttributes "inherits" the isPressable value from ancestors, so this
// only applies the current node's value for isPressable if it is truthy.
isPressable =
textAttributes.isPressable.has_value() && *textAttributes.isPressable
? textAttributes.isPressable
: isPressable;
layoutDirection = textAttributes.layoutDirection.has_value()
? textAttributes.layoutDirection
: layoutDirection;
accessibilityRole = textAttributes.accessibilityRole.has_value()
? textAttributes.accessibilityRole
: accessibilityRole;
role = textAttributes.role.has_value() ? textAttributes.role : role;
}
#pragma mark - Operators
bool TextAttributes::operator==(const TextAttributes& rhs) const {
return std::tie(
foregroundColor,
backgroundColor,
fontFamily,
fontWeight,
fontStyle,
fontVariant,
allowFontScaling,
dynamicTypeRamp,
alignment,
baseWritingDirection,
lineBreakStrategy,
textDecorationColor,
textDecorationLineType,
textDecorationStyle,
textShadowOffset,
textShadowColor,
isHighlighted,
isPressable,
layoutDirection,
accessibilityRole,
role,
textTransform) ==
std::tie(
rhs.foregroundColor,
rhs.backgroundColor,
rhs.fontFamily,
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment,
rhs.baseWritingDirection,
rhs.lineBreakStrategy,
rhs.textDecorationColor,
rhs.textDecorationLineType,
rhs.textDecorationStyle,
rhs.textShadowOffset,
rhs.textShadowColor,
rhs.isHighlighted,
rhs.isPressable,
rhs.layoutDirection,
rhs.accessibilityRole,
rhs.role,
rhs.textTransform) &&
floatEquality(opacity, rhs.opacity) &&
floatEquality(fontSize, rhs.fontSize) &&
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
floatEquality(letterSpacing, rhs.letterSpacing) &&
floatEquality(lineHeight, rhs.lineHeight) &&
floatEquality(textShadowRadius, rhs.textShadowRadius);
}
bool TextAttributes::operator!=(const TextAttributes& rhs) const {
return !(*this == rhs);
}
TextAttributes TextAttributes::defaultTextAttributes() {
static auto textAttributes = [] {
auto textAttributes = TextAttributes{};
// Non-obvious (can be different among platforms) default text attributes.
textAttributes.foregroundColor = blackColor();
textAttributes.backgroundColor = clearColor();
textAttributes.fontSize = 14.0;
textAttributes.fontSizeMultiplier = 1.0;
return textAttributes;
}();
return textAttributes;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
return {
// Color
debugStringConvertibleItem("backgroundColor", backgroundColor),
debugStringConvertibleItem("foregroundColor", foregroundColor),
debugStringConvertibleItem("opacity", opacity),
// Font
debugStringConvertibleItem("fontFamily", fontFamily),
debugStringConvertibleItem("fontSize", fontSize),
debugStringConvertibleItem("fontSizeMultiplier", fontSizeMultiplier),
debugStringConvertibleItem("fontWeight", fontWeight),
debugStringConvertibleItem("fontStyle", fontStyle),
debugStringConvertibleItem("fontVariant", fontVariant),
debugStringConvertibleItem("allowFontScaling", allowFontScaling),
debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp),
debugStringConvertibleItem("letterSpacing", letterSpacing),
// Paragraph Styles
debugStringConvertibleItem("lineHeight", lineHeight),
debugStringConvertibleItem("alignment", alignment),
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),
// Decoration
debugStringConvertibleItem("textDecorationColor", textDecorationColor),
debugStringConvertibleItem(
"textDecorationLineType", textDecorationLineType),
debugStringConvertibleItem("textDecorationStyle", textDecorationStyle),
// Shadow
debugStringConvertibleItem("textShadowOffset", textShadowOffset),
debugStringConvertibleItem("textShadowRadius", textShadowRadius),
debugStringConvertibleItem("textShadowColor", textShadowColor),
// Special
debugStringConvertibleItem("isHighlighted", isHighlighted),
debugStringConvertibleItem("isPressable", isPressable),
debugStringConvertibleItem("layoutDirection", layoutDirection),
debugStringConvertibleItem("accessibilityRole", accessibilityRole),
debugStringConvertibleItem("role", role),
};
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,141 @@
/*
* 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 <functional>
#include <limits>
#include <optional>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/components/view/AccessibilityPrimitives.h>
#include <react/renderer/core/LayoutPrimitives.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Size.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
class TextAttributes;
using SharedTextAttributes = std::shared_ptr<const TextAttributes>;
class TextAttributes : public DebugStringConvertible {
public:
/*
* Returns TextAttribute object which has actual default attribute values
* (e.g. `foregroundColor = black`), in oppose to TextAttribute's default
* constructor which creates an object with nulled attributes.
*/
static TextAttributes defaultTextAttributes();
#pragma mark - Fields
// Color
SharedColor foregroundColor{};
SharedColor backgroundColor{};
Float opacity{std::numeric_limits<Float>::quiet_NaN()};
// Font
std::string fontFamily{""};
Float fontSize{std::numeric_limits<Float>::quiet_NaN()};
Float fontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<FontWeight> fontWeight{};
std::optional<FontStyle> fontStyle{};
std::optional<FontVariant> fontVariant{};
std::optional<bool> allowFontScaling{};
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextTransform> textTransform{};
// Paragraph Styles
Float lineHeight{std::numeric_limits<Float>::quiet_NaN()};
std::optional<TextAlignment> alignment{};
std::optional<WritingDirection> baseWritingDirection{};
std::optional<LineBreakStrategy> lineBreakStrategy{};
// Decoration
SharedColor textDecorationColor{};
std::optional<TextDecorationLineType> textDecorationLineType{};
std::optional<TextDecorationStyle> textDecorationStyle{};
// Shadow
// TODO: Use `Point` type instead of `Size` for `textShadowOffset` attribute.
std::optional<Size> textShadowOffset{};
Float textShadowRadius{std::numeric_limits<Float>::quiet_NaN()};
SharedColor textShadowColor{};
// Special
std::optional<bool> isHighlighted{};
std::optional<bool> isPressable{};
// TODO T59221129: document where this value comes from and how it is set.
// It's not clear if this is being used properly, or if it's being set at all.
// Currently, it is intentionally *not* being set as part of BaseTextProps
// construction.
std::optional<LayoutDirection> layoutDirection{};
std::optional<AccessibilityRole> accessibilityRole{};
std::optional<Role> role{};
#pragma mark - Operations
void apply(TextAttributes textAttributes);
#pragma mark - Operators
bool operator==(const TextAttributes& rhs) const;
bool operator!=(const TextAttributes& rhs) const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::TextAttributes> {
size_t operator()(
const facebook::react::TextAttributes& textAttributes) const {
return facebook::react::hash_combine(
textAttributes.foregroundColor,
textAttributes.backgroundColor,
textAttributes.opacity,
textAttributes.fontFamily,
textAttributes.fontSize,
textAttributes.fontSizeMultiplier,
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.allowFontScaling,
textAttributes.letterSpacing,
textAttributes.textTransform,
textAttributes.lineHeight,
textAttributes.alignment,
textAttributes.baseWritingDirection,
textAttributes.lineBreakStrategy,
textAttributes.textDecorationColor,
textAttributes.textDecorationLineType,
textAttributes.textDecorationStyle,
textAttributes.textShadowOffset,
textAttributes.textShadowRadius,
textAttributes.textShadowColor,
textAttributes.isHighlighted,
textAttributes.isPressable,
textAttributes.layoutDirection,
textAttributes.accessibilityRole,
textAttributes.role);
}
};
} // namespace std

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
/*
* 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 <functional>
#include <limits>
namespace facebook::react {
enum class FontStyle { Normal, Italic, Oblique };
enum class FontWeight : int {
Weight100 = 100,
UltraLight = 100,
Weight200 = 200,
Thin = 200,
Weight300 = 300,
Light = 300,
Weight400 = 400,
Regular = 400,
Weight500 = 500,
Medium = 500,
Weight600 = 600,
Semibold = 600,
Demibold = 600,
Weight700 = 700,
Bold = 700,
Weight800 = 800,
Heavy = 800,
Weight900 = 900,
Black = 900
};
enum class FontVariant : int {
Default = 0,
SmallCaps = 1 << 1,
OldstyleNums = 1 << 2,
LiningNums = 1 << 3,
TabularNums = 1 << 4,
ProportionalNums = 1 << 5
};
enum class DynamicTypeRamp {
Caption2,
Caption1,
Footnote,
Subheadline,
Callout,
Body,
Headline,
Title3,
Title2,
Title1,
LargeTitle
};
enum class EllipsizeMode {
Clip, // Do not add ellipsize, simply clip.
Head, // Truncate at head of line: "...wxyz".
Tail, // Truncate at tail of line: "abcd...".
Middle // Truncate middle of line: "ab...yz".
};
enum class TextBreakStrategy {
Simple, // Simple strategy.
HighQuality, // High-quality strategy, including hyphenation.
Balanced // Balances line lengths.
};
enum class TextAlignment {
Natural, // Indicates the default alignment for script.
Left, // Visually left aligned.
Center, // Visually centered.
Right, // Visually right aligned.
Justified // Fully-justified. The last line in a paragraph is natural-aligned.
};
enum class WritingDirection {
Natural, // Determines direction using the Unicode Bidi Algorithm rules P2 and
// P3.
LeftToRight, // Left to right writing direction.
RightToLeft // Right to left writing direction.
};
enum class LineBreakStrategy {
None, // Don't use any line break strategies
PushOut, // Use the push out line break strategy.
HangulWordPriority, // When specified, it prohibits breaking between Hangul
// characters.
Standard // Use the same configuration of line break strategies that the
// system uses for standard UI labels.
};
enum class TextDecorationLineType {
None,
Underline,
Strikethrough,
UnderlineStrikethrough
};
enum class TextDecorationStyle { Solid, Double, Dotted, Dashed };
enum class TextTransform {
None,
Uppercase,
Lowercase,
Capitalize,
Unset,
};
enum class HyphenationFrequency {
None, // No hyphenation.
Normal, // Less frequent hyphenation.
Full // Standard amount of hyphenation.
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
namespace facebook::react {
TEST(AttributedStringBoxTest, testDefaultConstructor) {
auto attributedStringBox = AttributedStringBox{};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), AttributedString{});
}
TEST(AttributedStringBoxTest, testValueConstructor) {
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(fragment);
auto attributedStringBox = AttributedStringBox{attributedString};
EXPECT_EQ(attributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(attributedStringBox.getValue(), attributedString);
}
TEST(AttributedStringBoxTest, testOpaquePointerConstructor) {
auto string = std::make_shared<std::string>("test string");
auto attributedStringBox = AttributedStringBox{string};
EXPECT_EQ(
attributedStringBox.getMode(), AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(attributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
TEST(AttributedStringBoxTest, testMoveConstructor) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(moveToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(fragment);
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox =
AttributedStringBox{std::move(movedFromAttributedStringBox)};
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
TEST(AttributedStringBoxTest, testMoveAssignment) {
{
auto string = std::make_shared<std::string>("test string");
auto movedFromAttributedStringBox = AttributedStringBox{string};
auto movedToAttributedStringBox = AttributedStringBox{};
movedToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
movedToAttributedStringBox.getMode(),
AttributedStringBox::Mode::OpaquePointer);
EXPECT_EQ(movedToAttributedStringBox.getOpaquePointer(), string);
EXPECT_EQ(string.use_count(), 2);
}
{
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test string";
attributedString.appendFragment(fragment);
auto movedFromAttributedStringBox = AttributedStringBox{attributedString};
auto moveToAttributedStringBox = AttributedStringBox{};
moveToAttributedStringBox = std::move(movedFromAttributedStringBox);
EXPECT_EQ(
moveToAttributedStringBox.getMode(), AttributedStringBox::Mode::Value);
EXPECT_EQ(moveToAttributedStringBox.getValue(), attributedString);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/core/graphicsConversions.h>
namespace facebook::react {
#ifdef ANDROID
TEST(AttributedStringTest, testToDynamic) {
auto attributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = "test";
auto text = TextAttributes{};
text.foregroundColor = {
colorFromComponents({100 / 255.0, 153 / 255.0, 200 / 255.0, 1.0})};
text.opacity = 0.5;
text.fontStyle = FontStyle::Italic;
text.fontWeight = FontWeight::Thin;
text.fontVariant = FontVariant::TabularNums;
fragment.textAttributes = text;
attributedString.appendFragment(fragment);
auto result = toDynamic(attributedString);
EXPECT_EQ(result["string"], fragment.string);
auto textAttribute = result["fragments"][0]["textAttributes"];
EXPECT_EQ(textAttribute["foregroundColor"], toDynamic(text.foregroundColor));
EXPECT_EQ(textAttribute["opacity"], text.opacity);
EXPECT_EQ(textAttribute["fontStyle"], toString(text.fontStyle.value()));
EXPECT_EQ(textAttribute["fontWeight"], toString(text.fontWeight.value()));
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* 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/attributedstring/ParagraphAttributes.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
namespace facebook::react {
#ifdef ANDROID
TEST(ParagraphAttributesTest, testToDynamic) {
auto paragraphAttributes = ParagraphAttributes{};
paragraphAttributes.maximumNumberOfLines = 2;
paragraphAttributes.adjustsFontSizeToFit = false;
paragraphAttributes.ellipsizeMode = EllipsizeMode::Middle;
auto result = toDynamic(paragraphAttributes);
EXPECT_EQ(
result["maximumNumberOfLines"], paragraphAttributes.maximumNumberOfLines);
EXPECT_EQ(
result["adjustsFontSizeToFit"], paragraphAttributes.adjustsFontSizeToFit);
EXPECT_EQ(
result["ellipsizeMode"], toString(paragraphAttributes.ellipsizeMode));
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* 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/attributedstring/TextAttributes.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/attributedstring/primitives.h>
#include <react/renderer/core/graphicsConversions.h>
namespace facebook::react {
#ifdef ANDROID
TEST(TextAttributesTest, testToDynamic) {
auto textAttributes = TextAttributes{};
textAttributes.foregroundColor = {
colorFromComponents({200 / 255.0, 153 / 255.0, 100 / 255.0, 1.0})};
textAttributes.opacity = 0.5;
textAttributes.fontStyle = FontStyle::Italic;
textAttributes.fontWeight = FontWeight::Thin;
textAttributes.fontVariant = FontVariant::TabularNums;
auto result = toDynamic(textAttributes);
EXPECT_EQ(
result["foregroundColor"], toDynamic(textAttributes.foregroundColor));
EXPECT_EQ(result["opacity"], textAttributes.opacity);
EXPECT_EQ(result["fontStyle"], toString(textAttributes.fontStyle.value()));
EXPECT_EQ(result["fontWeight"], toString(textAttributes.fontWeight.value()));
}
#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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_componentregistry_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_componentregistry SHARED ${react_render_componentregistry_SRC})
target_include_directories(react_render_componentregistry PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_componentregistry
folly_runtime
glog_init
jsi
react_config
react_debug
react_render_core
react_render_debug
react_utils
rrc_legacyviewmanagerinterop
)

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 <memory>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/core/EventDispatcher.h>
#include <react/utils/ContextContainer.h>
#include "ComponentDescriptorRegistry.h"
namespace facebook::react {
/**
* A factory to provide hosting app specific set of ComponentDescriptor's.
* Each app must provide an implementation of the static class method which
* should register its specific set of supported components.
*/
using ComponentRegistryFactory =
std::function<SharedComponentDescriptorRegistry(
const EventDispatcher::Weak& eventDispatcher,
const ContextContainer::Shared& contextContainer)>;
ComponentRegistryFactory getDefaultComponentRegistryFactory();
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/core/EventDispatcher.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Callable signature that represents the signature of `ComponentDescriptor`
* constructor. The callable returns a unique pointer conveniently represents an
* abstract type and ownership of the newly created object.
*/
using ComponentDescriptorConstructor = ComponentDescriptor::Unique(
const ComponentDescriptorParameters& parameters);
/*
* Represents a unified way to construct an instance of a particular stored
* `ComponentDescriptor` class. C++ does not allow to create pointers to
* constructors, so we have to have such data structure to manipulate a
* collection of classes.
*
* Note: The actual values of `handle` and `name` for some components depend on
* `flavor`. The provider is valid if instantiated by `constructor` object with
* given `flavor` exposes the same values of `handle` and `name`.
*/
class ComponentDescriptorProvider final {
public:
ComponentHandle handle;
ComponentName name;
ComponentDescriptor::Flavor flavor;
ComponentDescriptorConstructor* constructor;
};
/*
* Creates a `ComponentDescriptor` for given `ComponentDescriptorParameters`.
*/
template <typename ComponentDescriptorT>
ComponentDescriptor::Unique concreteComponentDescriptorConstructor(
const ComponentDescriptorParameters& parameters) {
static_assert(
std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
"ComponentDescriptorT must be a descendant of ComponentDescriptor");
return std::make_unique<const ComponentDescriptorT>(parameters);
}
/*
* Creates a `ComponentDescriptorProvider` for given `ComponentDescriptor`
* class.
*/
template <typename ComponentDescriptorT>
ComponentDescriptorProvider concreteComponentDescriptorProvider() {
static_assert(
std::is_base_of<ComponentDescriptor, ComponentDescriptorT>::value,
"ComponentDescriptorT must be a descendant of ComponentDescriptor");
return {
ComponentDescriptorT::ConcreteShadowNode::Handle(),
ComponentDescriptorT::ConcreteShadowNode::Name(),
nullptr,
&concreteComponentDescriptorConstructor<ComponentDescriptorT>};
}
} // namespace facebook::react

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ComponentDescriptorProviderRegistry.h"
namespace facebook::react {
void ComponentDescriptorProviderRegistry::add(
const ComponentDescriptorProvider& provider) const {
std::unique_lock lock(mutex_);
/*
// TODO: T57583139
The assert is temporarily disabled to reduce the volume of the signal.
assert(
componentDescriptorProviders_.find(provider.handle) ==
componentDescriptorProviders_.end() &&
"Attempt to register an already registered ComponentDescriptorProvider.");
*/
if (componentDescriptorProviders_.find(provider.handle) !=
componentDescriptorProviders_.end()) {
// Re-registering a provider makes no sense because it's copyable: already
// registered one is as good as any new can be.
return;
}
componentDescriptorProviders_.insert({provider.handle, provider});
for (const auto& weakRegistry : componentDescriptorRegistries_) {
auto registry = weakRegistry.lock();
if (!registry) {
continue;
}
registry->add(provider);
}
}
void ComponentDescriptorProviderRegistry::setComponentDescriptorProviderRequest(
ComponentDescriptorProviderRequest componentDescriptorProviderRequest)
const {
std::shared_lock lock(mutex_);
componentDescriptorProviderRequest_ =
std::move(componentDescriptorProviderRequest);
}
void ComponentDescriptorProviderRegistry::request(
ComponentName componentName) const {
ComponentDescriptorProviderRequest componentDescriptorProviderRequest;
{
std::shared_lock lock(mutex_);
componentDescriptorProviderRequest = componentDescriptorProviderRequest_;
}
if (componentDescriptorProviderRequest) {
componentDescriptorProviderRequest(componentName);
}
}
ComponentDescriptorRegistry::Shared
ComponentDescriptorProviderRegistry::createComponentDescriptorRegistry(
const ComponentDescriptorParameters& parameters) const {
std::shared_lock lock(mutex_);
auto registry = std::make_shared<const ComponentDescriptorRegistry>(
parameters, *this, parameters.contextContainer);
for (const auto& pair : componentDescriptorProviders_) {
registry->add(pair.second);
}
componentDescriptorRegistries_.push_back(registry);
return registry;
}
} // 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 <shared_mutex>
#include <unordered_map>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/core/ComponentDescriptor.h>
namespace facebook::react {
using ComponentDescriptorProviderRequest =
std::function<void(ComponentName componentName)>;
/*
* Registry of `ComponentDescriptorProvider`s (and managed
* `ComponentDescriptorRegistry`s). The class maintains a list of
* `ComponentDescriptorRegistry`s (retaining pointers weakly) and update them
* accordingly to changes in the provider registry.
*/
class ComponentDescriptorProviderRegistry final {
public:
/*
* Adds a `ComponentDescriptorProvider`s and update the managed
* `ComponentDescriptorRegistry`s accordingly.
* The methods can be called on any thread.
*/
void add(const ComponentDescriptorProvider& provider) const;
/*
* ComponenDescriptorRegistry will call the `request` in case if a component
* with given name wasn't registered yet.
* The request handler must register a ComponentDescriptor with requested name
* synchronously during handling the request.
* The request can be called on any thread.
* The methods can be called on any thread.
*/
void setComponentDescriptorProviderRequest(
ComponentDescriptorProviderRequest request) const;
/*
* Creates managed `ComponentDescriptorRegistry` based on a stored list of
* `ComponentDescriptorProvider`s and given `ComponentDescriptorParameters`.
* The methods can be called on any thread.
*/
ComponentDescriptorRegistry::Shared createComponentDescriptorRegistry(
const ComponentDescriptorParameters& parameters) const;
private:
friend class ComponentDescriptorRegistry;
void request(ComponentName componentName) const;
mutable std::shared_mutex mutex_;
mutable std::vector<std::weak_ptr<const ComponentDescriptorRegistry>>
componentDescriptorRegistries_;
mutable std::unordered_map<ComponentHandle, ComponentDescriptorProvider const>
componentDescriptorProviders_;
mutable ComponentDescriptorProviderRequest
componentDescriptorProviderRequest_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,148 @@
/*
* 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 "ComponentDescriptorRegistry.h"
#include "componentNameByReactViewName.h"
#include <react/config/ReactNativeConfig.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticComponentDescriptor.h>
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <utility>
namespace facebook::react {
ComponentDescriptorRegistry::ComponentDescriptorRegistry(
ComponentDescriptorParameters parameters,
const ComponentDescriptorProviderRegistry& providerRegistry,
ContextContainer::Shared contextContainer)
: parameters_(std::move(parameters)),
providerRegistry_(providerRegistry),
contextContainer_(std::move(contextContainer)) {}
void ComponentDescriptorRegistry::add(
ComponentDescriptorProvider componentDescriptorProvider) const {
std::unique_lock lock(mutex_);
auto componentDescriptor = componentDescriptorProvider.constructor(
{parameters_.eventDispatcher,
parameters_.contextContainer,
componentDescriptorProvider.flavor});
react_native_assert(
componentDescriptor->getComponentHandle() ==
componentDescriptorProvider.handle);
react_native_assert(
componentDescriptor->getComponentName() ==
componentDescriptorProvider.name);
auto sharedComponentDescriptor = std::shared_ptr<const ComponentDescriptor>(
std::move(componentDescriptor));
_registryByHandle[componentDescriptorProvider.handle] =
sharedComponentDescriptor;
_registryByName[componentDescriptorProvider.name] = sharedComponentDescriptor;
}
void ComponentDescriptorRegistry::registerComponentDescriptor(
const SharedComponentDescriptor& componentDescriptor) const {
ComponentHandle componentHandle = componentDescriptor->getComponentHandle();
_registryByHandle[componentHandle] = componentDescriptor;
ComponentName componentName = componentDescriptor->getComponentName();
_registryByName[componentName] = componentDescriptor;
}
const ComponentDescriptor& ComponentDescriptorRegistry::at(
const std::string& componentName) const {
std::shared_lock lock(mutex_);
auto unifiedComponentName = componentNameByReactViewName(componentName);
auto it = _registryByName.find(unifiedComponentName);
if (it == _registryByName.end()) {
lock.unlock();
providerRegistry_.request(unifiedComponentName.c_str());
lock.lock();
it = _registryByName.find(unifiedComponentName);
/*
* TODO: T54849676
* Uncomment the `assert` after the following block that checks
* `_fallbackComponentDescriptor` is no longer needed. The assert assumes
* that `componentDescriptorProviderRequest` is always not null and register
* some component on every single request.
*/
// assert(it != _registryByName.end());
}
if (it == _registryByName.end()) {
auto reactNativeConfig_ =
contextContainer_->at<std::shared_ptr<const ReactNativeConfig>>(
"ReactNativeConfig");
if (reactNativeConfig_->getBool(
"react_fabric:enabled_automatic_interop_android")) {
auto componentDescriptor = std::make_shared<
const UnstableLegacyViewManagerAutomaticComponentDescriptor>(
parameters_, unifiedComponentName);
registerComponentDescriptor(componentDescriptor);
return *_registryByName.find(unifiedComponentName)->second;
} else if (_fallbackComponentDescriptor == nullptr) {
throw std::invalid_argument(
("Unable to find componentDescriptor for " + unifiedComponentName)
.c_str());
} else {
return *_fallbackComponentDescriptor.get();
}
}
return *it->second;
}
const ComponentDescriptor* ComponentDescriptorRegistry::
findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
auto iterator = _registryByHandle.find(componentHandle);
if (iterator == _registryByHandle.end()) {
return nullptr;
}
return iterator->second.get();
}
const ComponentDescriptor& ComponentDescriptorRegistry::at(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
return *_registryByHandle.at(componentHandle);
}
bool ComponentDescriptorRegistry::hasComponentDescriptorAt(
ComponentHandle componentHandle) const {
std::shared_lock lock(mutex_);
auto iterator = _registryByHandle.find(componentHandle);
return iterator != _registryByHandle.end();
}
void ComponentDescriptorRegistry::setFallbackComponentDescriptor(
const SharedComponentDescriptor& descriptor) {
_fallbackComponentDescriptor = descriptor;
registerComponentDescriptor(descriptor);
}
ComponentDescriptor::Shared
ComponentDescriptorRegistry::getFallbackComponentDescriptor() const {
return _fallbackComponentDescriptor;
}
} // namespace facebook::react

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <shared_mutex>
#include <unordered_map>
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/core/InstanceHandle.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class ComponentDescriptorProviderRegistry;
class ComponentDescriptorRegistry;
using SharedComponentDescriptorRegistry =
std::shared_ptr<const ComponentDescriptorRegistry>;
/*
* Registry of particular `ComponentDescriptor`s.
*/
class ComponentDescriptorRegistry {
public:
using Shared = std::shared_ptr<const ComponentDescriptorRegistry>;
/*
* Creates an object with stored `ComponentDescriptorParameters` which will
* be used later to create `ComponentDescriptor`s.
*/
ComponentDescriptorRegistry(
ComponentDescriptorParameters parameters,
const ComponentDescriptorProviderRegistry& providerRegistry,
ContextContainer::Shared contextContainer);
/*
* This is broken. Please do not use.
* If you requesting a ComponentDescriptor and unsure that it's there, you are
* doing something wrong.
*/
const ComponentDescriptor*
findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
ComponentHandle componentHandle) const;
const ComponentDescriptor& at(const std::string& componentName) const;
const ComponentDescriptor& at(ComponentHandle componentHandle) const;
bool hasComponentDescriptorAt(ComponentHandle componentHandle) const;
void setFallbackComponentDescriptor(
const SharedComponentDescriptor& descriptor);
ComponentDescriptor::Shared getFallbackComponentDescriptor() const;
private:
friend class ComponentDescriptorProviderRegistry;
void registerComponentDescriptor(
const SharedComponentDescriptor& componentDescriptor) const;
/*
* Creates a `ComponentDescriptor` using specified
* `ComponentDescriptorProvider` and stored `ComponentDescriptorParameters`,
* and then adds that to the registry.
* To be used by `ComponentDescriptorProviderRegistry` only.
* Thread safe.
*/
void add(ComponentDescriptorProvider componentDescriptorProvider) const;
mutable std::shared_mutex mutex_;
mutable std::unordered_map<ComponentHandle, SharedComponentDescriptor>
_registryByHandle;
mutable std::unordered_map<std::string, SharedComponentDescriptor>
_registryByName;
ComponentDescriptor::Shared _fallbackComponentDescriptor;
ComponentDescriptorParameters parameters_{};
const ComponentDescriptorProviderRegistry& providerRegistry_;
ContextContainer::Shared contextContainer_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "componentNameByReactViewName.h"
#include <algorithm>
namespace facebook::react {
std::string componentNameByReactViewName(std::string viewName) {
// We need this function only for the transition period;
// eventually, all names will be unified.
// TODO T97384889: unify component names between JS - Android - iOS - C++
std::string rctPrefix("RCT");
if (std::mismatch(rctPrefix.begin(), rctPrefix.end(), viewName.begin())
.first == rctPrefix.end()) {
// If `viewName` has "RCT" prefix, remove it.
viewName.erase(0, rctPrefix.length());
}
// Fabric uses slightly new names for Text components because of differences
// in semantic.
if (viewName == "Text") {
return "Paragraph";
}
// TODO T63839307: remove this condition after deleting TextInlineImage from
// old renderer code
if (viewName == "TextInlineImage") {
return "Image";
}
if (viewName == "VirtualText") {
return "Text";
}
if (viewName == "ImageView") {
return "Image";
}
if (viewName == "AndroidHorizontalScrollView") {
return "ScrollView";
}
if (viewName == "RKShimmeringView") {
return "ShimmeringView";
}
if (viewName == "RefreshControl") {
return "PullToRefreshView";
}
// We need this temporarily for testing purposes until we have proper
// implementation of core components.
// iOS-only
if (viewName == "ScrollContentView") {
return "View";
}
// iOS-only
if (viewName == "MultilineTextInputView" ||
viewName == "SinglelineTextInputView") {
return "TextInput";
}
return viewName;
}
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
namespace facebook::react {
/**
* Provides mapping from old view name format to the new format.
*/
std::string componentNameByReactViewName(std::string viewName);
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_native_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_native STATIC ${rrc_native_SRC})
target_include_directories(rrc_native PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_native
folly_runtime
glog_init
jsi
react_debug
react_render_core
react_render_debug
react_utils
callinvoker
)

View File

@@ -0,0 +1,28 @@
/*
* 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 "NativeComponentRegistryBinding.h"
#include <react/bridging/Bridging.h>
#include <stdexcept>
#include <string>
namespace facebook::react {
/**
* Public API to install the Native Component Registry bindings.
*/
void bindHasComponentProvider(
jsi::Runtime& runtime,
HasComponentProviderFunctionType&& provider) {
runtime.global().setProperty(
runtime,
"__nativeComponentRegistry__hasComponent",
bridging::toJs(runtime, provider, {}));
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <jsi/jsi.h>
namespace facebook::react {
/**
* An app/platform-specific provider function to determine if a component
* is registered in the native platform.
*/
using HasComponentProviderFunctionType =
std::function<bool(const std::string& name)>;
/*
* Installs HasComponentProviderFunction into JavaScript runtime.
* Thread synchronization must be enforced externally.
*/
void bindHasComponentProvider(
jsi::Runtime& runtime,
HasComponentProviderFunctionType&& provider);
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_image_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_image SHARED ${rrc_image_SRC})
target_include_directories(rrc_image PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_image
glog
folly_runtime
glog_init
jsi
react_debug
react_utils
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
rrc_view
yoga
)

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/image/ImageShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/imagemanager/ImageManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Descriptor for <Image> component.
*/
class ImageComponentDescriptor final
: public ConcreteComponentDescriptor<ImageShadowNode> {
public:
ImageComponentDescriptor(const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
imageManager_(std::make_shared<ImageManager>(contextContainer_)){};
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& imageShadowNode = static_cast<ImageShadowNode&>(shadowNode);
// `ImageShadowNode` uses `ImageManager` to initiate image loading and
// communicate the loading state and results to mounting layer.
imageShadowNode.setImageManager(imageManager_);
}
private:
const SharedImageManager imageManager_;
};
} // 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.
*/
#include "ImageEventEmitter.h"
namespace facebook::react {
void ImageEventEmitter::onLoadStart() const {
dispatchEvent("loadStart");
}
void ImageEventEmitter::onLoad() const {
dispatchEvent("load");
}
void ImageEventEmitter::onLoadEnd() const {
dispatchEvent("loadEnd");
}
void ImageEventEmitter::onProgress(double progress) const {
dispatchEvent("progress", [=](jsi::Runtime& runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "progress", progress);
return payload;
});
}
void ImageEventEmitter::onError() const {
dispatchEvent("error");
}
void ImageEventEmitter::onPartialLoad() const {
dispatchEvent("partialLoad");
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
namespace facebook::react {
class ImageEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onLoadStart() const;
void onLoad() const;
void onLoadEnd() const;
void onProgress(double) const;
void onError() const;
void onPartialLoad() const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/image/ImageProps.h>
#include <react/renderer/components/image/conversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/utils/CoreFeatures.h>
namespace facebook::react {
ImageProps::ImageProps(
const PropsParserContext& context,
const ImageProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
sources(
CoreFeatures::enablePropIteratorSetter ? sourceProps.sources
: convertRawProp(
context,
rawProps,
"source",
sourceProps.sources,
{})),
defaultSources(
CoreFeatures::enablePropIteratorSetter
? sourceProps.defaultSources
: convertRawProp(
context,
rawProps,
"defaultSource",
sourceProps.defaultSources,
{})),
resizeMode(
CoreFeatures::enablePropIteratorSetter
? sourceProps.resizeMode
: convertRawProp(
context,
rawProps,
"resizeMode",
sourceProps.resizeMode,
ImageResizeMode::Stretch)),
blurRadius(
CoreFeatures::enablePropIteratorSetter ? sourceProps.blurRadius
: convertRawProp(
context,
rawProps,
"blurRadius",
sourceProps.blurRadius,
{})),
capInsets(
CoreFeatures::enablePropIteratorSetter ? sourceProps.capInsets
: convertRawProp(
context,
rawProps,
"capInsets",
sourceProps.capInsets,
{})),
tintColor(
CoreFeatures::enablePropIteratorSetter ? sourceProps.tintColor
: convertRawProp(
context,
rawProps,
"tintColor",
sourceProps.tintColor,
{})),
internal_analyticTag(
CoreFeatures::enablePropIteratorSetter
? sourceProps.internal_analyticTag
: convertRawProp(
context,
rawProps,
"internal_analyticTag",
sourceProps.internal_analyticTag,
{})) {}
void ImageProps::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.
ViewProps::setProp(context, hash, propName, value);
static auto defaults = ImageProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE(sources, "source");
RAW_SET_PROP_SWITCH_CASE(defaultSources, "defaultSource");
RAW_SET_PROP_SWITCH_CASE_BASIC(resizeMode);
RAW_SET_PROP_SWITCH_CASE_BASIC(blurRadius);
RAW_SET_PROP_SWITCH_CASE_BASIC(capInsets);
RAW_SET_PROP_SWITCH_CASE_BASIC(tintColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(internal_analyticTag);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
// TODO (T28334063): Consider for codegen.
class ImageProps final : public ViewProps {
public:
ImageProps() = default;
ImageProps(
const PropsParserContext& context,
const ImageProps& sourceProps,
const RawProps& rawProps);
void setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value);
#pragma mark - Props
ImageSources sources{};
ImageSources defaultSources{};
ImageResizeMode resizeMode{ImageResizeMode::Stretch};
Float blurRadius{};
EdgeInsets capInsets{};
SharedColor tintColor{};
std::string internal_analyticTag{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cstdlib>
#include <limits>
#include <react/renderer/components/image/ImageShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include "ImageState.h"
namespace facebook::react {
const char ImageComponentName[] = "Image";
void ImageShadowNode::setImageManager(const SharedImageManager& imageManager) {
ensureUnsealed();
imageManager_ = imageManager;
}
void ImageShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto imageSource = getImageSource();
const auto& currentState = getStateData();
bool hasSameRadius =
getConcreteProps().blurRadius == currentState.getBlurRadius();
bool hasSameImageSource = currentState.getImageSource() == imageSource;
if (hasSameImageSource && hasSameRadius) {
return;
}
auto state = ImageState{
imageSource,
imageManager_->requestImage(imageSource, getSurfaceId()),
getConcreteProps().blurRadius};
setStateData(std::move(state));
}
ImageSource ImageShadowNode::getImageSource() const {
auto sources = getConcreteProps().sources;
if (sources.empty()) {
return {
/* .type = */ ImageSource::Type::Invalid,
};
}
auto layoutMetrics = getLayoutMetrics();
auto size = layoutMetrics.getContentFrame().size;
auto scale = layoutMetrics.pointScaleFactor;
if (sources.size() == 1) {
auto source = sources[0];
source.size = size;
source.scale = scale;
return source;
}
auto targetImageArea = size.width * size.height * scale * scale;
auto bestFit = std::numeric_limits<Float>::infinity();
auto bestSource = ImageSource{};
for (const auto& source : sources) {
auto sourceSize = source.size;
auto sourceScale = source.scale == 0 ? scale : source.scale;
auto sourceArea =
sourceSize.width * sourceSize.height * sourceScale * sourceScale;
auto fit = std::abs(1 - (sourceArea / targetImageArea));
if (fit < bestFit) {
bestFit = fit;
bestSource = source;
}
}
bestSource.size = size;
bestSource.scale = scale;
return bestSource;
}
#pragma mark - LayoutableShadowNode
void ImageShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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/image/ImageEventEmitter.h>
#include <react/renderer/components/image/ImageProps.h>
#include <react/renderer/components/image/ImageState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/ShadowNodeFamily.h>
#include <react/renderer/imagemanager/ImageManager.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
extern const char ImageComponentName[];
/*
* `ShadowNode` for <Image> component.
*/
class ImageShadowNode final : public ConcreteViewShadowNode<
ImageComponentName,
ImageProps,
ImageEventEmitter,
ImageState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
return traits;
}
/*
* Associates a shared `ImageManager` with the node.
*/
void setImageManager(const SharedImageManager& imageManager);
static ImageState initialStateData(
const Props::Shared& props,
const ShadowNodeFamily::Shared& /*family*/,
const ComponentDescriptor& componentDescriptor) {
auto imageSource = ImageSource{ImageSource::Type::Invalid};
return {imageSource, {imageSource, nullptr, {}}, 0};
}
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
private:
ImageSource getImageSource() const;
SharedImageManager imageManager_;
void updateStateIfNeeded();
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* 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 "ImageState.h"
namespace facebook::react {
ImageSource ImageState::getImageSource() const {
return imageSource_;
}
const ImageRequest& ImageState::getImageRequest() const {
return *imageRequest_;
}
Float ImageState::getBlurRadius() const {
return blurRadius_;
}
} // 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/imagemanager/ImageRequest.h>
#include <react/renderer/imagemanager/primitives.h>
#ifdef ANDROID
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
/*
* State for <Image> component.
*/
class ImageState final {
public:
ImageState(
const ImageSource& imageSource,
ImageRequest imageRequest,
const Float blurRadius)
: imageSource_(imageSource),
imageRequest_(std::make_shared<ImageRequest>(std::move(imageRequest))),
blurRadius_(blurRadius){};
/*
* Returns stored ImageSource object.
*/
ImageSource getImageSource() const;
/*
* Exposes for reading stored `ImageRequest` object.
* `ImageRequest` object cannot be copied or moved from `ImageLocalData`.
*/
const ImageRequest& getImageRequest() const;
Float getBlurRadius() const;
#ifdef ANDROID
ImageState(const ImageState& previousState, folly::dynamic data)
: blurRadius_{0} {};
/*
* Empty implementation for Android because it doesn't use this class.
*/
folly::dynamic getDynamic() const {
return {};
};
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
private:
ImageSource imageSource_;
std::shared_ptr<ImageRequest> imageRequest_;
const Float blurRadius_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,142 @@
/*
* 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 <unordered_map>
#include <folly/dynamic.h>
#include <glog/logging.h>
#include <react/debug/react_native_expect.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/imagemanager/primitives.h>
namespace facebook::react {
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ImageSource& result) {
if (value.hasType<std::string>()) {
result = {
/* .type = */ ImageSource::Type::Remote,
/* .uri = */ (std::string)value,
};
return;
}
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
auto items = (std::unordered_map<std::string, RawValue>)value;
result = {};
result.type = ImageSource::Type::Remote;
if (items.find("__packager_asset") != items.end()) {
result.type = ImageSource::Type::Local;
}
if (items.find("width") != items.end() &&
items.find("height") != items.end() &&
// The following checks have to be removed after codegen is shipped.
// See T45151459.
items.at("width").hasType<Float>() &&
items.at("height").hasType<Float>()) {
result.size = {(Float)items.at("width"), (Float)items.at("height")};
}
if (items.find("scale") != items.end() &&
// The following checks have to be removed after codegen is shipped.
// See T45151459.
items.at("scale").hasType<Float>()) {
result.scale = (Float)items.at("scale");
} else {
result.scale = items.find("deprecated") != items.end() ? 0.0f : 1.0f;
}
if (items.find("url") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("url").hasType<std::string>()) {
result.uri = (std::string)items.at("url");
}
if (items.find("uri") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("uri").hasType<std::string>()) {
result.uri = (std::string)items.at("uri");
}
if (items.find("bundle") != items.end() &&
// The following should be removed after codegen is shipped.
// See T45151459.
items.at("bundle").hasType<std::string>()) {
result.bundle = (std::string)items.at("bundle");
result.type = ImageSource::Type::Local;
}
return;
}
// The following should be removed after codegen is shipped.
// See T45151459.
result = {};
result.type = ImageSource::Type::Invalid;
}
inline std::string toString(const ImageSource& value) {
return "{uri: " + value.uri + "}";
}
inline void fromRawValue(
const PropsParserContext& context,
const RawValue& value,
ImageResizeMode& result) {
react_native_expect(value.hasType<std::string>());
if (!value.hasType<std::string>()) {
LOG(ERROR) << "Unsupported ImageResizeMode type";
// "cover" is default in non-Fabric web and iOS
result = ImageResizeMode::Cover;
return;
}
auto stringValue = (std::string)value;
if (stringValue == "cover") {
result = ImageResizeMode::Cover;
} else if (stringValue == "contain") {
result = ImageResizeMode::Contain;
} else if (stringValue == "stretch") {
result = ImageResizeMode::Stretch;
} else if (stringValue == "center") {
result = ImageResizeMode::Center;
} else if (stringValue == "repeat") {
result = ImageResizeMode::Repeat;
} else {
LOG(ERROR) << "Unsupported ImageResizeMode value: " << stringValue;
react_native_expect(false);
// "cover" is default in non-Fabric web and iOS
result = ImageResizeMode::Cover;
}
}
inline std::string toString(const ImageResizeMode& value) {
switch (value) {
case ImageResizeMode::Cover:
return "cover";
case ImageResizeMode::Contain:
return "contain";
case ImageResizeMode::Stretch:
return "stretch";
case ImageResizeMode::Center:
return "center";
case ImageResizeMode::Repeat:
return "repeat";
}
}
} // 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.
*/
#include <memory>
#include <gtest/gtest.h>
TEST(ImageTest, testSomething) {
// TODO
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/inputaccessory/InputAccessoryShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <InputAccessoryView> component.
*/
class InputAccessoryComponentDescriptor final
: public ConcreteComponentDescriptor<InputAccessoryShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData =
static_cast<const InputAccessoryShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setSize(
Size{stateData.viewportSize.width, stateData.viewportSize.height});
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // 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.
*/
#include "InputAccessoryShadowNode.h"
namespace facebook::react {
extern const char InputAccessoryComponentName[] = "InputAccessoryView";
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* 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/inputaccessory/InputAccessoryState.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char InputAccessoryComponentName[];
/*
* `ShadowNode` for <InputAccessory> component.
*/
class InputAccessoryShadowNode final : public ConcreteViewShadowNode<
InputAccessoryComponentName,
InputAccessoryProps,
InputAccessoryEventEmitter,
InputAccessoryState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/graphics/Float.h>
namespace facebook::react {
/*
* State for <InputAccessoryView> component.
*/
class InputAccessoryState final {
public:
InputAccessoryState(){};
InputAccessoryState(Size viewportSize_) : viewportSize(viewportSize_){};
const Size viewportSize{};
};
} // 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_legacyviewmanagerinterop_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_legacyviewmanagerinterop SHARED ${rrc_legacyviewmanagerinterop_SRC})
target_include_directories(rrc_legacyviewmanagerinterop PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_legacyviewmanagerinterop
glog
glog_init
folly_runtime
jsi
react_render_core
rrc_view
yoga
)

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
class LegacyViewManagerInteropComponentDescriptor final
: public ConcreteComponentDescriptor<LegacyViewManagerInteropShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
LegacyViewManagerInteropComponentDescriptor(
const ComponentDescriptorParameters& parameters);
/*
* Returns `name` and `handle` based on a `flavor`, not on static data from
* `LegacyViewManagerInteropShadowNode`.
*/
ComponentHandle getComponentHandle() const override;
ComponentName getComponentName() const override;
protected:
void adopt(ShadowNode& shadowNode) const override;
private:
const std::shared_ptr<void> _coordinator;
};
} // namespace facebook::react

View File

@@ -0,0 +1,165 @@
/*
* 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 "LegacyViewManagerInteropComponentDescriptor.h"
#include <React/RCTBridge.h>
#include <React/RCTBridgeModuleDecorator.h>
#include <React/RCTBridgeProxy.h>
#include <React/RCTComponentData.h>
#include <React/RCTEventDispatcher.h>
#include <React/RCTModuleData.h>
#include <react/utils/ContextContainer.h>
#include <react/utils/ManagedObjectWrapper.h>
#include "LegacyViewManagerInteropState.h"
#include "RCTLegacyViewManagerInteropCoordinator.h"
namespace facebook::react {
static std::string moduleNameFromComponentNameNoRCTPrefix(const std::string &componentName)
{
// TODO: remove FB specific code (T56174424)
if (componentName == "StickerInputView") {
return "FBStickerInputViewManager";
}
if (componentName == "FDSTooltipView") {
return "FBReactFDSTooltipViewManager";
}
std::string fbPrefix("FB");
if (std::mismatch(fbPrefix.begin(), fbPrefix.end(), componentName.begin()).first == fbPrefix.end()) {
// If `moduleName` has "FB" prefix.
return componentName + "Manager";
}
std::string artPrefix("ART");
if (std::mismatch(artPrefix.begin(), artPrefix.end(), componentName.begin()).first == artPrefix.end()) {
return componentName + "Manager";
}
std::string rnPrefix("RN");
if (std::mismatch(rnPrefix.begin(), rnPrefix.end(), componentName.begin()).first == rnPrefix.end()) {
return componentName + "Manager";
}
return componentName + "Manager";
}
inline NSString *RCTNSStringFromString(const std::string &string)
{
return [NSString stringWithUTF8String:string.c_str()];
}
static Class getViewManagerFromComponentName(const std::string &componentName)
{
auto viewManagerName = moduleNameFromComponentNameNoRCTPrefix(componentName);
// 1. Try to get the manager with the RCT prefix.
auto rctViewManagerName = "RCT" + viewManagerName;
Class viewManagerClass = NSClassFromString(RCTNSStringFromString(rctViewManagerName));
if (viewManagerClass) {
return viewManagerClass;
}
// 2. Try to get the manager without the prefix.
viewManagerClass = NSClassFromString(RCTNSStringFromString(viewManagerName));
if (viewManagerClass) {
return viewManagerClass;
}
return nil;
}
static Class getViewManagerClass(const std::string &componentName, RCTBridge *bridge, RCTBridgeProxy *bridgeProxy)
{
Class viewManager = getViewManagerFromComponentName(componentName);
if (viewManager != nil) {
return viewManager;
}
// If all the heuristics fail, let's try to retrieve the view manager from the bridge/bridgeProxy
if (bridge != nil) {
return [[bridge moduleForName:RCTNSStringFromString(componentName)] class];
}
if (bridgeProxy != nil) {
return [[bridgeProxy moduleForName:RCTNSStringFromString(componentName) lazilyLoadIfNecessary:YES] class];
}
return nil;
}
static const std::shared_ptr<void> constructCoordinator(
const ContextContainer::Shared &contextContainer,
const ComponentDescriptor::Flavor &flavor)
{
auto optionalBridge = contextContainer->find<std::shared_ptr<void>>("Bridge");
RCTBridge *bridge;
if (optionalBridge) {
bridge = unwrapManagedObjectWeakly(optionalBridge.value());
}
RCTBridgeProxy *bridgeProxy;
auto optionalBridgeProxy = contextContainer->find<std::shared_ptr<void>>("RCTBridgeProxy");
if (optionalBridgeProxy) {
bridgeProxy = unwrapManagedObjectWeakly(optionalBridgeProxy.value());
}
auto componentName = *std::static_pointer_cast<std::string const>(flavor);
Class viewManagerClass = getViewManagerClass(componentName, bridge, bridgeProxy);
assert(viewManagerClass);
auto optionalEventDispatcher = contextContainer->find<std::shared_ptr<void>>("RCTEventDispatcher");
RCTEventDispatcher *eventDispatcher;
if (optionalEventDispatcher) {
eventDispatcher = unwrapManagedObject(optionalEventDispatcher.value());
}
auto optionalModuleDecorator = contextContainer->find<std::shared_ptr<void>>("RCTBridgeModuleDecorator");
RCTBridgeModuleDecorator *bridgeModuleDecorator;
if (optionalModuleDecorator) {
bridgeModuleDecorator = unwrapManagedObject(optionalModuleDecorator.value());
}
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:viewManagerClass
bridge:bridge
eventDispatcher:eventDispatcher];
return wrapManagedObject([[RCTLegacyViewManagerInteropCoordinator alloc]
initWithComponentData:componentData
bridge:bridge
bridgeProxy:bridgeProxy
bridgelessInteropData:bridgeModuleDecorator]);
}
LegacyViewManagerInteropComponentDescriptor::LegacyViewManagerInteropComponentDescriptor(
const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor(parameters), _coordinator(constructCoordinator(contextContainer_, flavor_))
{
}
ComponentHandle LegacyViewManagerInteropComponentDescriptor::getComponentHandle() const
{
return reinterpret_cast<ComponentHandle>(getComponentName());
}
ComponentName LegacyViewManagerInteropComponentDescriptor::getComponentName() const
{
return static_cast<const std::string *>(flavor_.get())->c_str();
}
void LegacyViewManagerInteropComponentDescriptor::adopt(ShadowNode &shadowNode) const
{
ConcreteComponentDescriptor::adopt(shadowNode);
auto &legacyViewManagerInteropShadowNode = static_cast<LegacyViewManagerInteropShadowNode &>(shadowNode);
auto state = LegacyViewManagerInteropState{};
state.coordinator = _coordinator;
legacyViewManagerInteropShadowNode.setStateData(std::move(state));
}
} // namespace facebook::react

View File

@@ -0,0 +1,13 @@
/*
* 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.
*/
namespace facebook::react {
extern const char LegacyViewManagerInteropComponentName[] =
"LegacyViewManagerInterop";
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropState.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewEventEmitter.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char LegacyViewManagerInteropComponentName[];
using LegacyViewManagerInteropShadowNode = ConcreteViewShadowNode<
LegacyViewManagerInteropComponentName,
LegacyViewManagerInteropViewProps,
LegacyViewManagerInteropViewEventEmitter,
LegacyViewManagerInteropState>;
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#import <memory>
namespace facebook::react {
/*
* State for <LegacyViewManagerInterop> component.
*/
class LegacyViewManagerInteropState final {
public:
std::shared_ptr<void> coordinator;
};
} // namespace facebook::react

View File

@@ -0,0 +1,11 @@
/*
* 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 "LegacyViewManagerInteropState.h"
namespace facebook::react {
} // namespace facebook::react

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LegacyViewManagerInteropViewEventEmitter.h"
#include <iostream>
namespace facebook::react {
void LegacyViewManagerInteropViewEventEmitter::dispatchEvent(
const std::string& type,
const folly::dynamic& payload) const {
EventEmitter::dispatchEvent(type, payload);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <react/renderer/core/EventEmitter.h>
namespace facebook::react {
class LegacyViewManagerInteropViewEventEmitter;
using SharedLegacyViewManagerInteropViewEventEmitter =
std::shared_ptr<const LegacyViewManagerInteropViewEventEmitter>;
class LegacyViewManagerInteropViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void dispatchEvent(const std::string& type, const folly::dynamic& payload)
const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* 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 "LegacyViewManagerInteropViewProps.h"
#include <react/renderer/core/DynamicPropsUtilities.h>
namespace facebook::react {
LegacyViewManagerInteropViewProps::LegacyViewManagerInteropViewProps(
const PropsParserContext& context,
const LegacyViewManagerInteropViewProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
otherProps(
mergeDynamicProps(sourceProps.otherProps, (folly::dynamic)rawProps)) {
}
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* 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 <folly/dynamic.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <unordered_map>
namespace facebook::react {
class LegacyViewManagerInteropViewProps final : public ViewProps {
public:
LegacyViewManagerInteropViewProps() = default;
LegacyViewManagerInteropViewProps(
const PropsParserContext& context,
const LegacyViewManagerInteropViewProps& sourceProps,
const RawProps& rawProps);
#pragma mark - Props
folly::dynamic const otherProps;
};
} // 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModuleDecorator.h>
#import <UIKit/UIKit.h>
#include <folly/dynamic.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTComponentData;
@class RCTBridge;
@class RCTBridgeProxy;
typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
@interface RCTLegacyViewManagerInteropCoordinator : NSObject
- (instancetype)initWithComponentData:(RCTComponentData *)componentData
bridge:(nullable RCTBridge *)bridge
bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy
bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData;
- (UIView *)createPaperViewWithTag:(NSInteger)tag;
- (void)addObserveForTag:(NSInteger)tag usingBlock:(InterceptorBlock)block;
- (void)removeObserveForTag:(NSInteger)tag;
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view;
- (NSString *)componentViewName;
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(UIView *)paperView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,223 @@
/*
* 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 "RCTLegacyViewManagerInteropCoordinator.h"
#include <React/RCTBridge+Private.h>
#include <React/RCTBridgeMethod.h>
#include <React/RCTBridgeProxy.h>
#include <React/RCTComponentData.h>
#include <React/RCTEventDispatcherProtocol.h>
#include <React/RCTFollyConvert.h>
#include <React/RCTModuleData.h>
#include <React/RCTModuleMethod.h>
#include <React/RCTUIManager.h>
#include <React/RCTUIManagerUtils.h>
#include <React/RCTUtils.h>
#include <React/RCTViewManager.h>
#include <folly/json.h>
#include <objc/runtime.h>
using namespace facebook::react;
@implementation RCTLegacyViewManagerInteropCoordinator {
RCTComponentData *_componentData;
__weak RCTBridge *_bridge;
__weak RCTBridgeModuleDecorator *_bridgelessInteropData;
__weak RCTBridgeProxy *_bridgeProxy;
/*
Each instance of `RCTLegacyViewManagerInteropComponentView` registers a block to which events are dispatched.
This is the container that maps unretained UIView pointer to a block to which the event is dispatched.
*/
NSMutableDictionary<NSNumber *, InterceptorBlock> *_eventInterceptors;
/*
* In bridgeless mode, instead of using the bridge to look up RCTModuleData,
* store that information locally.
*/
NSMutableArray<id<RCTBridgeMethod>> *_moduleMethods;
NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *_moduleMethodsByName;
}
- (instancetype)initWithComponentData:(RCTComponentData *)componentData
bridge:(nullable RCTBridge *)bridge
bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy
bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData
{
if (self = [super init]) {
_componentData = componentData;
_bridge = bridge;
_bridgelessInteropData = bridgelessInteropData;
_bridgeProxy = bridgeProxy;
if (bridgelessInteropData) {
// During bridge mode, RCTBridgeModules will be decorated with these APIs by the bridge.
RCTAssert(
_bridge == nil,
@"RCTLegacyViewManagerInteropCoordinator should not be initialized with RCTBridgeModuleDecorator in bridge mode.");
}
_eventInterceptors = [NSMutableDictionary new];
__weak __typeof(self) weakSelf = self;
_componentData.eventInterceptor = ^(NSString *eventName, NSDictionary *event, NSNumber *reactTag) {
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
InterceptorBlock block = [strongSelf->_eventInterceptors objectForKey:reactTag];
if (block) {
block(
std::string([RCTNormalizeInputEventName(eventName) UTF8String]),
convertIdToFollyDynamic(event ? event : @{}));
}
}
};
}
return self;
}
- (void)addObserveForTag:(NSInteger)tag usingBlock:(InterceptorBlock)block
{
[_eventInterceptors setObject:block forKey:[NSNumber numberWithInteger:tag]];
}
- (void)removeObserveForTag:(NSInteger)tag
{
[_eventInterceptors removeObjectForKey:[NSNumber numberWithInteger:tag]];
}
- (UIView *)createPaperViewWithTag:(NSInteger)tag
{
UIView *view = [_componentData createViewWithTag:[NSNumber numberWithInteger:tag] rootTag:NULL];
[_bridgelessInteropData attachInteropAPIsToModule:(id<RCTBridgeModule>)_componentData.bridgelessViewManager];
return view;
}
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(UIView *)view
{
[_componentData setProps:props forView:view];
if ([view respondsToSelector:@selector(didSetProps:)]) {
[view performSelector:@selector(didSetProps:) withObject:[props allKeys]];
}
}
- (NSString *)componentViewName
{
return RCTDropReactPrefixes(_componentData.name);
}
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(nonnull UIView *)paperView
{
Class managerClass = _componentData.managerClass;
[self _lookupModuleMethodsIfNecessary];
RCTModuleData *moduleData = [_bridge.batchedBridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
id<RCTBridgeMethod> method;
// We can't use `[NSString intValue]` as "0" is a valid command,
// but also a falsy value. [NSNumberFormatter numberFromString] returns a
// `NSNumber *` which is NULL when it's to be NULL
// and it points to 0 when the string is @"0" (not a falsy value).
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
if ([commandName isKindOfClass:[NSNumber class]] || [formatter numberFromString:commandName] != NULL) {
method = moduleData ? moduleData.methods[[commandName intValue]] : _moduleMethods[[commandName intValue]];
} else if ([commandName isKindOfClass:[NSString class]]) {
method = moduleData ? moduleData.methodsByName[commandName] : _moduleMethodsByName[commandName];
if (method == nil) {
RCTLogError(@"No command found with name \"%@\"", commandName);
}
} else {
RCTLogError(@"dispatchViewManagerCommand must be called with a string or integer command");
return;
}
NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args];
if (_bridge) {
[self _handleCommandsOnBridge:method withArgs:newArgs];
} else {
[self _handleCommandsOnBridgeless:method withArgs:newArgs];
}
}
#pragma mark - Private
- (void)_handleCommandsOnBridge:(id<RCTBridgeMethod>)method withArgs:(NSArray *)newArgs
{
[_bridge.batchedBridge
dispatchBlock:^{
[method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs];
[self->_bridge.uiManager setNeedsLayout];
}
queue:RCTGetUIManagerQueue()];
}
- (void)_handleCommandsOnBridgeless:(id<RCTBridgeMethod>)method withArgs:(NSArray *)newArgs
{
RCTViewManager *componentViewManager = self->_componentData.manager;
[componentViewManager setValue:_bridgeProxy forKey:@"bridge"];
[self->_bridgeProxy.uiManager
addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
[method invokeWithBridge:nil module:componentViewManager arguments:newArgs];
}];
}
- (void)_addUIBlock:(RCTViewManagerUIBlock)block
{
if (_bridge) {
[self _addUIBlockOnBridge:block];
} else {
[self->_bridgeProxy.uiManager addUIBlock:block];
}
}
- (void)_addUIBlockOnBridge:(RCTViewManagerUIBlock)block
{
__weak __typeof__(self) weakSelf = self;
[_bridge.batchedBridge
dispatchBlock:^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf->_bridge.uiManager addUIBlock:block];
}
queue:RCTGetUIManagerQueue()];
}
// This is copy-pasta from RCTModuleData.
- (void)_lookupModuleMethodsIfNecessary
{
if (!_bridge && !_moduleMethods) {
_moduleMethods = [NSMutableArray new];
_moduleMethodsByName = [NSMutableDictionary new];
unsigned int methodCount;
Class cls = _componentData.managerClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_componentData.managerClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod moduleClass:_componentData.managerClass];
[_moduleMethodsByName setValue:moduleMethod forKey:[NSString stringWithUTF8String:moduleMethod.JSMethodName]];
[_moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
}
}
@end

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UnstableLegacyViewManagerAutomaticComponentDescriptor.h"
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <string>
namespace facebook::react {
ComponentName
UnstableLegacyViewManagerAutomaticComponentDescriptor::getComponentName()
const {
return legacyComponentName_.c_str();
}
ComponentHandle
UnstableLegacyViewManagerAutomaticComponentDescriptor::getComponentHandle()
const {
return reinterpret_cast<ComponentHandle>(getComponentName());
}
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/legacyviewmanagerinterop/UnstableLegacyViewManagerAutomaticShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <string>
namespace facebook::react {
class UnstableLegacyViewManagerAutomaticComponentDescriptor final
: public ConcreteComponentDescriptor<
LegacyViewManagerAndroidInteropShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
UnstableLegacyViewManagerAutomaticComponentDescriptor(
const ComponentDescriptorParameters& parameters,
std::string legacyComponentName)
: ConcreteComponentDescriptor(parameters),
legacyComponentName_(std::move(legacyComponentName)) {}
ComponentHandle getComponentHandle() const override;
ComponentName getComponentName() const override;
private:
std::string legacyComponentName_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,13 @@
/*
* 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.
*/
namespace facebook::react {
extern const char LegacyViewManagerAndroidInteropComponentName[] =
"LegacyViewManagerInterop";
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* 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/legacyviewmanagerinterop/LegacyViewManagerInteropViewEventEmitter.h>
#include <react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropViewProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char LegacyViewManagerAndroidInteropComponentName[];
using LegacyViewManagerAndroidInteropShadowNode = ConcreteViewShadowNode<
LegacyViewManagerAndroidInteropComponentName,
LegacyViewManagerInteropViewProps>;
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <UnstableReactLegacyComponent> component.
*
* This component is part of the Fabric Interop Layer and is subject to future
* changes (hence the "Unstable" prefix).
*/
template <const char* concreteComponentName>
class UnstableLegacyViewManagerInteropComponentDescriptor
: public ConcreteComponentDescriptor<
ConcreteViewShadowNode<concreteComponentName, ViewProps>> {
public:
UnstableLegacyViewManagerInteropComponentDescriptor<concreteComponentName>(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor<
ConcreteViewShadowNode<concreteComponentName, ViewProps>>(
parameters) {}
private:
};
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
# 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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_modal_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_modal STATIC ${rrc_modal_SRC})
target_include_directories(rrc_modal PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_modal
glog
folly_runtime
glog_init
react_codegen_rncore
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
react_render_uimanager
rrc_image
rrc_view
yoga
)

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <glog/logging.h>
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <ModalHostView> component.
*/
class ModalHostViewComponentDescriptor final
: public ConcreteComponentDescriptor<ModalHostViewShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData =
static_cast<const ModalHostViewShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setSize(
Size{stateData.screenSize.width, stateData.screenSize.height});
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // 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.
*/
#include "ModalHostViewShadowNode.h"
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
namespace facebook::react {
extern const char ModalHostViewComponentName[] = "ModalHostView";
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* 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/modal/ModalHostViewState.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char ModalHostViewComponentName[];
/*
* `ShadowNode` for <ModalHostView> component.
*/
class ModalHostViewShadowNode final : public ConcreteViewShadowNode<
ModalHostViewComponentName,
ModalHostViewProps,
ModalHostViewEventEmitter,
ModalHostViewState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ModalHostViewState.h"
namespace facebook::react {
#ifdef ANDROID
folly::dynamic ModalHostViewState::getDynamic() const {
return folly::dynamic::object("screenWidth", screenSize.width)(
"screenHeight", screenSize.height);
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/graphics/Float.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
/*
* State for <ModalHostView> component.
*/
class ModalHostViewState final {
public:
using Shared = std::shared_ptr<const ModalHostViewState>;
ModalHostViewState(){};
ModalHostViewState(Size screenSize_) : screenSize(screenSize_){};
#ifdef ANDROID
ModalHostViewState(
const ModalHostViewState& previousState,
folly::dynamic data)
: screenSize(Size{
(Float)data["screenWidth"].getDouble(),
(Float)data["screenHeight"].getDouble()}){};
#endif
const Size screenSize{};
#ifdef ANDROID
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
#pragma mark - Getters
};
} // 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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_progressbar_SRC CONFIGURE_DEPENDS android/react/renderer/components/progressbar/*.cpp)
add_library(rrc_progressbar STATIC ${rrc_progressbar_SRC})
target_include_directories(rrc_progressbar
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/android/
)
target_link_libraries(rrc_progressbar
glog
fbjni
folly_runtime
glog_init
react_codegen_rncore
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_uimanager
reactnativejni
rrc_view
yoga
)

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.
*/
#pragma once
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include "AndroidProgressBarMeasurementsManager.h"
#include "AndroidProgressBarShadowNode.h"
namespace facebook::react {
/*
* Descriptor for <AndroidProgressBar> component.
*/
class AndroidProgressBarComponentDescriptor final
: public ConcreteComponentDescriptor<AndroidProgressBarShadowNode> {
public:
AndroidProgressBarComponentDescriptor(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
measurementsManager_(
std::make_shared<AndroidProgressBarMeasurementsManager>(
contextContainer_)) {}
void adopt(ShadowNode& shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
auto& androidProgressBarShadowNode =
static_cast<AndroidProgressBarShadowNode&>(shadowNode);
// `AndroidProgressBarShadowNode` uses
// `AndroidProgressBarMeasurementsManager` to provide measurements to Yoga.
androidProgressBarShadowNode.setAndroidProgressBarMeasurementsManager(
measurementsManager_);
// All `AndroidProgressBarShadowNode`s must have leaf Yoga nodes with
// properly setup measure function.
androidProgressBarShadowNode.enableMeasurement();
}
private:
const std::shared_ptr<AndroidProgressBarMeasurementsManager>
measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,73 @@
/*
* 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 "AndroidProgressBarMeasurementsManager.h"
#include <fbjni/fbjni.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/core/conversions.h>
using namespace facebook::jni;
namespace facebook::react {
Size AndroidProgressBarMeasurementsManager::measure(
SurfaceId surfaceId,
const AndroidProgressBarProps& props,
LayoutConstraints layoutConstraints) const {
{
std::scoped_lock lock(mutex_);
if (hasBeenMeasured_) {
return cachedMeasurement_;
}
}
const jni::global_ref<jobject>& fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
static auto measure = facebook::jni::findClassStatic(
"com/facebook/react/fabric/FabricUIManager")
->getMethod<jlong(
jint,
jstring,
ReadableMap::javaobject,
ReadableMap::javaobject,
ReadableMap::javaobject,
jfloat,
jfloat,
jfloat,
jfloat)>("measure");
auto minimumSize = layoutConstraints.minimumSize;
auto maximumSize = layoutConstraints.maximumSize;
local_ref<JString> componentName = make_jstring("AndroidProgressBar");
auto serialiazedProps = toDynamic(props);
local_ref<ReadableNativeMap::javaobject> propsRNM =
ReadableNativeMap::newObjectCxxArgs(serialiazedProps);
local_ref<ReadableMap::javaobject> propsRM =
make_local(reinterpret_cast<ReadableMap::javaobject>(propsRNM.get()));
auto measurement = yogaMeassureToSize(measure(
fabricUIManager,
surfaceId,
componentName.get(),
nullptr,
propsRM.get(),
nullptr,
minimumSize.width,
maximumSize.width,
minimumSize.height,
maximumSize.height));
std::scoped_lock lock(mutex_);
cachedMeasurement_ = measurement;
return measurement;
}
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/progressbar/conversions.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class AndroidProgressBarMeasurementsManager {
public:
AndroidProgressBarMeasurementsManager(
const ContextContainer::Shared& contextContainer)
: contextContainer_(contextContainer) {}
Size measure(
SurfaceId surfaceId,
const AndroidProgressBarProps& props,
LayoutConstraints layoutConstraints) const;
private:
const ContextContainer::Shared contextContainer_;
mutable std::mutex mutex_;
mutable bool hasBeenMeasured_ = false;
mutable Size cachedMeasurement_{};
};
} // 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 "AndroidProgressBarShadowNode.h"
#include <react/renderer/components/progressbar/AndroidProgressBarShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
namespace facebook::react {
extern const char AndroidProgressBarComponentName[] = "AndroidProgressBar";
void AndroidProgressBarShadowNode::setAndroidProgressBarMeasurementsManager(
const std::shared_ptr<AndroidProgressBarMeasurementsManager>&
measurementsManager) {
ensureUnsealed();
measurementsManager_ = measurementsManager;
}
#pragma mark - LayoutableShadowNode
Size AndroidProgressBarShadowNode::measureContent(
const LayoutContext& /*layoutContext*/,
const LayoutConstraints& layoutConstraints) const {
return measurementsManager_->measure(
getSurfaceId(), getConcreteProps(), layoutConstraints);
}
} // 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/renderer/components/progressbar/AndroidProgressBarMeasurementsManager.h>
#include <react/renderer/components/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char AndroidProgressBarComponentName[];
/*
* `ShadowNode` for <AndroidProgressBar> component.
*/
class AndroidProgressBarShadowNode final : public ConcreteViewShadowNode<
AndroidProgressBarComponentName,
AndroidProgressBarProps,
AndroidProgressBarEventEmitter> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
// Associates a shared `AndroidProgressBarMeasurementsManager` with the node.
void setAndroidProgressBarMeasurementsManager(
const std::shared_ptr<AndroidProgressBarMeasurementsManager>&
measurementsManager);
#pragma mark - LayoutableShadowNode
Size measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;
private:
std::shared_ptr<AndroidProgressBarMeasurementsManager> measurementsManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* 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 <folly/dynamic.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
#ifdef ANDROID
inline folly::dynamic toDynamic(const AndroidProgressBarProps& props) {
folly::dynamic serializedProps = folly::dynamic::object();
serializedProps["styleAttr"] = props.styleAttr;
serializedProps["typeAttr"] = props.typeAttr;
serializedProps["indeterminate"] = props.indeterminate;
serializedProps["progress"] = props.progress;
serializedProps["animating"] = props.animating;
serializedProps["color"] = toAndroidRepr(props.color);
serializedProps["testID"] = props.testID;
return serializedProps;
}
#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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_root_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_root STATIC ${rrc_root_SRC})
target_include_directories(rrc_root PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_root
folly_runtime
glog
glog_init
react_debug
react_render_core
react_render_debug
react_render_graphics
rrc_view
yoga
)

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/root/RootShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
using RootComponentDescriptor = ConcreteComponentDescriptor<RootShadowNode>;
} // namespace facebook::react

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RootProps.h"
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
#include <react/renderer/components/view/conversions.h>
namespace facebook::react {
// Note that a default/empty context may be passed here from RootShadowNode.
// If that's a problem and the context is necessary here, refactor
// RootShadowNode first.
RootProps::RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps) {}
// Note that a default/empty context may be passed here from RootShadowNode.
// If that's a problem and the context is necessary here, refactor
// RootShadowNode first.
RootProps::RootProps(
const PropsParserContext& /*context*/,
const RootProps& /*sourceProps*/,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext)
: ViewProps(),
layoutConstraints(layoutConstraints),
layoutContext(layoutContext){};
} // namespace facebook::react

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
class RootProps final : public ViewProps {
public:
RootProps() = default;
RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const RawProps& rawProps);
RootProps(
const PropsParserContext& context,
const RootProps& sourceProps,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext);
#pragma mark - Props
LayoutConstraints layoutConstraints{};
LayoutContext layoutContext{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* 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 "RootShadowNode.h"
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/debug/SystraceSection.h>
namespace facebook::react {
const char RootComponentName[] = "RootView";
bool RootShadowNode::layoutIfNeeded(
std::vector<const LayoutableShadowNode*>* affectedNodes) {
SystraceSection s("RootShadowNode::layout");
if (getIsLayoutClean()) {
return false;
}
ensureUnsealed();
auto layoutContext = getConcreteProps().layoutContext;
layoutContext.affectedNodes = affectedNodes;
layoutTree(layoutContext, getConcreteProps().layoutConstraints);
return true;
}
Transform RootShadowNode::getTransform() const {
auto viewportOffset = getConcreteProps().layoutContext.viewportOffset;
return Transform::Translate(viewportOffset.x, viewportOffset.y, 0);
}
RootShadowNode::Unshared RootShadowNode::clone(
const PropsParserContext& propsParserContext,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext) const {
auto props = std::make_shared<const RootProps>(
propsParserContext, getConcreteProps(), layoutConstraints, layoutContext);
auto newRootShadowNode = std::make_shared<RootShadowNode>(
*this,
ShadowNodeFragment{
/* .props = */ props,
});
if (layoutConstraints != getConcreteProps().layoutConstraints) {
newRootShadowNode->dirtyLayout();
}
return newRootShadowNode;
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* 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 <react/renderer/components/root/RootProps.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/PropsParserContext.h>
namespace facebook::react {
class RootShadowNode;
extern const char RootComponentName[];
/*
* `ShadowNode` for the root component.
* Besides all functionality of the `View` component, `RootShadowNode` contains
* props which represent external layout constraints and context of the
* shadow tree.
*/
class RootShadowNode final
: public ConcreteViewShadowNode<RootComponentName, RootProps> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using Shared = std::shared_ptr<const RootShadowNode>;
using Unshared = std::shared_ptr<RootShadowNode>;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
return traits;
}
/*
* Layouts the shadow tree if needed.
* Returns `false` if the three is already laid out.
*/
bool layoutIfNeeded(
std::vector<const LayoutableShadowNode*>* affectedNodes = {});
/*
* Clones the node with given `layoutConstraints` and `layoutContext`.
*/
RootShadowNode::Unshared clone(
const PropsParserContext& propsParserContext,
const LayoutConstraints& layoutConstraints,
const LayoutContext& layoutContext) const;
Transform getTransform() const override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <gtest/gtest.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
namespace facebook::react {
TEST(RootShadowNodeTest, cloneWithLayoutConstraints) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto builder = simpleComponentBuilder();
std::shared_ptr<RootShadowNode> rootShadowNode;
LayoutConstraints defaultLayoutConstraints = {};
auto element =
Element<RootShadowNode>().reference(rootShadowNode).tag(1).props([&] {
auto sharedProps = std::make_shared<RootProps>();
sharedProps->layoutConstraints = defaultLayoutConstraints;
return sharedProps;
});
builder.build(element);
EXPECT_FALSE(rootShadowNode->getIsLayoutClean());
EXPECT_TRUE(rootShadowNode->layoutIfNeeded());
EXPECT_TRUE(rootShadowNode->getIsLayoutClean());
auto clonedWithDifferentLayoutConstraints = rootShadowNode->clone(
parserContext, LayoutConstraints{{0, 0}, {10, 10}}, {});
EXPECT_FALSE(clonedWithDifferentLayoutConstraints->getIsLayoutClean());
EXPECT_TRUE(clonedWithDifferentLayoutConstraints->layoutIfNeeded());
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/safeareaview/SafeAreaViewShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <SafeAreaView> component.
*/
class SafeAreaViewComponentDescriptor final
: public ConcreteComponentDescriptor<SafeAreaViewShadowNode> {
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
void adopt(ShadowNode& shadowNode) const override {
auto& layoutableShadowNode =
static_cast<YogaLayoutableShadowNode&>(shadowNode);
auto& stateData = static_cast<const SafeAreaViewShadowNode::ConcreteState&>(
*shadowNode.getState())
.getData();
layoutableShadowNode.setPadding(stateData.padding);
ConcreteComponentDescriptor::adopt(shadowNode);
}
};
} // 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.
*/
#include "SafeAreaViewShadowNode.h"
namespace facebook::react {
extern const char SafeAreaViewComponentName[] = "SafeAreaView";
} // 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/rncore/EventEmitters.h>
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/components/safeareaview/SafeAreaViewState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
namespace facebook::react {
extern const char SafeAreaViewComponentName[];
/*
* `ShadowNode` for <SafeAreaView> component.
*/
class SafeAreaViewShadowNode final : public ConcreteViewShadowNode<
SafeAreaViewComponentName,
SafeAreaViewProps,
ViewEventEmitter,
SafeAreaViewState> {
using ConcreteViewShadowNode::ConcreteViewShadowNode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,10 @@
/*
* 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 "SafeAreaViewState.h"
namespace facebook::react {} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* 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/RectangleEdges.h>
namespace facebook::react {
/*
* State for <SafeAreaView> component.
*/
class SafeAreaViewState final {
public:
EdgeInsets padding{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
# 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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB rrc_scrollview_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rrc_scrollview STATIC ${rrc_scrollview_SRC})
target_include_directories(rrc_scrollview PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(rrc_scrollview
glog
folly_runtime
glog_init
jsi
react_debug
react_render_core
react_render_debug
react_render_graphics
react_render_mapbuffer
rrc_view
yoga
)

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RCTScrollViewProtocol <NSObject>
- (void)flashScrollIndicators;
- (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated;
- (void)scrollToEnd:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
@end
RCT_EXTERN inline void
RCTScrollViewHandleCommand(id<RCTScrollViewProtocol> componentView, const NSString *commandName, const NSArray *args)
{
if ([commandName isEqualToString:@"flashScrollIndicators"]) {
#if RCT_DEBUG
if ([args count] != 0) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 1);
return;
}
#endif
[componentView flashScrollIndicators];
return;
}
if ([commandName isEqualToString:@"scrollTo"]) {
#if RCT_DEBUG
if ([args count] != 3) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 3);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"float", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"float", @"ScrollView", commandName, @"2nd")) {
return;
}
#endif
NSObject *arg2 = args[2];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg2, [NSNumber class], @"boolean", @"ScrollView", commandName, @"3rd")) {
return;
}
#endif
double x = [(NSNumber *)arg0 doubleValue];
double y = [(NSNumber *)arg1 doubleValue];
BOOL animated = [(NSNumber *)arg2 boolValue];
[componentView scrollTo:x y:y animated:animated];
return;
}
if ([commandName isEqualToString:@"scrollToEnd"]) {
#if RCT_DEBUG
if ([args count] != 1) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 1);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"boolean", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
BOOL animated = [(NSNumber *)arg0 boolValue];
[componentView scrollToEnd:animated];
return;
}
if ([commandName isEqualToString:@"zoomToRect"]) {
#if RCT_DEBUG
if ([args count] != 2) {
RCTLogError(
@"%@ command %@ received %d arguments, expected %d.", @"ScrollView", commandName, (int)[args count], 2);
return;
}
#endif
NSObject *arg0 = args[0];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(
arg0, [NSDictionary class], @"dictionary", @"ScrollView", commandName, @"1st")) {
return;
}
#endif
NSDictionary *rectDict = (NSDictionary *)arg0;
NSNumber *x = rectDict[@"x"];
NSNumber *y = rectDict[@"y"];
NSNumber *width = rectDict[@"width"];
NSNumber *height = rectDict[@"height"];
CGRect rect = CGRectMake(x.doubleValue, y.doubleValue, width.doubleValue, height.doubleValue);
NSObject *arg1 = args[1];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"boolean", @"ScrollView", commandName, @"2nd")) {
return;
}
#endif
BOOL animated = [(NSNumber *)arg1 boolValue];
[componentView zoomToRect:rect animated:animated];
return;
}
}
NS_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More