first commit

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

View File

@@ -0,0 +1,301 @@
/*
* 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 "AnimatedModule.h"
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
namespace facebook::react {
AnimatedModule::AnimatedModule(
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider)
: NativeAnimatedModuleCxxSpec(jsInvoker),
nodesManagerProvider_(std::move(nodesManagerProvider)) {}
void AnimatedModule::startOperationBatch(jsi::Runtime& /*rt*/) {}
void AnimatedModule::finishOperationBatch(jsi::Runtime& /*rt*/) {
// No mutex needed, operations only added via TurboModule method invocation
std::vector<Operation> preOperations;
std::vector<Operation> operations;
std::swap(preOperations_, preOperations);
std::swap(operations_, operations);
if (nodesManager_) {
// TODO: nodesManager_ must exist at all times. But without this check
// AnimatedProps-itest.js fails.
nodesManager_->scheduleOnUI([this,
preOperations = std::move(preOperations),
operations = std::move(operations)]() {
for (auto& preOperation : preOperations) {
executeOperation(preOperation);
}
for (auto& operation : operations) {
executeOperation(operation);
}
});
}
}
void AnimatedModule::createAnimatedNode(
jsi::Runtime& rt,
Tag tag,
jsi::Object config) {
auto configDynamic = dynamicFromValue(rt, jsi::Value(rt, config));
if (auto it = configDynamic.find("disableBatchingForNativeCreate");
it != configDynamic.items().end() && it->second == true) {
if (nodesManager_) {
nodesManager_->createAnimatedNodeAsync(tag, configDynamic);
}
} else {
operations_.emplace_back(
CreateAnimatedNodeOp{.tag = tag, .config = std::move(configDynamic)});
}
}
void AnimatedModule::updateAnimatedNodeConfig(
jsi::Runtime& rt,
Tag tag,
jsi::Object config) {
// TODO(T196513045): missing implementation. This API is only used by Animated
// when PlatformColor API is used and animation is updated with a new value
// through AnimatedColor.setValue.
}
void AnimatedModule::getValue(
jsi::Runtime& /*rt*/,
Tag tag,
AsyncCallback<double> saveValueCallback) {
operations_.emplace_back(
GetValueOp{.tag = tag, .callback = std::move(saveValueCallback)});
}
void AnimatedModule::startListeningToAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag tag) {
operations_.emplace_back(StartListeningToAnimatedNodeValueOp{tag});
}
void AnimatedModule::stopListeningToAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag tag) {
operations_.emplace_back(StopListeningToAnimatedNodeValueOp{tag});
}
void AnimatedModule::connectAnimatedNodes(
jsi::Runtime& /*rt*/,
Tag parentTag,
Tag childTag) {
operations_.emplace_back(
ConnectAnimatedNodesOp{.parentTag = parentTag, .childTag = childTag});
}
void AnimatedModule::disconnectAnimatedNodes(
jsi::Runtime& /*rt*/,
Tag parentTag,
Tag childTag) {
operations_.emplace_back(
DisconnectAnimatedNodesOp{.parentTag = parentTag, .childTag = childTag});
}
void AnimatedModule::startAnimatingNode(
jsi::Runtime& rt,
int animationId,
Tag nodeTag,
jsi::Object config,
AnimationEndCallback endCallback) {
auto configDynamic = dynamicFromValue(rt, jsi::Value(rt, config));
operations_.emplace_back(
StartAnimatingNodeOp{
.animationId = animationId,
.nodeTag = nodeTag,
.config = std::move(configDynamic),
.endCallback = std::move(endCallback)});
}
void AnimatedModule::stopAnimation(jsi::Runtime& /*rt*/, int animationId) {
operations_.emplace_back(StopAnimationOp{animationId});
}
void AnimatedModule::setAnimatedNodeValue(
jsi::Runtime& /*rt*/,
Tag nodeTag,
double value) {
operations_.emplace_back(
SetAnimatedNodeValueOp{.nodeTag = nodeTag, .value = value});
}
void AnimatedModule::setAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag,
double offset) {
operations_.emplace_back(
SetAnimatedNodeOffsetOp{.nodeTag = nodeTag, .offset = offset});
}
void AnimatedModule::flattenAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag) {
operations_.emplace_back(FlattenAnimatedNodeOffsetOp({.nodeTag = nodeTag}));
}
void AnimatedModule::extractAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag nodeTag) {
operations_.emplace_back(ExtractAnimatedNodeOffsetOp({.nodeTag = nodeTag}));
}
void AnimatedModule::connectAnimatedNodeToView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
Tag viewTag) {
operations_.emplace_back(
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}
void AnimatedModule::disconnectAnimatedNodeFromView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
Tag viewTag) {
operations_.emplace_back(
DisconnectAnimatedNodeFromViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}
void AnimatedModule::restoreDefaultValues(jsi::Runtime& /*rt*/, Tag nodeTag) {
preOperations_.emplace_back(RestoreDefaultValuesOp{nodeTag});
}
void AnimatedModule::dropAnimatedNode(jsi::Runtime& /*rt*/, Tag tag) {
operations_.emplace_back(DropAnimatedNodeOp{tag});
}
void AnimatedModule::addAnimatedEventToView(
jsi::Runtime& rt,
Tag viewTag,
std::string eventName,
jsi::Object eventMapping) {
auto eventMappingDynamic = dynamicFromValue(rt, jsi::Value(rt, eventMapping));
operations_.emplace_back(
AddAnimatedEventToViewOp{
.viewTag = viewTag,
.eventName = std::move(eventName),
.eventMapping = std::move(eventMappingDynamic)});
}
void AnimatedModule::removeAnimatedEventFromView(
jsi::Runtime& /*rt*/,
Tag viewTag,
std::string eventName,
Tag animatedNodeTag) {
operations_.emplace_back(
RemoveAnimatedEventFromViewOp{
.viewTag = viewTag,
.eventName = std::move(eventName),
.animatedNodeTag = animatedNodeTag});
}
void AnimatedModule::addListener(
jsi::Runtime& /*rt*/,
const std::string& /*eventName*/) {
// Not needed in C++ Animated. addListener is used to synchronise event
// animations like onScroll with React and Fabric. However C++ Animated
// synchronises with Fabric directly.
}
void AnimatedModule::removeListeners(jsi::Runtime& /*rt*/, int /*count*/) {
// Not needed in C++ Animated. removeListeners is used to synchronise event
// animations like onScroll with React and Fabric. However C++ Animated
// synchronises with Fabric directly.
}
void AnimatedModule::queueAndExecuteBatchedOperations(
jsi::Runtime& /*rt*/,
jsi::Array /*operationsAndArgs*/) {
// TODO(T225953475): missing implementation
}
void AnimatedModule::executeOperation(const Operation& operation) {
std::visit(
[&](const auto& op) {
using T = std::decay_t<decltype(op)>;
if constexpr (std::is_same_v<T, CreateAnimatedNodeOp>) {
nodesManager_->createAnimatedNode(op.tag, op.config);
} else if constexpr (std::is_same_v<T, GetValueOp>) {
auto animValue = nodesManager_->getValue(op.tag);
if (animValue) {
op.callback.call(animValue.value());
}
} else if constexpr (std::is_same_v<
T,
StartListeningToAnimatedNodeValueOp>) {
nodesManager_->startListeningToAnimatedNodeValue(
op.tag, [this, tag = op.tag](double value) {
emitDeviceEvent(
"onAnimatedValueUpdate",
[tag, value](
jsi::Runtime& rt, std::vector<jsi::Value>& args) {
auto arg = jsi::Object(rt);
arg.setProperty(rt, "tag", jsi::Value(tag));
arg.setProperty(rt, "value", jsi::Value(value));
args.emplace_back(rt, arg);
});
});
} else if constexpr (std::is_same_v<
T,
StopListeningToAnimatedNodeValueOp>) {
nodesManager_->stopListeningToAnimatedNodeValue(op.tag);
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodesOp>) {
nodesManager_->connectAnimatedNodes(op.parentTag, op.childTag);
} else if constexpr (std::is_same_v<T, DisconnectAnimatedNodesOp>) {
nodesManager_->disconnectAnimatedNodes(op.parentTag, op.childTag);
} else if constexpr (std::is_same_v<T, StartAnimatingNodeOp>) {
nodesManager_->startAnimatingNode(
op.animationId,
op.nodeTag,
std::move(op.config),
std::move(op.endCallback));
} else if constexpr (std::is_same_v<T, StopAnimationOp>) {
nodesManager_->stopAnimation(op.animationId, false);
} else if constexpr (std::is_same_v<T, SetAnimatedNodeValueOp>) {
nodesManager_->setAnimatedNodeValue(op.nodeTag, op.value);
} else if constexpr (std::is_same_v<T, SetAnimatedNodeOffsetOp>) {
nodesManager_->setAnimatedNodeOffset(op.nodeTag, op.offset);
} else if constexpr (std::is_same_v<T, FlattenAnimatedNodeOffsetOp>) {
nodesManager_->flattenAnimatedNodeOffset(op.nodeTag);
} else if constexpr (std::is_same_v<T, ExtractAnimatedNodeOffsetOp>) {
nodesManager_->extractAnimatedNodeOffsetOp(op.nodeTag);
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodeToViewOp>) {
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<
T,
DisconnectAnimatedNodeFromViewOp>) {
nodesManager_->disconnectAnimatedNodeFromView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<T, RestoreDefaultValuesOp>) {
nodesManager_->restoreDefaultValues(op.nodeTag);
} else if constexpr (std::is_same_v<T, DropAnimatedNodeOp>) {
nodesManager_->dropAnimatedNode(op.tag);
} else if constexpr (std::is_same_v<T, AddAnimatedEventToViewOp>) {
nodesManager_->addAnimatedEventToView(
op.viewTag, op.eventName, op.eventMapping);
} else if constexpr (std::is_same_v<T, RemoveAnimatedEventFromViewOp>) {
nodesManager_->removeAnimatedEventFromView(
op.viewTag, op.eventName, op.animatedNodeTag);
}
},
operation);
}
void AnimatedModule::installJSIBindingsWithRuntime(jsi::Runtime& runtime) {
if (nodesManagerProvider_) {
nodesManager_ = nodesManagerProvider_->getOrCreate(runtime, jsInvoker_);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,205 @@
/*
* 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
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <ReactCommon/TurboModuleWithJSIBindings.h>
#include <folly/dynamic.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/NativeAnimatedNodesManagerProvider.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <memory>
#include <string>
#include <variant>
namespace facebook::react {
class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>, public TurboModuleWithJSIBindings {
#pragma mark - Operation structures for each type of animated operation
struct CreateAnimatedNodeOp {
Tag tag{};
folly::dynamic config;
};
struct GetValueOp {
Tag tag{};
AsyncCallback<double> callback;
};
struct StartListeningToAnimatedNodeValueOp {
Tag tag{};
};
struct StopListeningToAnimatedNodeValueOp {
Tag tag{};
};
struct ConnectAnimatedNodesOp {
Tag parentTag{};
Tag childTag{};
};
struct DisconnectAnimatedNodesOp {
Tag parentTag{};
Tag childTag{};
};
struct StartAnimatingNodeOp {
int animationId{};
Tag nodeTag{};
folly::dynamic config;
AnimationEndCallback endCallback;
};
struct StopAnimationOp {
int animationId{};
};
struct SetAnimatedNodeValueOp {
Tag nodeTag{};
double value{};
};
struct SetAnimatedNodeOffsetOp {
Tag nodeTag{};
double offset{};
};
struct FlattenAnimatedNodeOffsetOp {
Tag nodeTag{};
};
struct ExtractAnimatedNodeOffsetOp {
Tag nodeTag{};
};
struct ConnectAnimatedNodeToViewOp {
Tag nodeTag{};
Tag viewTag{};
};
struct DisconnectAnimatedNodeFromViewOp {
Tag nodeTag{};
Tag viewTag{};
};
struct RestoreDefaultValuesOp {
Tag nodeTag{};
};
struct DropAnimatedNodeOp {
Tag tag{};
};
struct AddAnimatedEventToViewOp {
Tag viewTag{};
std::string eventName;
folly::dynamic eventMapping;
};
struct RemoveAnimatedEventFromViewOp {
Tag viewTag{};
std::string eventName;
Tag animatedNodeTag{};
};
using Operation = std::variant<
CreateAnimatedNodeOp,
GetValueOp,
StartListeningToAnimatedNodeValueOp,
StopListeningToAnimatedNodeValueOp,
ConnectAnimatedNodesOp,
DisconnectAnimatedNodesOp,
StartAnimatingNodeOp,
StopAnimationOp,
SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
ExtractAnimatedNodeOffsetOp,
DropAnimatedNodeOp,
AddAnimatedEventToViewOp,
RemoveAnimatedEventFromViewOp>;
#pragma mark -
public:
AnimatedModule(
std::shared_ptr<CallInvoker> jsInvoker,
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider);
void startOperationBatch(jsi::Runtime &rt);
void finishOperationBatch(jsi::Runtime &rt);
void createAnimatedNode(jsi::Runtime &rt, Tag tag, jsi::Object config);
void updateAnimatedNodeConfig(jsi::Runtime &rt, Tag tag, jsi::Object config);
void getValue(jsi::Runtime &rt, Tag tag, AsyncCallback<double> saveValueCallback);
void startListeningToAnimatedNodeValue(jsi::Runtime &rt, Tag tag);
void stopListeningToAnimatedNodeValue(jsi::Runtime &rt, Tag tag);
void connectAnimatedNodes(jsi::Runtime &rt, Tag parentTag, Tag childTag);
void disconnectAnimatedNodes(jsi::Runtime &rt, Tag parentTag, Tag childTag);
void startAnimatingNode(
jsi::Runtime &rt,
int animationId,
Tag nodeTag,
jsi::Object config,
AnimationEndCallback endCallback);
void stopAnimation(jsi::Runtime &rt, int animationId);
void setAnimatedNodeValue(jsi::Runtime &rt, Tag nodeTag, double value);
void setAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag, double offset);
void flattenAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag);
void extractAnimatedNodeOffset(jsi::Runtime &rt, Tag nodeTag);
void connectAnimatedNodeToView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
void disconnectAnimatedNodeFromView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
void restoreDefaultValues(jsi::Runtime &rt, Tag nodeTag);
void dropAnimatedNode(jsi::Runtime &rt, Tag tag);
void addAnimatedEventToView(jsi::Runtime &rt, Tag viewTag, std::string eventName, jsi::Object eventMapping);
void removeAnimatedEventFromView(jsi::Runtime &rt, Tag viewTag, std::string eventName, Tag animatedNodeTag);
void addListener(jsi::Runtime &rt, const std::string &eventName);
void removeListeners(jsi::Runtime &rt, int count);
void queueAndExecuteBatchedOperations(jsi::Runtime &rt, jsi::Array operationsAndArgs);
private:
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider_;
std::shared_ptr<NativeAnimatedNodesManager> nodesManager_;
std::vector<Operation> preOperations_;
std::vector<Operation> operations_;
void executeOperation(const Operation &operation);
void installJSIBindingsWithRuntime(jsi::Runtime &runtime) override;
};
} // 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_animated_SRC CONFIGURE_DEPENDS
*.cpp
drivers/*.cpp
event_drivers/*.cpp
internal/*.cpp
nodes/*.cpp)
add_library(react_renderer_animated OBJECT ${react_renderer_animated_SRC})
target_include_directories(react_renderer_animated PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_animated
react_codegen_rncore
react_debug
react_renderer_core
react_renderer_graphics
react_renderer_mounting
react_renderer_uimanager
react_renderer_scheduler
react_renderer_animationbackend
glog
folly_runtime
)
target_compile_reactnative_options(react_renderer_animated PRIVATE)
target_compile_options(react_renderer_animated PRIVATE -Wpedantic)
target_compile_definitions(react_renderer_animated PRIVATE RN_USE_ANIMATION_BACKEND)

View File

@@ -0,0 +1,71 @@
/*
* 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 <mutex>
#include <shared_mutex>
#include <react/renderer/core/EventPayload.h>
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react {
/**
* Listener for events dispatched to JS runtime.
* Return `true` to interrupt default dispatch to JS event emitter, `false` to
* pass through to default handlers.
*/
template <typename... TArgs>
using EventListenerT = std::function<bool(TArgs...)>;
template <typename... TArgs>
class EventListenerContainerT {
public:
/*
* Invoke listeners in this container with the event.
* Returns true if event was handled by the listener, false to continue
* default dispatch.
*/
bool willDispatchEvent(TArgs... args)
{
std::shared_lock lock(mutex_);
bool handled = false;
for (const auto &listener : eventListeners_) {
handled = (*listener)(args...);
if (handled) {
break;
}
}
return handled;
}
void addListener(std::shared_ptr<const EventListenerT<TArgs...>> listener)
{
std::unique_lock lock(mutex_);
eventListeners_.push_back(std::move(listener));
}
void removeListener(const std::shared_ptr<const EventListenerT<TArgs...>> &listener)
{
std::unique_lock lock(mutex_);
auto it = std::find(eventListeners_.begin(), eventListeners_.end(), listener);
if (it != eventListeners_.end()) {
eventListeners_.erase(it);
}
}
private:
std::shared_mutex mutex_;
std::vector<std::shared_ptr<const EventListenerT<TArgs...>>> eventListeners_;
};
using EventEmitterListener = EventListenerT<Tag, const std::string &, const EventPayload &>;
using EventEmitterListenerContainer = EventListenerContainerT<Tag, const std::string &, const EventPayload &>;
} // 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 "MergedValueDispatcher.h"
#include <react/renderer/core/DynamicPropsUtilities.h>
namespace facebook::react {
MergedValueDispatcher::MergedValueDispatcher(
DispatchFunction dispatchFunction,
MergedValueFunction mergedValueFunction)
: dispatchFunction_(std::move(dispatchFunction)),
mergedValueFunction_(std::move(mergedValueFunction)) {}
void MergedValueDispatcher::dispatch(
const std::unordered_map<Tag, folly::dynamic>& value) {
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [viewTag, props] : value) {
accumulatedValues_[viewTag] = mergeDynamicProps(
accumulatedValues_[viewTag], props, NullValueStrategy::Override);
}
if (hasPendingDispatch_) {
return;
}
hasPendingDispatch_ = true;
}
dispatchFunction_([this]() {
auto accumulatedValuesCopy = std::unordered_map<Tag, folly::dynamic>{};
{
std::lock_guard<std::mutex> lock(mutex_);
std::swap(accumulatedValues_, accumulatedValuesCopy);
hasPendingDispatch_ = false;
}
mergedValueFunction_(std::move(accumulatedValuesCopy));
});
}
} // 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 <folly/dynamic.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <functional>
#include <mutex>
namespace facebook::react {
/**
* A thread-safe dispatcher that ensures only the latest value is dispatched
* to avoid overloading the target thread. Multiple rapid calls are coalesced
* into a single dispatch operation merging the values.
*/
class MergedValueDispatcher {
public:
using DispatchFunction = std::function<void(std::function<void()> &&)>;
using MergedValueFunction = std::function<void(std::unordered_map<Tag, folly::dynamic> &&tagToProps)>;
/**
* Creates a MergedValueDispatcher with the given dispatch function.
*
* @param dispatchFunction - function that dispatches to the target
* thread.
* @param latestValueFunction - function that will be called on the target
* thread with all values merged.
*/
explicit MergedValueDispatcher(DispatchFunction dispatchFunction, MergedValueFunction mergedValueFunction);
/**
* Dispatches the given value. If a dispatch is already pending, this will
* merge with the pending value instead of creating a new dispatch.
*
* @param value - value to be dispatched.
*/
void dispatch(const std::unordered_map<Tag, folly::dynamic> &value);
private:
DispatchFunction dispatchFunction_;
MergedValueFunction mergedValueFunction_;
std::mutex mutex_;
bool hasPendingDispatch_{false};
std::unordered_map<Tag, folly::dynamic> accumulatedValues_;
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,268 @@
/*
* 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
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <folly/dynamic.h>
#include <react/bridging/Function.h>
#include <react/debug/flags.h>
#include <react/renderer/animated/EventEmitterListener.h>
#include <react/renderer/animated/event_drivers/EventAnimationDriver.h>
#ifdef RN_USE_ANIMATION_BACKEND
#include <react/renderer/animationbackend/AnimationBackend.h>
#endif
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace facebook::react {
using TimePointFunction = std::chrono::steady_clock::time_point (*)();
// A way to inject a custom time function for testing purposes.
// Default is `std::chrono::steady_clock::now`.
void g_setNativeAnimatedNowTimestampFunction(TimePointFunction nowFunction);
class AnimatedNode;
class AnimationDriver;
class Scheduler;
using ValueListenerCallback = std::function<void(double)>;
using UiTask = std::function<void()>;
using EndResult = NativeAnimatedTurboModuleEndResult<bool, std::optional<double>, std::optional<double>>;
using AnimationEndCallback = AsyncCallback<EndResult>;
template <>
struct Bridging<EndResult> : NativeAnimatedTurboModuleEndResultBridging<EndResult> {};
class NativeAnimatedNodesManager {
public:
using DirectManipulationCallback = std::function<void(Tag, const folly::dynamic &)>;
using FabricCommitCallback = std::function<void(std::unordered_map<Tag, folly::dynamic> &)>;
using StartOnRenderCallback = std::function<void(std::function<void()> &&, bool isAsync)>;
using StopOnRenderCallback = std::function<void(bool isAsync)>;
using FrameRateListenerCallback = std::function<void(bool /* shouldEnableListener */)>;
explicit NativeAnimatedNodesManager(
DirectManipulationCallback &&directManipulationCallback,
FabricCommitCallback &&fabricCommitCallback,
StartOnRenderCallback &&startOnRenderCallback = nullptr,
StopOnRenderCallback &&stopOnRenderCallback = nullptr,
FrameRateListenerCallback &&frameRateListenerCallback = nullptr) noexcept;
explicit NativeAnimatedNodesManager(std::shared_ptr<UIManagerAnimationBackend> animationBackend) noexcept;
~NativeAnimatedNodesManager() noexcept;
template <typename T, typename = std::enable_if_t<std::is_base_of_v<AnimatedNode, T>>>
T *getAnimatedNode(Tag tag) const
requires(std::is_base_of_v<AnimatedNode, T>)
{
if (auto it = animatedNodes_.find(tag); it != animatedNodes_.end()) {
return static_cast<T *>(it->second.get());
}
return nullptr;
}
std::optional<double> getValue(Tag tag) noexcept;
#pragma mark - Graph
// Called from JS thread
void createAnimatedNodeAsync(Tag tag, const folly::dynamic &config) noexcept;
void createAnimatedNode(Tag tag, const folly::dynamic &config) noexcept;
void connectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
void restoreDefaultValues(Tag tag) noexcept;
void dropAnimatedNode(Tag tag) noexcept;
void setAnimatedNodeValue(Tag tag, double value);
void flattenAnimatedNodeOffset(Tag tag);
void extractAnimatedNodeOffsetOp(Tag tag);
void setAnimatedNodeOffset(Tag tag, double offset);
#ifdef RN_USE_ANIMATION_BACKEND
AnimationMutations pullAnimationMutations();
#endif
#pragma mark - Drivers
void startAnimatingNode(
int animationId,
Tag animatedNodeTag,
folly::dynamic config,
std::optional<AnimationEndCallback> endCallback) noexcept;
void stopAnimation(int animationId, bool isTrackingAnimation = false) noexcept;
void addAnimatedEventToView(Tag viewTag, const std::string &eventName, const folly::dynamic &eventMapping) noexcept;
void removeAnimatedEventFromView(Tag viewTag, const std::string &eventName, Tag animatedValueTag) noexcept;
std::shared_ptr<EventEmitterListener> getEventEmitterListener() noexcept
{
return ensureEventEmitterListener();
}
#pragma mark - Listeners
void startListeningToAnimatedNodeValue(Tag tag, ValueListenerCallback &&callback) noexcept;
void stopListeningToAnimatedNodeValue(Tag tag) noexcept;
void schedulePropsCommit(
Tag viewTag,
const folly::dynamic &props,
bool layoutStyleUpdated,
bool forceFabricCommit) noexcept;
/**
* Commits all pending animated property updates to their respective views.
*
* This method is the final step in the animation pipeline that applies
* calculated property values to the actual UI components. It uses
* Fabric-based updates if layout properties are affected, otherwise uses
* direct manipulation.
*
* returns boolean indicating whether any changes were committed to views.
* Returns true if no changes were made, which helps the animation
* system determine if animations are still active.
*/
bool commitProps();
void scheduleOnUI(UiTask &&task)
{
{
std::lock_guard<std::mutex> lock(uiTasksMutex_);
operations_.push_back(std::move(task));
}
// Whenever a batch is flushed to the UI thread, start the onRender
// callbacks to guarantee they run at least once. E.g., to execute
// setValue calls.
startRenderCallbackIfNeeded(true);
}
void onRender();
void startRenderCallbackIfNeeded(bool isAsync);
void updateNodes(const std::set<int> &finishedAnimationValueNodes = {}) noexcept;
folly::dynamic managedProps(Tag tag) const noexcept;
bool hasManagedProps() const noexcept;
void onManagedPropsRemoved(Tag tag) noexcept;
bool isOnRenderThread() const noexcept;
private:
void stopRenderCallbackIfNeeded(bool isAsync) noexcept;
bool onAnimationFrame(double timestamp);
bool isAnimationUpdateNeeded() const noexcept;
void stopAnimationsForNode(Tag nodeTag);
std::shared_ptr<EventEmitterListener> ensureEventEmitterListener() noexcept;
void handleAnimatedEvent(Tag tag, const std::string &eventName, const EventPayload &payload) noexcept;
std::weak_ptr<UIManagerAnimationBackend> animationBackend_;
std::unique_ptr<AnimatedNode> animatedNode(Tag tag, const folly::dynamic &config) noexcept;
static thread_local bool isOnRenderThread_;
std::mutex animatedNodesCreatedAsyncMutex_;
std::unordered_map<Tag, std::unique_ptr<AnimatedNode>> animatedNodesCreatedAsync_;
std::unordered_map<Tag, std::unique_ptr<AnimatedNode>> animatedNodes_;
std::unordered_map<Tag, Tag> connectedAnimatedNodes_;
std::unordered_map<int, std::unique_ptr<AnimationDriver>> activeAnimations_;
std::unordered_map<
EventAnimationDriverKey,
std::vector<std::unique_ptr<EventAnimationDriver>>,
std::hash<facebook::react::EventAnimationDriverKey>>
eventDrivers_;
std::unordered_set<Tag> updatedNodeTags_;
mutable std::mutex connectedAnimatedNodesMutex_;
std::mutex uiTasksMutex_;
std::vector<UiTask> operations_;
/*
* Tracks whether a event-driven animation is currently in progress.
* This is set to true when an event handler triggers an animation,
* and reset to false when UI tick results in no changes to UI from
* animations.
*/
bool isEventAnimationInProgress_{false};
// React context required to commit props onto Component View
const DirectManipulationCallback directManipulationCallback_;
const FabricCommitCallback fabricCommitCallback_;
/*
* Tracks whether the render callback loop for animations is currently active.
*/
std::atomic_bool isRenderCallbackStarted_{false};
const StartOnRenderCallback startOnRenderCallback_;
const StopOnRenderCallback stopOnRenderCallback_;
const FrameRateListenerCallback frameRateListenerCallback_;
std::shared_ptr<EventEmitterListener> eventEmitterListener_{nullptr};
std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
/*
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
* NativeAnimated has previously changed the view's props via direct
* manipulation, we use unsyncedDirectViewProps_ to keep track of those
* props, to make sure later Fabric commits will not override direct
* manipulation result on this view.
*/
mutable std::mutex unsyncedDirectViewPropsMutex_;
std::unordered_map<Tag, folly::dynamic> unsyncedDirectViewProps_{};
int animatedGraphBFSColor_ = 0;
#ifdef REACT_NATIVE_DEBUG
bool warnedAboutGraphTraversal_ = false;
#endif
friend class ColorAnimatedNode;
friend class AnimationDriver;
friend class AnimationTestsBase;
};
} // namespace facebook::react

View File

@@ -0,0 +1,181 @@
/*
* 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 "NativeAnimatedNodesManagerProvider.h"
#include <glog/logging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/animated/MergedValueDispatcher.h>
#include <react/renderer/animated/internal/AnimatedMountingOverrideDelegate.h>
#ifdef RN_USE_ANIMATION_BACKEND
#include <react/renderer/animationbackend/AnimationBackend.h>
#endif
#include <react/renderer/uimanager/UIManagerBinding.h>
namespace facebook::react {
UIManagerNativeAnimatedDelegateImpl::UIManagerNativeAnimatedDelegateImpl(
std::weak_ptr<NativeAnimatedNodesManager> manager)
: nativeAnimatedNodesManager_(manager) {}
void UIManagerNativeAnimatedDelegateImpl::runAnimationFrame() {
if (auto nativeAnimatedNodesManagerStrong =
nativeAnimatedNodesManager_.lock()) {
nativeAnimatedNodesManagerStrong->onRender();
}
}
NativeAnimatedNodesManagerProvider::NativeAnimatedNodesManagerProvider(
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback,
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback,
NativeAnimatedNodesManager::FrameRateListenerCallback
frameRateListenerCallback)
: eventEmitterListenerContainer_(
std::make_shared<EventEmitterListenerContainer>()),
startOnRenderCallback_(std::move(startOnRenderCallback)),
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
frameRateListenerCallback_(std::move(frameRateListenerCallback)) {}
std::shared_ptr<NativeAnimatedNodesManager>
NativeAnimatedNodesManagerProvider::getOrCreate(
jsi::Runtime& runtime,
std::shared_ptr<CallInvoker> jsInvoker) {
if (nativeAnimatedNodesManager_ == nullptr) {
auto* uiManager = &UIManagerBinding::getBinding(runtime)->getUIManager();
NativeAnimatedNodesManager::FabricCommitCallback fabricCommitCallback =
nullptr;
if (!ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated()) {
mergedValueDispatcher_ = std::make_unique<MergedValueDispatcher>(
[jsInvoker](std::function<void()>&& func) {
jsInvoker->invokeAsync(std::move(func));
},
[uiManager](std::unordered_map<Tag, folly::dynamic>&& tagToProps) {
uiManager->updateShadowTree(std::move(tagToProps));
});
fabricCommitCallback =
[this](std::unordered_map<Tag, folly::dynamic>& tagToProps) {
mergedValueDispatcher_->dispatch(tagToProps);
};
}
auto directManipulationCallback =
[uiManager](Tag viewTag, const folly::dynamic& props) {
uiManager->synchronouslyUpdateViewOnUIThread(viewTag, props);
};
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
#ifdef RN_USE_ANIMATION_BACKEND
// TODO: this should be initialized outside of animated, but for now it
// was convenient to do it here
animationBackend_ = std::make_shared<AnimationBackend>(
std::move(startOnRenderCallback_),
std::move(stopOnRenderCallback_),
std::move(directManipulationCallback),
std::move(fabricCommitCallback),
uiManager);
#endif
nativeAnimatedNodesManager_ =
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
uiManager->unstable_setAnimationBackend(animationBackend_);
} else {
nativeAnimatedNodesManager_ =
std::make_shared<NativeAnimatedNodesManager>(
std::move(directManipulationCallback),
std::move(fabricCommitCallback),
std::move(startOnRenderCallback_),
std::move(stopOnRenderCallback_));
}
addEventEmitterListener(
nativeAnimatedNodesManager_->getEventEmitterListener());
uiManager->addEventListener(
std::make_shared<EventListener>(
[eventEmitterListenerContainerWeak =
std::weak_ptr<EventEmitterListenerContainer>(
eventEmitterListenerContainer_)](
const RawEvent& rawEvent) {
const auto& eventTarget = rawEvent.eventTarget;
const auto& eventPayload = rawEvent.eventPayload;
if (eventTarget && eventPayload) {
if (auto eventEmitterListenerContainer =
eventEmitterListenerContainerWeak.lock();
eventEmitterListenerContainer != nullptr) {
return eventEmitterListenerContainer->willDispatchEvent(
eventTarget->getTag(), rawEvent.type, *eventPayload);
}
}
return false;
}));
nativeAnimatedDelegate_ =
std::make_shared<UIManagerNativeAnimatedDelegateImpl>(
nativeAnimatedNodesManager_);
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
// TODO: remove force casting.
auto* scheduler = (Scheduler*)uiManager->getDelegate();
animatedMountingOverrideDelegate_ =
std::make_shared<AnimatedMountingOverrideDelegate>(
*nativeAnimatedNodesManager_, *scheduler);
// Register on existing surfaces
uiManager->getShadowTreeRegistry().enumerate(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
// Register on surfaces started in the future
uiManager->setOnSurfaceStartCallback(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
}
return nativeAnimatedNodesManager_;
}
void NativeAnimatedNodesManagerProvider::addEventEmitterListener(
const std::shared_ptr<EventEmitterListener>& listener) {
eventEmitterListenerContainer_->addListener(listener);
}
std::shared_ptr<EventEmitterListener>
NativeAnimatedNodesManagerProvider::getEventEmitterListener() {
if (!eventEmitterListener_) {
eventEmitterListener_ = std::make_shared<EventEmitterListener>(
[eventEmitterListenerContainerWeak =
std::weak_ptr<EventEmitterListenerContainer>(
eventEmitterListenerContainer_)](
Tag tag,
const std::string& eventName,
const EventPayload& payload) -> bool {
if (auto eventEmitterListenerContainer =
eventEmitterListenerContainerWeak.lock();
eventEmitterListenerContainer != nullptr) {
return eventEmitterListenerContainer->willDispatchEvent(
tag, eventName, payload);
}
return false;
});
}
return eventEmitterListener_;
}
} // 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 <react/renderer/animated/MergedValueDispatcher.h>
#include <react/renderer/uimanager/UIManagerNativeAnimatedDelegate.h>
#include "NativeAnimatedNodesManager.h"
namespace facebook::react {
class AnimatedMountingOverrideDelegate;
class NativeAnimatedNodesManagerProvider {
public:
NativeAnimatedNodesManagerProvider(
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback = nullptr,
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback = nullptr,
NativeAnimatedNodesManager::FrameRateListenerCallback frameRateListenerCallback = nullptr);
std::shared_ptr<NativeAnimatedNodesManager> getOrCreate(
jsi::Runtime &runtime,
std::shared_ptr<CallInvoker> jsInvoker);
// Native Event Listeners
void addEventEmitterListener(const std::shared_ptr<EventEmitterListener> &listener);
std::shared_ptr<EventEmitterListener> getEventEmitterListener();
private:
std::shared_ptr<UIManagerAnimationBackend> animationBackend_;
std::shared_ptr<NativeAnimatedNodesManager> nativeAnimatedNodesManager_;
std::shared_ptr<EventEmitterListenerContainer> eventEmitterListenerContainer_;
std::shared_ptr<EventEmitterListener> eventEmitterListener_;
std::shared_ptr<UIManagerNativeAnimatedDelegate> nativeAnimatedDelegate_;
std::shared_ptr<AnimatedMountingOverrideDelegate> animatedMountingOverrideDelegate_;
NativeAnimatedNodesManager::StartOnRenderCallback startOnRenderCallback_;
NativeAnimatedNodesManager::StopOnRenderCallback stopOnRenderCallback_;
NativeAnimatedNodesManager::FrameRateListenerCallback frameRateListenerCallback_;
std::unique_ptr<MergedValueDispatcher> mergedValueDispatcher_;
};
class UIManagerNativeAnimatedDelegateImpl : public UIManagerNativeAnimatedDelegate {
public:
explicit UIManagerNativeAnimatedDelegateImpl(std::weak_ptr<NativeAnimatedNodesManager> manager);
void runAnimationFrame() override;
private:
std::weak_ptr<NativeAnimatedNodesManager> nativeAnimatedNodesManager_;
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AnimationDriver.h"
#include <glog/logging.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <utility>
namespace facebook::react {
std::optional<AnimationDriverType> AnimationDriver::getDriverTypeByName(
const std::string& driverTypeName) {
if (driverTypeName == "frames") {
return AnimationDriverType::Frames;
} else if (driverTypeName == "spring") {
return AnimationDriverType::Spring;
} else if (driverTypeName == "decay") {
return AnimationDriverType::Decay;
} else {
return std::nullopt;
}
}
AnimationDriver::AnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: endCallback_(std::move(endCallback)),
id_(id),
animatedValueTag_(animatedValueTag),
manager_(manager),
config_(std::move(config)) {
onConfigChanged();
}
void AnimationDriver::startAnimation() {
startFrameTimeMs_ = -1;
isStarted_ = true;
}
void AnimationDriver::stopAnimation(bool /*ignoreCompletedHandlers*/) {
if (auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_);
node != nullptr && endCallback_) {
endCallback_.value().call(
{.finished = true,
.value = node->getRawValue(),
.offset = node->getOffset()});
}
}
void AnimationDriver::runAnimationStep(double renderingTime) {
if (!isStarted_ || isComplete_) {
return;
}
const auto frameTimeMs = renderingTime;
auto restarting = false;
if (startFrameTimeMs_ < 0) {
startFrameTimeMs_ = frameTimeMs;
restarting = true;
}
const auto timeDeltaMs = frameTimeMs - startFrameTimeMs_;
const auto isComplete = update(timeDeltaMs, restarting);
if (isComplete) {
if (iterations_ == -1 || ++currentIteration_ < iterations_) {
startFrameTimeMs_ = -1;
} else {
isComplete_ = true;
}
}
}
void AnimationDriver::updateConfig(folly::dynamic config) {
config_ = std::move(config);
onConfigChanged();
}
void AnimationDriver::onConfigChanged() {
iterations_ = (config_.count("iterations") != 0u)
? static_cast<int>(config_["iterations"].asDouble())
: 1;
isComplete_ = iterations_ == 0;
currentIteration_ = 1;
startFrameTimeMs_ = -1;
}
} // namespace facebook::react

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <react/debug/flags.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
enum class AnimationDriverType {
Frames,
Spring,
Decay,
};
class AnimationDriver {
public:
AnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
virtual ~AnimationDriver() = default;
void startAnimation();
void stopAnimation(bool ignoreCompletedHandlers = false);
inline int getId() const noexcept
{
return id_;
}
inline Tag getAnimatedValueTag() const noexcept
{
return animatedValueTag_;
}
bool getIsComplete() const noexcept
{
return isComplete_;
}
void runAnimationStep(double renderingTime);
virtual void updateConfig(folly::dynamic config);
#ifdef REACT_NATIVE_DEBUG
std::string debugID() const
{
return (config_.count("debugID") != 0u) ? config_["debugID"].asString() : "";
}
#endif
static std::optional<AnimationDriverType> getDriverTypeByName(const std::string &driverTypeName);
protected:
virtual bool update(double /*timeDeltaMs*/, bool /*restarting*/)
{
return true;
}
void markNodeUpdated(Tag tag)
{
manager_->updatedNodeTags_.insert(tag);
}
std::optional<AnimationEndCallback> endCallback_;
int id_{0};
Tag animatedValueTag_{}; // Tag of a ValueAnimatedNode
int iterations_{0};
NativeAnimatedNodesManager *manager_;
bool isComplete_{false};
int currentIteration_{0};
double startFrameTimeMs_{-1};
bool isStarted_{false};
bool ignoreCompletedHandlers_{false};
folly::dynamic config_{};
private:
void onConfigChanged();
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <string_view>
namespace facebook::react {
static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
static constexpr std::string_view ExtrapolateTypeExtend = "extend";
static constexpr double SingleFrameIntervalMs = 1000.0 / 60.0;
static constexpr double TicksPerMs = 10000.0; // ticks are 100 nanoseconds
inline double interpolate(
double inputValue,
double inputMin,
double inputMax,
double outputMin,
double outputMax,
std::string_view extrapolateLeft,
std::string_view extrapolateRight)
{
auto result = inputValue;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft == ExtrapolateTypeIdentity) {
return result;
} else if (extrapolateLeft == ExtrapolateTypeClamp) {
result = inputMin;
}
}
if (result > inputMax) {
if (extrapolateRight == ExtrapolateTypeIdentity) {
return result;
} else if (extrapolateRight == ExtrapolateTypeClamp) {
result = inputMax;
}
}
if (inputMin == inputMax) {
if (inputValue <= inputMin) {
return outputMin;
}
return outputMax;
}
return outputMin + (outputMax - outputMin) * (result - inputMin) / (inputMax - inputMin);
}
} // namespace facebook::react

View File

@@ -0,0 +1,81 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DecayAnimationDriver.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
DecayAnimationDriver::DecayAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager),
velocity_(config_["velocity"].asDouble()),
deceleration_(config_["deceleration"].asDouble()) {
react_native_assert(deceleration_ > 0);
}
std::tuple<float, double> DecayAnimationDriver::getValueAndVelocityForTime(
double time) const {
const auto value = fromValue_.value() +
velocity_ / (1 - deceleration_) *
(1 - std::exp(-(1 - deceleration_) * (1000 * time)));
return std::make_tuple(
static_cast<float>(value),
42.0f); // we don't need the velocity, so set it to a dummy value
}
bool DecayAnimationDriver::update(double timeDeltaMs, bool restarting) {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (restarting) {
const auto value = node->getRawValue();
if (!fromValue_.has_value()) {
// First iteration, assign fromValue based on AnimatedValue
fromValue_ = value;
} else {
// Not the first iteration, reset AnimatedValue based on
// originalValue
if (node->setRawValue(fromValue_.value())) {
markNodeUpdated(node->tag());
}
}
lastValue_ = value;
}
const auto [value, velocity] =
getValueAndVelocityForTime(timeDeltaMs / 1000.0);
auto isComplete =
lastValue_.has_value() && std::abs(value - lastValue_.value()) < 0.1;
if (!restarting && isComplete) {
return true;
} else {
lastValue_ = value;
if (node->setRawValue(value)) {
markNodeUpdated(node->tag());
}
return false;
}
}
return true;
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class DecayAnimationDriver : public AnimationDriver {
public:
DecayAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
private:
std::tuple<float, double> getValueAndVelocityForTime(double time) const;
private:
double velocity_{0};
double deceleration_{0};
std::optional<double> fromValue_{std::nullopt};
std::optional<double> lastValue_{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,113 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "FrameAnimationDriver.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/drivers/AnimationDriver.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <cmath>
#include <utility>
namespace facebook::react {
FrameAnimationDriver::FrameAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager) {
onConfigChanged();
}
void FrameAnimationDriver::updateConfig(folly::dynamic config) {
AnimationDriver::updateConfig(config);
onConfigChanged();
}
void FrameAnimationDriver::onConfigChanged() {
auto frames = config_["frames"];
react_native_assert(frames.type() == folly::dynamic::ARRAY);
for (const auto& frame : frames) {
auto frameValue = frame.asDouble();
frames_.push_back(frameValue);
}
toValue_ = config_["toValue"].asDouble();
}
bool FrameAnimationDriver::update(double timeDeltaMs, bool /*restarting*/) {
if (auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (!startValue_) {
startValue_ = node->getRawValue();
}
const auto startIndex =
static_cast<size_t>(std::round(timeDeltaMs / SingleFrameIntervalMs));
assert(startIndex >= 0);
const auto nextIndex = startIndex + 1;
double nextValue = NAN;
auto isComplete = false;
if (nextIndex >= frames_.size()) {
if (iterations_ == -1 || currentIteration_ < iterations_) {
// Use last frame value, just in case it's different from toValue_
nextValue = startValue_.value() +
frames_[frames_.size() - 1] * (toValue_ - startValue_.value());
} else {
nextValue = toValue_;
}
isComplete = true;
} else {
const auto fromInterval = startIndex * SingleFrameIntervalMs;
const auto toInterval = nextIndex * SingleFrameIntervalMs;
const auto fromValue = frames_[startIndex];
const auto toValue = frames_[nextIndex];
// Map timestamp to frame value (frames_ elements are in [0,1])
const auto frameOutput = interpolate(
timeDeltaMs,
fromInterval,
toInterval,
fromValue,
toValue,
ExtrapolateTypeExtend,
ExtrapolateTypeExtend);
// Map frame to output value
nextValue = interpolate(
frameOutput,
0,
1,
startValue_.value(),
toValue_,
ExtrapolateTypeExtend,
ExtrapolateTypeExtend);
}
if (node->setRawValue(nextValue)) {
markNodeUpdated(node->tag());
}
return isComplete;
}
return true;
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class FrameAnimationDriver : public AnimationDriver {
public:
FrameAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
void updateConfig(folly::dynamic config) override;
private:
void onConfigChanged();
std::vector<double> frames_{};
double toValue_{0};
std::optional<double> startValue_{};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "SpringAnimationDriver.h"
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
static constexpr auto MaxDeltaTimeMs = 4.0 * 1000.0 / 60.0;
SpringAnimationDriver::SpringAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager* manager)
: AnimationDriver(
id,
animatedValueTag,
std::move(endCallback),
std::move(config),
manager),
springStiffness_(config_["stiffness"].asDouble()),
springDamping_(config_["damping"].asDouble()),
springMass_(config_["mass"].asDouble()),
initialVelocity_(config_["initialVelocity"].asDouble()),
endValue_(config_["toValue"].asDouble()),
restSpeedThreshold_(config_["restSpeedThreshold"].asDouble()),
displacementFromRestThreshold_(
config_["restDisplacementThreshold"].asDouble()),
overshootClampingEnabled_(config_["overshootClamping"].asBool()) {}
std::tuple<float, double> SpringAnimationDriver::getValueAndVelocityForTime(
double time) const {
const auto startValue = fromValue_.value();
const auto toValue = endValue_;
const auto c = springDamping_;
const auto m = springMass_;
const auto k = springStiffness_;
const auto v0 = -initialVelocity_;
const auto zeta = c / (2 * std::sqrt(k * m));
const auto omega0 = std::sqrt(k / m);
const auto omega1 = omega0 * std::sqrt(1.0 - (zeta * zeta));
const auto x0 = toValue - startValue;
if (zeta < 1) {
const auto envelope = std::exp(-zeta * omega0 * time);
const auto value = static_cast<float>(
toValue -
envelope *
((v0 + zeta * omega0 * x0) / omega1 * std::sin(omega1 * time) +
x0 * std::cos(omega1 * time)));
const auto velocity = zeta * omega0 * envelope *
(std::sin(omega1 * time) * (v0 + zeta * omega0 * x0) / omega1 +
x0 * std::cos(omega1 * time)) -
envelope *
(std::cos(omega1 * time) * (v0 + zeta * omega0 * x0) -
omega1 * x0 * std::sin(omega1 * time));
return std::make_tuple(value, velocity);
} else {
const auto envelope = std::exp(-omega0 * time);
const auto value = static_cast<float>(
endValue_ - envelope * (x0 + (v0 + omega0 * x0) * time));
const auto velocity =
envelope * (v0 * (time * omega0 - 1) + time * x0 * (omega0 * omega0));
return std::make_tuple(value, velocity);
}
}
bool SpringAnimationDriver::update(double timeDeltaMs, bool restarting) {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(animatedValueTag_)) {
if (restarting) {
if (!fromValue_.has_value()) {
fromValue_ = node->getRawValue();
} else {
if (node->setRawValue(fromValue_.value())) {
markNodeUpdated(node->tag());
}
}
// Spring animations run a frame behind JS driven animations if we do
// not start the first frame at 16ms.
lastTime_ = timeDeltaMs - SingleFrameIntervalMs;
timeAccumulator_ = 0.0;
}
// clamp the amount of timeDeltaMs to avoid stuttering in the UI.
// We should be able to catch up in a subsequent advance if necessary.
auto adjustedDeltaTime = timeDeltaMs - lastTime_;
if (adjustedDeltaTime > MaxDeltaTimeMs) {
adjustedDeltaTime = MaxDeltaTimeMs;
}
timeAccumulator_ += adjustedDeltaTime;
lastTime_ = timeDeltaMs;
auto [value, velocity] =
getValueAndVelocityForTime(timeAccumulator_ / 1000.0);
auto isComplete = false;
if (isAtRest(velocity, value, endValue_) ||
(overshootClampingEnabled_ && isOvershooting(value))) {
if (springStiffness_ > 0) {
value = static_cast<float>(endValue_);
} else {
endValue_ = value;
}
isComplete = true;
}
if (node->setRawValue(value)) {
markNodeUpdated(node->tag());
}
return isComplete;
}
return true;
}
bool SpringAnimationDriver::isAtRest(
double currentVelocity,
double currentValue,
double endValue) const {
return std::abs(currentVelocity) <= restSpeedThreshold_ &&
(std::abs(currentValue - endValue) <= displacementFromRestThreshold_ ||
springStiffness_ == 0);
}
bool SpringAnimationDriver::isOvershooting(double currentValue) const {
const auto startValue = fromValue_.value();
return springStiffness_ > 0 &&
((startValue < endValue_ && currentValue > endValue_) ||
(startValue > endValue_ && currentValue < endValue_));
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimationDriver.h"
namespace facebook::react {
class SpringAnimationDriver : public AnimationDriver {
public:
SpringAnimationDriver(
int id,
Tag animatedValueTag,
std::optional<AnimationEndCallback> endCallback,
folly::dynamic config,
NativeAnimatedNodesManager *manager);
protected:
bool update(double timeDeltaMs, bool restarting) override;
private:
std::tuple<float, double> getValueAndVelocityForTime(double time) const;
bool isAtRest(double currentVelocity, double currentValue, double endValue) const;
bool isOvershooting(double currentValue) const;
double springStiffness_{0};
double springDamping_{0};
double springMass_{0};
double initialVelocity_{0};
std::optional<double> fromValue_{std::nullopt};
double endValue_{0};
double restSpeedThreshold_{0};
double displacementFromRestThreshold_{0};
bool overshootClampingEnabled_{false};
double lastTime_{0};
double timeAccumulator_{0};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "EventAnimationDriver.h"
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
EventAnimationDriver::EventAnimationDriver(
const std::vector<std::string>& eventPath,
Tag animatedValueTag)
: eventPath_(eventPath), animatedValueTag_(animatedValueTag) {}
std::optional<double> EventAnimationDriver::getValueFromPayload(
const EventPayload& eventPayload) {
return eventPayload.extractValue(eventPath_);
}
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/renderer/core/EventPayload.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <optional>
#include <string>
#include <vector>
namespace facebook::react {
class EventAnimationDriver {
public:
EventAnimationDriver(const std::vector<std::string> &eventPath, Tag animatedValueTag);
~EventAnimationDriver() = default;
std::optional<double> getValueFromPayload(const EventPayload &eventPayload);
Tag getAnimatedNodeTag() const
{
return animatedValueTag_;
}
protected:
std::vector<std::string> eventPath_;
const Tag animatedValueTag_;
};
struct EventAnimationDriverKey {
Tag viewTag;
std::string eventName;
bool operator==(const facebook::react::EventAnimationDriverKey &rhs) const noexcept
{
return viewTag == rhs.viewTag && eventName == rhs.eventName;
}
};
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::EventAnimationDriverKey> {
size_t operator()(const facebook::react::EventAnimationDriverKey &key) const
{
return std::hash<facebook::react::Tag>()(key.viewTag) ^ std::hash<std::string>()(key.eventName);
}
};
} // namespace std

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AnimatedMountingOverrideDelegate.h"
#include "../NativeAnimatedNodesManager.h"
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/DynamicPropsUtilities.h>
#include <react/renderer/scheduler/Scheduler.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
namespace facebook::react {
AnimatedMountingOverrideDelegate::AnimatedMountingOverrideDelegate(
NativeAnimatedNodesManager& animatedManager,
const Scheduler& scheduler)
: MountingOverrideDelegate(),
animatedManager_(&animatedManager),
scheduler_(&scheduler) {};
bool AnimatedMountingOverrideDelegate::shouldOverridePullTransaction() const {
if (animatedManager_ != nullptr) {
return animatedManager_->hasManagedProps();
}
return false;
}
std::optional<MountingTransaction>
AnimatedMountingOverrideDelegate::pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number transactionNumber,
const TransactionTelemetry& telemetry,
ShadowViewMutationList mutations) const {
std::unordered_map<Tag, folly::dynamic> animatedManagedProps;
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Update) {
const auto tag = mutation.newChildShadowView.tag;
auto props = animatedManager_->managedProps(tag);
if (!props.isNull()) {
animatedManagedProps.insert({tag, std::move(props)});
}
} else if (mutation.type == ShadowViewMutation::Delete) {
animatedManager_->onManagedPropsRemoved(mutation.oldChildShadowView.tag);
}
}
if (animatedManagedProps.empty()) {
return MountingTransaction{
surfaceId, transactionNumber, std::move(mutations), telemetry};
}
ShadowViewMutation::List filteredMutations;
filteredMutations.reserve(mutations.size());
for (const auto& mutation : mutations) {
folly::dynamic modifiedProps = folly::dynamic::object();
if (mutation.type == ShadowViewMutation::Update) {
if (auto node =
animatedManagedProps.extract(mutation.newChildShadowView.tag)) {
modifiedProps = std::move(node.mapped());
}
}
if (modifiedProps.empty()) {
filteredMutations.push_back(mutation);
} else {
if (const auto* componentDescriptor =
scheduler_
->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
mutation.newChildShadowView.componentHandle)) {
PropsParserContext propsParserContext{
mutation.newChildShadowView.surfaceId,
*scheduler_->getContextContainer()};
auto modifiedNewChildShadowView = mutation.newChildShadowView;
modifiedNewChildShadowView.props = componentDescriptor->cloneProps(
propsParserContext,
mutation.newChildShadowView.props,
RawProps(modifiedProps));
#ifdef RN_SERIALIZABLE_STATE
// Until Props 2.0 is shipped, android uses rawProps.
// RawProps must be kept synced with C++ Animated as well
// as props object.
auto& castedProps =
const_cast<Props&>(*modifiedNewChildShadowView.props);
castedProps.rawProps = mergeDynamicProps(
mutation.newChildShadowView.props->rawProps,
modifiedProps,
NullValueStrategy::Override);
#endif
filteredMutations.emplace_back(
ShadowViewMutation::UpdateMutation(
mutation.oldChildShadowView,
std::move(modifiedNewChildShadowView),
mutation.parentTag));
}
}
}
return MountingTransaction{
surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
}
} // 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.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <functional>
#include <optional>
namespace facebook::react {
class Scheduler;
class NativeAnimatedNodesManager;
class AnimatedMountingOverrideDelegate : public MountingOverrideDelegate {
public:
AnimatedMountingOverrideDelegate(NativeAnimatedNodesManager &animatedManager, const Scheduler &scheduler);
bool shouldOverridePullTransaction() const override;
std::optional<MountingTransaction> pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number transactionNumber,
const TransactionTelemetry &telemetry,
ShadowViewMutationList mutations) const override;
private:
mutable NativeAnimatedNodesManager *animatedManager_;
const Scheduler *scheduler_;
};
} // 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 <string>
#include <unordered_set>
namespace facebook::react {
inline static std::unordered_set<std::string> getDirectManipulationAllowlist()
{
/**
* Direct manipulation eligible styles allowed by the NativeAnimated JS
* implementation. Keep in sync with
* packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js
*/
static std::unordered_set<std::string> DIRECT_MANIPULATION_STYLES{
/* SUPPORTED_COLOR_STYLES */
"backgroundColor",
"borderBottomColor",
"borderColor",
"borderEndColor",
"borderLeftColor",
"borderRightColor",
"borderStartColor",
"borderTopColor",
"color",
"tintColor",
/* SUPPORTED STYLES */
"borderBottomEndRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
"borderBottomStartRadius",
"borderEndEndRadius",
"borderEndStartRadius",
"borderRadius",
"borderTopEndRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"borderTopStartRadius",
"borderStartEndRadius",
"borderStartStartRadius",
"elevation",
"opacity",
"transform",
"zIndex",
/* ios styles */
"shadowOpacity",
"shadowRadius",
/* legacy android transform properties */
"scaleX",
"scaleY",
"translateX",
"translateY",
};
return DIRECT_MANIPULATION_STYLES;
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react::animated {
// Indicates that the animated node identifier is not defined.
// It is safe to use 0 because JavaScript starts assigning identifiers from 1.
// https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/animated/NativeAnimatedHelper.js#L35
constexpr static Tag undefinedAnimatedNodeIdentifier = 0;
} // namespace facebook::react::animated

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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AdditionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void AdditionAnimatedNode::update() {
auto rawValue = 0.0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for AdditionAnimatedNode");
rawValue += node->getValue();
}
setRawValue(rawValue);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class AdditionAnimatedNode final : public OperatorAnimatedNode {
public:
AdditionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "AnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <utility>
namespace facebook::react {
AnimatedNode::AnimatedNode(
Tag tag,
folly::dynamic config,
NativeAnimatedNodesManager& manager,
AnimatedNodeType type)
: tag_(tag), manager_(&manager), type_(type), config_(std::move(config)) {}
void AnimatedNode::addChild(const Tag animatedNodeTag) {
children_.insert(animatedNodeTag);
getChildNode(animatedNodeTag)->onAttachToNode(tag_);
}
void AnimatedNode::removeChild(const Tag tag) {
if (const auto childNode = getChildNode(tag)) {
childNode->onDetachedFromNode(tag_);
children_.erase(tag);
}
}
AnimatedNode* AnimatedNode::getChildNode(Tag tag) {
if (children_.find(tag) != children_.end()) {
return manager_->getAnimatedNode<AnimatedNode>(tag);
}
return nullptr;
}
std::optional<AnimatedNodeType> AnimatedNode::getNodeTypeByName(
const std::string& nodeTypeName) {
if (nodeTypeName == "style") {
return AnimatedNodeType::Style;
} else if (nodeTypeName == "value") {
return AnimatedNodeType::Value;
} else if (nodeTypeName == "color") {
return AnimatedNodeType::Color;
} else if (nodeTypeName == "props") {
return AnimatedNodeType::Props;
} else if (nodeTypeName == "interpolation") {
return AnimatedNodeType::Interpolation;
} else if (nodeTypeName == "addition") {
return AnimatedNodeType::Addition;
} else if (nodeTypeName == "subtraction") {
return AnimatedNodeType::Subtraction;
} else if (nodeTypeName == "division") {
return AnimatedNodeType::Division;
} else if (nodeTypeName == "multiplication") {
return AnimatedNodeType::Multiplication;
} else if (nodeTypeName == "modulus") {
return AnimatedNodeType::Modulus;
} else if (nodeTypeName == "diffclamp") {
return AnimatedNodeType::Diffclamp;
} else if (nodeTypeName == "transform") {
return AnimatedNodeType::Transform;
} else if (nodeTypeName == "tracking") {
return AnimatedNodeType::Tracking;
} else if (nodeTypeName == "round") {
return AnimatedNodeType::Round;
} else if (nodeTypeName == "object") {
return AnimatedNodeType::Object;
} else {
return std::nullopt;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,112 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include <react/debug/flags.h>
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react {
enum class AnimatedNodeType {
Style,
Value,
Props,
Interpolation,
Addition,
Subtraction,
Division,
Multiplication,
Modulus,
Diffclamp,
Transform,
Tracking,
Color,
Round,
Object
};
class NativeAnimatedNodesManager;
class AnimatedNode {
public:
AnimatedNode(
Tag tag,
folly::dynamic config,
// TODO: T190028913 maybe pass in strongly typed data when constructing
// AnimatedNode
NativeAnimatedNodesManager &manager,
AnimatedNodeType type);
// Detach Node
virtual ~AnimatedNode() = default;
AnimatedNode(AnimatedNode &&) noexcept = default;
AnimatedNode &operator=(AnimatedNode &&) noexcept = default;
AnimatedNode(const AnimatedNode &) = default;
AnimatedNode &operator=(const AnimatedNode &) = default;
Tag tag() const noexcept
{
return tag_;
}
void addChild(Tag tag);
void removeChild(Tag tag);
const std::unordered_set<Tag> &getChildren() const noexcept
{
return children_;
}
AnimatedNodeType type() const noexcept
{
return type_;
}
const folly::dynamic &getConfig() const noexcept
{
return config_;
}
#ifdef REACT_NATIVE_DEBUG
std::string debugID() const
{
return (getConfig().count("debugID") != 0u) ? getConfig()["debugID"].asString() : "";
}
#endif
virtual void update() {}
virtual void onDetachedFromNode(Tag /*animatedNodeTag*/) {}
virtual void onAttachToNode(Tag /*animatedNodeTag*/) {}
static std::optional<AnimatedNodeType> getNodeTypeByName(const std::string &nodeTypeName);
int activeIncomingNodes = 0;
int bfsColor = 0;
static constexpr int INITIAL_BFS_COLOR = 0;
protected:
AnimatedNode *getChildNode(Tag tag);
Tag tag_{0};
NativeAnimatedNodesManager *manager_;
AnimatedNodeType type_;
std::unordered_set<Tag> children_{};
private:
// Should remain unchanged after initialized in constructor
folly::dynamic config_;
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ColorAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
uint8_t getColorValue(
const NativeAnimatedNodesManager& manager,
Tag nodeTag,
bool isDecimal = false) {
if (const auto node = manager.getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (isDecimal) {
return std::clamp(
static_cast<uint32_t>(node->getValue() * 255), 0u, 255u);
} else {
return std::clamp(static_cast<uint32_t>(node->getValue()), 0u, 255u);
}
}
return 0;
}
uint8_t getAlphaValue(const NativeAnimatedNodesManager& manager, Tag nodeTag) {
return getColorValue(manager, nodeTag, true);
}
} // namespace
ColorAnimatedNode::ColorAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Color),
rNodeTag_(static_cast<Tag>(getConfig()["r"].asInt())),
gNodeTag_(static_cast<Tag>(getConfig()["g"].asInt())),
bNodeTag_(static_cast<Tag>(getConfig()["b"].asInt())),
aNodeTag_(static_cast<Tag>(getConfig()["a"].asInt())) {}
void ColorAnimatedNode::update() {
color_ = *colorFromRGBA(
getColorValue(*manager_, rNodeTag_),
getColorValue(*manager_, gNodeTag_),
getColorValue(*manager_, bNodeTag_),
getAlphaValue(*manager_, aNodeTag_));
}
Color ColorAnimatedNode::getColor() {
if (manager_->updatedNodeTags_.contains(tag_)) {
update();
manager_->updatedNodeTags_.erase(tag_);
}
return color_;
return 0;
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class ColorAnimatedNode final : public AnimatedNode {
public:
ColorAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
Color getColor();
private:
Tag rNodeTag_{};
Tag gNodeTag_{};
Tag bNodeTag_{};
Tag aNodeTag_{};
Color color_{0};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DiffClampAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
DiffClampAnimatedNode::DiffClampAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asDouble())),
min_(getConfig()["min"].asDouble()),
max_(getConfig()["max"].asDouble()) {}
void DiffClampAnimatedNode::update() {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_)) {
const auto value = node->getValue();
const auto diff = value - lastValue_;
lastValue_ = value;
setRawValue(std::clamp(this->getValue() + diff, min_, max_));
}
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class DiffClampAnimatedNode final : public ValueAnimatedNode {
public:
DiffClampAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_;
double min_;
double max_;
double lastValue_{0};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "DivisionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void DivisionAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for DivisionAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue /= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class DivisionAnimatedNode final : public OperatorAnimatedNode {
public:
DivisionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "InterpolationAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/animated/internal/primitives.h>
#include <react/renderer/graphics/HostPlatformColor.h>
namespace facebook::react {
InterpolationAnimatedNode::InterpolationAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager) {
// inputRange example: [0, 1, 10], [1, 1.4, 1.5]
const auto& nodeConfig = getConfig();
for (const auto& rangeValue : nodeConfig["inputRange"]) {
inputRanges_.push_back(rangeValue.asDouble());
}
const bool isColorOutput = nodeConfig["outputType"].isString() &&
nodeConfig["outputType"].asString() == "color";
if (isColorOutput) {
isColorValue_ = true;
for (const auto& rangeValue : nodeConfig["outputRange"]) {
colorOutputRanges_.push_back(static_cast<int>(rangeValue.asInt()));
}
} else {
for (const auto& rangeValue : nodeConfig["outputRange"]) {
defaultOutputRanges_.push_back(rangeValue.asDouble());
}
}
extrapolateLeft_ = nodeConfig["extrapolateLeft"].asString();
extrapolateRight_ = nodeConfig["extrapolateRight"].asString();
}
void InterpolationAnimatedNode::update() {
if (parentTag_ == animated::undefinedAnimatedNodeIdentifier) {
return;
}
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(parentTag_)) {
if (isColorValue_) {
setRawValue(interpolateColor(node->getValue()));
} else {
setRawValue(interpolateValue(node->getValue()));
}
}
}
void InterpolationAnimatedNode::onDetachedFromNode(Tag animatedNodeTag) {
assert(parentTag_ == animatedNodeTag);
parentTag_ = animated::undefinedAnimatedNodeIdentifier;
}
void InterpolationAnimatedNode::onAttachToNode(Tag animatedNodeTag) {
assert(!parentTag_);
parentTag_ = animatedNodeTag;
}
double InterpolationAnimatedNode::interpolateValue(double value) {
// Compute range index
int index = 1;
for (; index < inputRanges_.size() - 1; ++index) {
if (inputRanges_[index] >= value) {
break;
}
}
index--;
return interpolate(
value,
inputRanges_[index],
inputRanges_[index + 1],
defaultOutputRanges_[index],
defaultOutputRanges_[index + 1],
extrapolateLeft_,
extrapolateRight_);
}
double InterpolationAnimatedNode::interpolateColor(double value) {
// Compute range index
int index = 1;
for (; index < inputRanges_.size() - 1; ++index) {
if (inputRanges_[index] >= value) {
break;
}
}
index--;
const auto outputMin = colorOutputRanges_[index];
const auto outputMax = colorOutputRanges_[index + 1];
if (outputMin == outputMax) {
return outputMin;
}
const auto inputMin = inputRanges_[index];
const auto inputMax = inputRanges_[index + 1];
if (inputMin == inputMax) {
if (value <= inputMin) {
return static_cast<int32_t>(outputMin);
} else {
return static_cast<int32_t>(outputMax);
}
}
auto ratio = (value - inputMin) / (inputMax - inputMin);
auto outputMinA = alphaFromHostPlatformColor(outputMin);
auto outputMinR = redFromHostPlatformColor(outputMin);
auto outputMinG = greenFromHostPlatformColor(outputMin);
auto outputMinB = blueFromHostPlatformColor(outputMin);
auto outputMaxA = alphaFromHostPlatformColor(outputMax);
auto outputMaxR = redFromHostPlatformColor(outputMax);
auto outputMaxG = greenFromHostPlatformColor(outputMax);
auto outputMaxB = blueFromHostPlatformColor(outputMax);
auto outputValueA = ratio * (outputMaxA - outputMinA) + outputMinA;
auto outputValueR = ratio * (outputMaxR - outputMinR) + outputMinR;
auto outputValueG = ratio * (outputMaxG - outputMinG) + outputMinG;
auto outputValueB = ratio * (outputMaxB - outputMinB) + outputMinB;
return static_cast<int32_t>(hostPlatformColorFromRGBA(
static_cast<uint8_t>(outputValueR),
static_cast<uint8_t>(outputValueG),
static_cast<uint8_t>(outputValueB),
static_cast<uint8_t>(outputValueA)));
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
#include <react/renderer/animated/internal/primitives.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class InterpolationAnimatedNode final : public ValueAnimatedNode {
public:
InterpolationAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
void onDetachedFromNode(Tag animatedNodeTag) override;
void onAttachToNode(Tag animatedNodeTag) override;
private:
double interpolateValue(double value);
double interpolateColor(double value);
std::vector<double> inputRanges_;
std::vector<double> defaultOutputRanges_;
std::vector<Color> colorOutputRanges_;
std::string extrapolateLeft_;
std::string extrapolateRight_;
Tag parentTag_{animated::undefinedAnimatedNodeIdentifier};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ModulusAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
ModulusAnimatedNode::ModulusAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asInt())),
modulus_(getConfig()["modulus"].asDouble()) {}
void ModulusAnimatedNode::update() {
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_)) {
setRawValue(std::fmod(node->getValue(), modulus_));
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class ModulusAnimatedNode final : public ValueAnimatedNode {
public:
ModulusAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_{};
double modulus_{};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "MultiplicationAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void MultiplicationAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for MultiplicationAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue *= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
namespace facebook::react {
class MultiplicationAnimatedNode final : public OperatorAnimatedNode {
public:
MultiplicationAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,121 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ObjectAnimatedNode.h"
#include <glog/logging.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/internal/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
ObjectAnimatedNode::ObjectAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Object) {}
void ObjectAnimatedNode::collectViewUpdates(
std::string propKey,
folly::dynamic& props) {
const auto& value = getConfig()["value"];
switch (value.type()) {
case folly::dynamic::OBJECT: {
props.insert(propKey, collectViewUpdatesObjectHelper(value));
} break;
case folly::dynamic::ARRAY: {
props.insert(propKey, collectViewUpdatesArrayHelper(value));
} break;
default: {
LOG(ERROR) << "Invalid value type for ObjectAnimatedNode";
} break;
}
}
folly::dynamic ObjectAnimatedNode::collectViewUpdatesObjectHelper(
const folly::dynamic& value) const {
folly::dynamic result = folly::dynamic::object();
for (const auto& valueProp : value.items()) {
result.insert(valueProp.first.asString(), getValueProp(valueProp.second));
}
return result;
}
folly::dynamic ObjectAnimatedNode::collectViewUpdatesArrayHelper(
const folly::dynamic& value) const {
folly::dynamic result = folly::dynamic::array();
for (const auto& valueProp : value) {
result.push_back(getValueProp(valueProp));
}
return result;
}
folly::dynamic ObjectAnimatedNode::getValueProp(
const folly::dynamic& prop) const {
switch (prop.type()) {
case folly::dynamic::OBJECT: {
if (auto itNodeTag = prop.find("nodeTag");
itNodeTag != prop.items().end()) {
auto nodeTag = static_cast<Tag>(itNodeTag->second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
return static_cast<int32_t>(valueNode->getValue());
} else {
return valueNode->getValue();
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto colorAnimNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
return static_cast<int32_t>(colorAnimNode->getColor());
}
} break;
default:
break;
}
}
} else {
return collectViewUpdatesObjectHelper(prop);
}
} break;
case folly::dynamic::ARRAY: {
return collectViewUpdatesArrayHelper(prop);
};
case folly::dynamic::NULLT:
case folly::dynamic::BOOL:
case folly::dynamic::DOUBLE:
case folly::dynamic::INT64:
case folly::dynamic::STRING: {
return prop;
};
}
LOG(ERROR) << "Invalid prop type for ObjectAnimatedNode";
return nullptr;
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <folly/dynamic.h>
namespace facebook::react {
class ObjectAnimatedNode final : public AnimatedNode {
public:
ObjectAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(std::string propKey, folly::dynamic &props);
private:
folly::dynamic collectViewUpdatesObjectHelper(const folly::dynamic &value) const;
folly::dynamic collectViewUpdatesArrayHelper(const folly::dynamic &value) const;
folly::dynamic getValueProp(const folly::dynamic &prop) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,150 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "PropsAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/animated/nodes/StyleAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
bool isLayoutStyleUpdated(
const folly::dynamic& props,
NativeAnimatedNodesManager& manager) {
for (const auto& entry : props.items()) {
auto nodeTag = static_cast<Tag>(entry.second.asInt());
if (const auto& node = manager.getAnimatedNode<AnimatedNode>(nodeTag)) {
if (node->type() == AnimatedNodeType::Style) {
if (const auto& styleNode =
manager.getAnimatedNode<StyleAnimatedNode>(nodeTag)) {
if (styleNode->isLayoutStyleUpdated()) {
return true;
}
}
}
}
}
return false;
}
} // namespace
PropsAnimatedNode::PropsAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Props),
props_(folly::dynamic::object()) {}
void PropsAnimatedNode::connectToView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier &&
"Animated node has already been attached to a view already exists.");
connectedViewTag_ = viewTag;
}
void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == viewTag &&
"Attempting to disconnect view that has not been connected with the given animated node.");
connectedViewTag_ = animated::undefinedAnimatedNodeIdentifier;
}
// restore the value to whatever the value was on the ShadowNode instead of in
// the View hierarchy
void PropsAnimatedNode::restoreDefaultValues() {
// If node is already disconnected from View, we cannot restore default values
if (connectedViewTag_ != animated::undefinedAnimatedNodeIdentifier) {
manager_->schedulePropsCommit(
connectedViewTag_, folly::dynamic::object(), false, false);
}
}
void PropsAnimatedNode::update() {
return update(false);
}
void PropsAnimatedNode::update(bool forceFabricCommit) {
if (connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier) {
return;
}
// TODO: T190192206 consolidate shared update logic between
// Props/StyleAnimatedNode
std::lock_guard<std::mutex> lock(propsMutex_);
const auto& configProps = getConfig()["props"];
for (const auto& entry : configProps.items()) {
auto propName = entry.first.asString();
auto nodeTag = static_cast<Tag>(entry.second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto& valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
props_.insert(
propName.c_str(),
static_cast<int32_t>(valueNode->getValue()));
} else {
props_.insert(propName.c_str(), valueNode->getValue());
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto& colorNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
props_.insert(
propName.c_str(), static_cast<int32_t>(colorNode->getColor()));
}
} break;
case AnimatedNodeType::Style: {
if (const auto& styleNode =
manager_->getAnimatedNode<StyleAnimatedNode>(nodeTag)) {
styleNode->collectViewUpdates(props_);
}
} break;
case AnimatedNodeType::Object: {
if (const auto objectNode =
manager_->getAnimatedNode<ObjectAnimatedNode>(nodeTag)) {
objectNode->collectViewUpdates(propName, props_);
}
} break;
case AnimatedNodeType::Props:
case AnimatedNodeType::Tracking:
case AnimatedNodeType::Transform:
break;
}
}
}
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
manager_->schedulePropsCommit(
connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <react/renderer/animated/internal/primitives.h>
#include <mutex>
namespace facebook::react {
class PropsAnimatedNode final : public AnimatedNode {
public:
PropsAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void connectToView(Tag viewTag);
void disconnectFromView(Tag viewTag);
void restoreDefaultValues();
Tag connectedViewTag() const
{
return connectedViewTag_;
}
folly::dynamic props()
{
std::lock_guard<std::mutex> lock(propsMutex_);
return props_;
}
void update() override;
void update(bool forceFabricCommit);
private:
std::mutex propsMutex_;
folly::dynamic props_;
bool layoutStyleUpdated_{false};
Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "RoundAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
RoundAnimatedNode::RoundAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager),
inputNodeTag_(static_cast<Tag>(getConfig()["input"].asInt())),
nearest_(getConfig()["input"].asDouble()) {
react_native_assert(
nearest_ != 0 &&
"'nearest' cannot be 0 (can't round to the nearest multiple of 0)");
}
void RoundAnimatedNode::update() {
auto node = manager_->getAnimatedNode<ValueAnimatedNode>(inputNodeTag_);
react_native_assert(
node && "Illegal node ID set as an input for Animated.round node");
setRawValue(round(node->getValue() / nearest_) * nearest_);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "ValueAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
/**
* Animated node that rounds the value of another animated node to the nearest
* multiple of a given number.
*/
class RoundAnimatedNode : public ValueAnimatedNode {
public:
RoundAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
Tag inputNodeTag_;
double nearest_;
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "StyleAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/internal/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
namespace {
bool isLayoutPropsUpdated(const folly::dynamic& props) {
for (const auto& styleNodeProp : props.items()) {
if (getDirectManipulationAllowlist().count(
styleNodeProp.first.asString()) == 0u) {
return true;
}
}
return false;
}
} // namespace
StyleAnimatedNode::StyleAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Style) {}
void StyleAnimatedNode::collectViewUpdates(folly::dynamic& props) {
const auto& style = getConfig()["style"];
for (const auto& styleProp : style.items()) {
auto propName = styleProp.first.asString();
const auto nodeTag = static_cast<Tag>(styleProp.second.asInt());
if (auto node = manager_->getAnimatedNode<AnimatedNode>(nodeTag)) {
switch (node->type()) {
case AnimatedNodeType::Transform: {
if (const auto transformNode =
manager_->getAnimatedNode<TransformAnimatedNode>(nodeTag)) {
transformNode->collectViewUpdates(props);
}
} break;
case AnimatedNodeType::Value:
case AnimatedNodeType::Interpolation:
case AnimatedNodeType::Modulus:
case AnimatedNodeType::Round:
case AnimatedNodeType::Diffclamp:
// Operators
case AnimatedNodeType::Addition:
case AnimatedNodeType::Subtraction:
case AnimatedNodeType::Multiplication:
case AnimatedNodeType::Division: {
if (const auto valueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(nodeTag)) {
if (valueNode->getIsColorValue()) {
props.insert(
propName.c_str(),
static_cast<int32_t>(valueNode->getValue()));
} else {
props.insert(propName.c_str(), valueNode->getValue());
}
}
} break;
case AnimatedNodeType::Color: {
if (const auto colorAnimNode =
manager_->getAnimatedNode<ColorAnimatedNode>(nodeTag)) {
props.insert(
propName.c_str(),
static_cast<int32_t>(colorAnimNode->getColor()));
}
} break;
case AnimatedNodeType::Object: {
if (const auto objectNode =
manager_->getAnimatedNode<ObjectAnimatedNode>(nodeTag)) {
objectNode->collectViewUpdates(propName, props);
}
} break;
case AnimatedNodeType::Tracking:
case AnimatedNodeType::Style:
case AnimatedNodeType::Props:
break;
}
}
}
layoutStyleUpdated_ = isLayoutPropsUpdated(props);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <folly/dynamic.h>
namespace facebook::react {
class StyleAnimatedNode final : public AnimatedNode {
public:
StyleAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(folly::dynamic &props);
bool isLayoutStyleUpdated() const noexcept
{
return layoutStyleUpdated_;
}
private:
bool layoutStyleUpdated_{};
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "SubtractionAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
void SubtractionAnimatedNode::update() {
auto rawValue = 0.0;
int count = 0;
for (const auto& tag : inputNodes_) {
const auto node = manager_->getAnimatedNode<ValueAnimatedNode>(tag);
react_native_assert(
node && "Invalid node tag set as input for SubtractionAnimatedNode");
if (count == 0) {
rawValue = node->getValue();
} else {
rawValue -= node->getValue();
}
count++;
}
setRawValue(rawValue);
}
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include <folly/dynamic.h>
#include "ValueAnimatedNode.h"
namespace facebook::react {
class SubtractionAnimatedNode final : public OperatorAnimatedNode {
public:
SubtractionAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager)
: OperatorAnimatedNode(tag, config, manager)
{
}
void update() override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "TrackingAnimatedNode.h"
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
namespace facebook::react {
TrackingAnimatedNode::TrackingAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Tracking),
animationId_(static_cast<int>(getConfig()["animationId"].asInt())),
toValueNodeId_(static_cast<Tag>(getConfig()["toValue"].asInt())),
valueNodeId_(static_cast<Tag>(getConfig()["value"].asInt())) {}
void TrackingAnimatedNode::update() {
if (const auto toValueNode =
manager_->getAnimatedNode<ValueAnimatedNode>(toValueNodeId_)) {
// In case the animation is already running, we need to stop it to free up
// the animationId key in the active animations map in the animation
// manager.
manager_->stopAnimation(animationId_, true);
auto animationConfig = getConfig()["animationConfig"];
animationConfig["toValue"] = toValueNode->getValue();
manager_->startAnimatingNode(
animationId_, valueNodeId_, animationConfig, std::nullopt);
}
};
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
namespace facebook::react {
class TrackingAnimatedNode final : public AnimatedNode {
public:
TrackingAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void update() override;
private:
int animationId_{};
Tag toValueNodeId_{}; // Value node to be tracked
Tag valueNodeId_{}; // Value node to be updated
};
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "TransformAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>
#include <utility>
namespace facebook::react {
static constexpr std::string_view sTransformsName{"transforms"};
static constexpr std::string_view sPropertyName{"property"};
static constexpr std::string_view sTypeName{"type"};
static constexpr std::string_view sAnimatedName{"animated"};
static constexpr std::string_view sNodeTagName{"nodeTag"};
static constexpr std::string_view sValueName{"value"};
static constexpr std::string_view sTransformPropName{"transform"};
TransformAnimatedNode::TransformAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Transform) {}
void TransformAnimatedNode::collectViewUpdates(folly::dynamic& props) {
folly::dynamic transforms = folly::dynamic::array();
auto transformsArray = getConfig()[sTransformsName];
react_native_assert(transformsArray.type() == folly::dynamic::ARRAY);
for (const auto& transform : transformsArray) {
std::optional<double> value;
if (transform[sTypeName].asString() == sAnimatedName) {
const auto inputTag = static_cast<Tag>(transform[sNodeTagName].asInt());
if (const auto node =
manager_->getAnimatedNode<ValueAnimatedNode>(inputTag)) {
value = node->getValue();
}
} else {
value = transform[sValueName].asDouble();
}
if (value) {
const auto property = transform[sPropertyName].asString();
transforms.push_back(folly::dynamic::object(property, value.value()));
}
}
props[sTransformPropName] = std::move(transforms);
}
} // 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
namespace facebook::react {
struct TransformConfig {
public:
std::string property;
Tag nodeTag;
double value;
};
class TransformAnimatedNode final : public AnimatedNode {
public:
TransformAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
void collectViewUpdates(folly::dynamic &props);
};
} // namespace facebook::react

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#include "ValueAnimatedNode.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
ValueAnimatedNode::ValueAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Value) {
auto value = 0.0;
auto offset = 0.0;
if ((getConfig().count("value") != 0u) &&
(getConfig().count("offset") != 0u)) {
value = getConfig()["value"].asDouble();
offset = getConfig()["offset"].asDouble();
}
value_ = value;
offset_ = offset;
}
bool ValueAnimatedNode::setRawValue(double value) noexcept {
if (value_ != value) {
value_ = value;
onValueUpdate();
return true;
}
return false;
}
double ValueAnimatedNode::getRawValue() const noexcept {
return value_;
}
double ValueAnimatedNode::getOffset() const noexcept {
return offset_;
}
bool ValueAnimatedNode::setOffset(double offset) noexcept {
if (offset_ != offset) {
offset_ = offset;
return true;
}
return true;
}
double ValueAnimatedNode::getValue() const noexcept {
return value_ + getOffset();
}
void ValueAnimatedNode::flattenOffset() noexcept {
value_ = value_ + offset_;
offset_ = 0;
}
void ValueAnimatedNode::extractOffset() noexcept {
offset_ += value_;
value_ = 0;
}
void ValueAnimatedNode::onValueUpdate() noexcept {
if (valueListener_) {
valueListener_(getValue());
}
}
void ValueAnimatedNode::setValueListener(
ValueListenerCallback&& callback) noexcept {
valueListener_ = std::move(callback);
}
OperatorAnimatedNode::OperatorAnimatedNode(
Tag tag,
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: ValueAnimatedNode(tag, config, manager) {
const auto& input = getConfig()["input"];
react_native_assert(
input.type() == folly::dynamic::ARRAY && input.size() >= 2);
for (const auto& inputNode : input) {
const auto inputTag = static_cast<Tag>(inputNode.asInt());
inputNodes_.push_back(inputTag);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
/*
* Adapted from react-native-windows under the MIT license.
*/
#pragma once
#include "AnimatedNode.h"
#include <vector>
namespace facebook::react {
using ValueListenerCallback = std::function<void(double)>;
class ValueAnimatedNode : public AnimatedNode {
public:
ValueAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
double getValue() const noexcept;
double getRawValue() const noexcept;
bool setRawValue(double value) noexcept;
double getOffset() const noexcept;
bool setOffset(double offset) noexcept;
void flattenOffset() noexcept;
void extractOffset() noexcept;
void setValueListener(ValueListenerCallback &&callback) noexcept;
bool getIsColorValue() const noexcept
{
return isColorValue_;
}
protected:
bool isColorValue_{false};
private:
void onValueUpdate() noexcept;
double value_{0.0};
double offset_{0.0};
ValueListenerCallback valueListener_{};
};
class OperatorAnimatedNode : public ValueAnimatedNode {
public:
OperatorAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
protected:
std::vector<Tag> inputNodes_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,268 @@
/*
* 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 "AnimationTestsBase.h"
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/ObjectAnimatedNode.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
#include <react/renderer/graphics/Color.h>
namespace facebook::react {
class AnimatedNodeTests : public AnimationTestsBase {};
TEST_F(AnimatedNodeTests, setAnimatedNodeValue) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto animatedNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
animatedNodeTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 5));
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), true);
runAnimationFrame(0);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), false);
nodesManager_->setAnimatedNodeValue(animatedNodeTag, 100);
// ValueAnimatedNode will immediately update value, before Animated updates
// dirtied nodes at next frame
EXPECT_EQ(nodesManager_->getValue(animatedNodeTag), 105);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), true);
runAnimationFrame(0);
EXPECT_EQ(nodeNeedsUpdate(animatedNodeTag), false);
nodesManager_->dropAnimatedNode(animatedNodeTag);
}
TEST_F(AnimatedNodeTests, updatePropsNode) {
initNodesManager();
// Step 1: Build the Nodes graph
auto rootTag = getNextRootViewTag();
// Create ColorNode
auto rTag = ++rootTag;
auto gTag = ++rootTag;
auto bTag = ++rootTag;
auto aTag = ++rootTag;
auto colorNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
rTag, folly::dynamic::object("type", "value")("value", 0)("offset", 0));
nodesManager_->createAnimatedNode(
gTag, folly::dynamic::object("type", "value")("value", 255)("offset", 0));
nodesManager_->createAnimatedNode(
bTag, folly::dynamic::object("type", "value")("value", 0)("offset", 0));
nodesManager_->createAnimatedNode(
aTag, folly::dynamic::object("type", "value")("value", 0.5)("offset", 0));
nodesManager_->createAnimatedNode(
colorNodeTag,
folly::dynamic::object("type", "color")("r", rTag)("g", gTag)("b", bTag)(
"a", aTag));
nodesManager_->connectAnimatedNodes(rTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(gTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(bTag, colorNodeTag);
nodesManager_->connectAnimatedNodes(aTag, colorNodeTag);
// Create opacity ValueNode
auto opacityNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
opacityNodeTag,
folly::dynamic::object("type", "value")("value", 0.8f)("offset", 0));
// Create StyleNode
auto styleNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
styleNodeTag,
folly::dynamic::object("type", "style")(
"style",
folly::dynamic::object("backgroundColor", colorNodeTag)(
"opacity", opacityNodeTag)));
nodesManager_->connectAnimatedNodes(colorNodeTag, styleNodeTag);
nodesManager_->connectAnimatedNodes(opacityNodeTag, styleNodeTag);
// Create PropsNode
auto propsNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
propsNodeTag,
folly::dynamic::object("type", "props")(
"props", folly::dynamic::object("style", styleNodeTag)));
nodesManager_->connectAnimatedNodes(styleNodeTag, propsNodeTag);
// Connect PropsNode to View
auto viewTag = ++rootTag;
nodesManager_->connectAnimatedNodeToView(propsNodeTag, viewTag);
runAnimationFrame(0);
// Step 2: Update backgroundColor
{
nodesManager_->setAnimatedNodeValue(bTag, 100);
nodesManager_->setAnimatedNodeValue(aTag, 0.3);
// Confirm the nodes graph is correctly marked dirty
EXPECT_EQ(nodeNeedsUpdate(rTag), false);
EXPECT_EQ(nodeNeedsUpdate(gTag), false);
EXPECT_EQ(nodeNeedsUpdate(bTag), true);
EXPECT_EQ(nodeNeedsUpdate(aTag), true);
EXPECT_EQ(nodeNeedsUpdate(opacityNodeTag), false);
// connected style/prop nodes are not marked dirty but they will be updated
// at next render
EXPECT_EQ(nodeNeedsUpdate(styleNodeTag), false);
EXPECT_EQ(nodeNeedsUpdate(propsNodeTag), false);
// Flush changes
runAnimationFrame(0);
// Check props commit done via MountingManager
auto color =
static_cast<Color>(lastCommittedProps["backgroundColor"].asInt());
EXPECT_EQ(redFromColor({color}), 0);
EXPECT_EQ(greenFromColor({color}), 255);
EXPECT_EQ(blueFromColor({color}), 100);
EXPECT_EQ(alphaFromColor({color}), static_cast<uint8_t>(0.3 * 255));
EXPECT_EQ(lastUpdatedNodeTag, viewTag);
}
// Step 3: Update opacity
{
nodesManager_->setAnimatedNodeValue(opacityNodeTag, 0.1f);
// Confirm the nodes graph is correctly marked dirty
EXPECT_EQ(nodeNeedsUpdate(rTag), false);
EXPECT_EQ(nodeNeedsUpdate(gTag), false);
EXPECT_EQ(nodeNeedsUpdate(bTag), false);
EXPECT_EQ(nodeNeedsUpdate(aTag), false);
EXPECT_EQ(nodeNeedsUpdate(opacityNodeTag), true);
// connected style/prop nodes are not marked dirty but they will be updated
// at next render
EXPECT_EQ(nodeNeedsUpdate(styleNodeTag), false);
EXPECT_EQ(nodeNeedsUpdate(propsNodeTag), false);
// Flush changes
runAnimationFrame(0);
// Check props commit done via MountingManager
EXPECT_EQ(lastCommittedProps["opacity"], 0.1f);
EXPECT_EQ(lastUpdatedNodeTag, viewTag);
}
}
TEST_F(AnimatedNodeTests, ModulusAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto moduloTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 1));
nodesManager_->createAnimatedNode(
moduloTag,
folly::dynamic::object("type", "modulus")("input", valueTag)(
"modulus", 3.1));
nodesManager_->connectAnimatedNodes(valueTag, moduloTag);
runAnimationFrame(0);
nodesManager_->setAnimatedNodeValue(valueTag, 4.1);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(valueTag), 5.1);
EXPECT_EQ(nodesManager_->getValue(moduloTag), std::fmod(5.1, 3.1));
nodesManager_->setAnimatedNodeValue(valueTag, 7.6);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(valueTag), 8.6);
EXPECT_EQ(nodesManager_->getValue(moduloTag), std::fmod(8.6, 3.1));
}
TEST_F(AnimatedNodeTests, DiffClampAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto diffClampTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 4)("offset", 0));
nodesManager_->createAnimatedNode(
diffClampTag,
folly::dynamic::object("type", "diffclamp")("input", valueTag)("min", 1)(
"max", 2));
nodesManager_->connectAnimatedNodes(valueTag, diffClampTag);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(diffClampTag), 2);
nodesManager_->setAnimatedNodeValue(valueTag, 2);
runAnimationFrame(0);
EXPECT_EQ(nodesManager_->getValue(diffClampTag), 1);
}
TEST_F(AnimatedNodeTests, ObjectAnimatedNode) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueTag = ++rootTag;
auto objectTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueTag,
folly::dynamic::object("type", "value")("value", 4)("offset", 0));
nodesManager_->createAnimatedNode(
objectTag,
folly::dynamic::object("type", "object")(
"value",
folly::dynamic::array(
folly::dynamic::object(
"translate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)),
folly::dynamic::object(
"rotate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)(
"angle", "180deg")),
folly::dynamic::object(
"scale3d", folly::dynamic::object("nodeTag", valueTag)))));
nodesManager_->connectAnimatedNodes(valueTag, objectTag);
const auto objectNode =
nodesManager_->getAnimatedNode<ObjectAnimatedNode>(objectTag);
folly::dynamic collectedProps = folly::dynamic::object();
objectNode->collectViewUpdates("test", collectedProps);
const auto expected = folly::dynamic::object(
"test",
folly::dynamic::array(
folly::dynamic::object(
"translate3d", folly::dynamic::object("x", 1)("y", 0)("z", 0)),
folly::dynamic::object(
"rotate3d",
folly::dynamic::object("x", 1)("y", 0)("z", 0)(
"angle", "180deg")),
folly::dynamic::object("scale3d", 4)));
EXPECT_EQ(collectedProps["test"].size(), 3);
EXPECT_EQ(collectedProps["test"][0]["translate3d"]["x"], 1);
EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["y"], 0);
EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["angle"], "180deg");
EXPECT_EQ(collectedProps["test"][2]["scale3d"], 4);
}
} // 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.
*/
#include "AnimationTestsBase.h"
#include <react/renderer/animated/drivers/AnimationDriverUtils.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
namespace facebook::react {
class AnimationDriverTests : public AnimationTestsBase {
protected:
double round(double value) noexcept {
// Round to 2 decimal places
return std::ceil(value * 100) / 100;
}
};
TEST_F(AnimationDriverTests, framesAnimation) {
initNodesManager();
auto rootTag = getNextRootViewTag();
auto valueNodeTag = ++rootTag;
nodesManager_->createAnimatedNode(
valueNodeTag,
folly::dynamic::object("type", "value")("value", 0)("offset", 0));
const auto animationId = 1;
const auto frames = folly::dynamic::array(0.0f, 0.1f, 0.4f, 0.9f, 1.0f);
const auto toValue = 100;
nodesManager_->startAnimatingNode(
animationId,
valueNodeTag,
folly::dynamic::object("type", "frames")("frames", frames)(
"toValue", toValue),
std::nullopt);
const double startTimeInTick = 12345;
runAnimationFrame(startTimeInTick);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 0);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 2.5);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 65);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 3);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 90);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 4);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue);
runAnimationFrame(startTimeInTick + SingleFrameIntervalMs * 10);
EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue);
}
} // namespace facebook::react

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.
*/
#pragma once
#ifdef _WIN32
#include <folly/portability/Unistd.h>
#include <folly/portability/Windows.h>
#endif
#include <gtest/gtest.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
namespace facebook::react {
class AnimationTestsBase : public testing::Test {
public:
AnimationTestsBase() = default;
protected:
void initNodesManager() noexcept
{
nodesManager_.reset();
nodesManager_ = std::make_shared<NativeAnimatedNodesManager>(
[this](Tag reactTag, const folly::dynamic &changedProps) {
lastUpdatedNodeTag = reactTag;
lastCommittedProps = changedProps;
},
[this](const std::unordered_map<Tag, folly::dynamic> &nodesProps) {
if (!nodesProps.empty()) {
lastUpdatedNodeTag = nodesProps.begin()->first;
lastCommittedProps = nodesProps.begin()->second;
}
});
NativeAnimatedNodesManager::isOnRenderThread_ = true;
}
bool nodeNeedsUpdate(Tag nodeTag) const
{
return nodesManager_->updatedNodeTags_.contains(nodeTag);
}
void runAnimationFrame(double timestamp)
{
nodesManager_->onAnimationFrame(timestamp);
}
std::shared_ptr<NativeAnimatedNodesManager> nodesManager_;
folly::dynamic lastCommittedProps{folly::dynamic::object()};
Tag lastUpdatedNodeTag{};
};
} // 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.
*/
#include "AnimationTestsBase.h"
#include <react/renderer/components/scrollview/ScrollEvent.h>
#include <react/renderer/core/ReactRootViewTagGenerator.h>
namespace facebook::react {
class EventAnimationDriverTests : public AnimationTestsBase {};
TEST_F(EventAnimationDriverTests, subscribeToViewEvent) {
initNodesManager();
auto tag = getNextRootViewTag();
auto viewTag = ++tag;
const auto animatedValueTag = ++tag;
const auto animatedValueTag2 = ++tag;
const auto eventName = "scroll";
const folly::dynamic valueNodeConfig =
folly::dynamic::object("type", "value")("value", 0)("offset", 0);
// Call onRender once to initialize thread local
nodesManager_->onRender();
{
nodesManager_->createAnimatedNode(animatedValueTag, valueNodeConfig);
folly::dynamic eventMapping =
folly::dynamic::object("animatedValueTag", animatedValueTag)(
"nativeEventPath", folly::dynamic::array("contentOffset", "y"));
nodesManager_->addAnimatedEventToView(viewTag, eventName, eventMapping);
}
{
nodesManager_->createAnimatedNode(animatedValueTag2, valueNodeConfig);
folly::dynamic eventMapping =
folly::dynamic::object("animatedValueTag", animatedValueTag2)(
"nativeEventPath", folly::dynamic::array("zoomScale"));
nodesManager_->addAnimatedEventToView(viewTag, eventName, eventMapping);
}
EXPECT_EQ(nodesManager_->getValue(animatedValueTag), 0);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag2), 0);
auto scrollEvent = std::make_shared<ScrollEvent>();
scrollEvent->contentSize = {.width = 1, .height = 2};
scrollEvent->contentOffset = {.x = 3, .y = 4};
scrollEvent->contentInset = {.left = 5, .top = 6, .right = 7, .bottom = 8};
scrollEvent->containerSize = {.width = 9, .height = 10};
scrollEvent->zoomScale = 11.0f;
const std::string eventType{eventName};
const SharedEventPayload payload = scrollEvent;
(*nodesManager_->getEventEmitterListener())(viewTag, eventName, *scrollEvent);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag), 4);
EXPECT_EQ(nodesManager_->getValue(animatedValueTag2), 11);
}
} // namespace facebook::react