diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index d49a865037a..72a7e3f8d1a 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -488,6 +488,10 @@ void UIManager::sendAccessibilityEvent( } } +UIManagerViewTransitionDelegate* UIManager::getViewTransitionDelegate() const { + return viewTransitionDelegate_; +} + void UIManager::configureNextLayoutAnimation( jsi::Runtime& runtime, const RawValue& config, @@ -708,6 +712,13 @@ void UIManager::setNativeAnimatedDelegate( nativeAnimatedDelegate_ = delegate; } +void UIManager::setViewTransitionDelegate( + UIManagerViewTransitionDelegate* delegate) { + if (ReactNativeFeatureFlags::viewTransitionEnabled()) { + viewTransitionDelegate_ = delegate; + } +} + void UIManager::unstable_setAnimationBackend( std::shared_ptr animationBackend) { animationBackend_ = animationBackend; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 4140457aefb..28b8729e3e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,12 @@ class UIManager final : public ShadowTreeDelegate { void setNativeAnimatedDelegate(std::weak_ptr delegate); + /** + * Sets and gets UIManager's ViewTransition API delegate. + */ + void setViewTransitionDelegate(UIManagerViewTransitionDelegate *delegate); + UIManagerViewTransitionDelegate *getViewTransitionDelegate() const; + void animationTick() const; void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props); @@ -242,6 +249,7 @@ class UIManager final : public ShadowTreeDelegate { UIManagerDelegate *delegate_{}; UIManagerAnimationDelegate *animationDelegate_{nullptr}; std::weak_ptr nativeAnimatedDelegate_; + UIManagerViewTransitionDelegate *viewTransitionDelegate_{nullptr}; const RuntimeExecutor runtimeExecutor_{}; ShadowTreeRegistry shadowTreeRegistry_{}; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index f5494cc4cd6..023b3704a9c 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -876,6 +876,256 @@ jsi::Value UIManagerBinding::get( }); } + if (methodName == "measureInstance") { + auto paramCount = 1; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + if (!arguments[0].isObject()) { + auto result = jsi::Object(runtime); + result.setProperty(runtime, "x", 0); + result.setProperty(runtime, "y", 0); + result.setProperty(runtime, "width", 0); + result.setProperty(runtime, "height", 0); + return result; + } + + auto shadowNode = Bridging>::fromJs( + runtime, arguments[0]); + + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); + + if (currentRevision == nullptr) { + auto result = jsi::Object(runtime); + result.setProperty(runtime, "x", 0); + result.setProperty(runtime, "y", 0); + result.setProperty(runtime, "width", 0); + result.setProperty(runtime, "height", 0); + return result; + } + + auto domRect = dom::getBoundingClientRect( + currentRevision, *shadowNode, true /* includeTransform */); + + auto result = jsi::Object(runtime); + result.setProperty(runtime, "x", domRect.x); + result.setProperty(runtime, "y", domRect.y); + result.setProperty(runtime, "width", domRect.width); + result.setProperty(runtime, "height", domRect.height); + + auto* viewTransitionDelegate = uiManager->getViewTransitionDelegate(); + if (viewTransitionDelegate != nullptr) { + viewTransitionDelegate->captureLayoutMetricsFromRoot(shadowNode); + } + + return result; + }); + } + + if (methodName == "applyViewTransitionName") { + auto paramCount = 3; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + if (arguments[0].isObject()) { + auto shadowNode = + Bridging>::fromJs( + runtime, arguments[0]); + auto transitionName = arguments[1].isString() + ? stringFromValue(runtime, arguments[1]) + : ""; + auto className = arguments[2].isString() + ? stringFromValue(runtime, arguments[2]) + : ""; + if (!transitionName.empty()) { + auto* viewTransitionDelegate = + uiManager->getViewTransitionDelegate(); + if (viewTransitionDelegate != nullptr) { + viewTransitionDelegate->applyViewTransitionName( + shadowNode, transitionName, className); + } + } + } + + return jsi::Value::undefined(); + }); + } + + if (methodName == "cancelViewTransitionName") { + auto paramCount = 2; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + if (arguments[0].isObject()) { + auto shadowNode = + Bridging>::fromJs( + runtime, arguments[0]); + auto transitionName = arguments[1].isString() + ? stringFromValue(runtime, arguments[1]) + : ""; + if (!transitionName.empty()) { + auto* viewTransitionDelegate = + uiManager->getViewTransitionDelegate(); + if (viewTransitionDelegate != nullptr) { + viewTransitionDelegate->cancelViewTransitionName( + shadowNode, transitionName); + } + } + } + + return jsi::Value::undefined(); + }); + } + + if (methodName == "restoreViewTransitionName") { + auto paramCount = 1; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + if (arguments[0].isObject()) { + auto shadowNode = + Bridging>::fromJs( + runtime, arguments[0]); + auto* viewTransitionDelegate = + uiManager->getViewTransitionDelegate(); + if (viewTransitionDelegate != nullptr) { + viewTransitionDelegate->restoreViewTransitionName(shadowNode); + } + } + + return jsi::Value::undefined(); + }); + } + + if (methodName == "startViewTransition") { + auto paramCount = 1; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + auto* viewTransitionDelegate = uiManager->getViewTransitionDelegate(); + if (viewTransitionDelegate == nullptr) { + return jsi::Value::undefined(); + } + + auto promiseConstructor = + runtime.global().getPropertyAsFunction(runtime, "Promise"); + + auto readyResolveFunc = + std::make_shared>(); + auto finishedResolveFunc = + std::make_shared>(); + + auto mutationFunc = std::make_shared( + arguments[0].asObject(runtime).asFunction(runtime)); + + auto readyPromise = promiseConstructor.callAsConstructor( + runtime, + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "readyExecutor"), + 2, + [readyResolveFunc]( + jsi::Runtime& runtime, + const jsi::Value& /*thisValue*/, + const jsi::Value* args, + size_t /*count*/) -> jsi::Value { + auto onReadyFunc = std::make_shared( + args[0].asObject(runtime).asFunction(runtime)); + *readyResolveFunc = onReadyFunc; + return jsi::Value::undefined(); + })); + + auto finishedPromise = promiseConstructor.callAsConstructor( + runtime, + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "finishedExecutor"), + 2, + [finishedResolveFunc]( + jsi::Runtime& rt, + const jsi::Value& /*thisValue*/, + const jsi::Value* args, + size_t /*count*/) -> jsi::Value { + auto onCompleteFunc = std::make_shared( + args[0].asObject(rt).asFunction(rt)); + *finishedResolveFunc = onCompleteFunc; + return jsi::Value::undefined(); + })); + + auto result = jsi::Object(runtime); + result.setProperty(runtime, "ready", std::move(readyPromise)); + result.setProperty(runtime, "finished", std::move(finishedPromise)); + + viewTransitionDelegate->startViewTransition( + [&runtime, mutationFunc = std::move(mutationFunc)]() { + mutationFunc->call(runtime); + }, + [readyResolveFunc = std::move(readyResolveFunc), uiManager]() { + uiManager->runtimeExecutor_( + [readyResolveFunc = + readyResolveFunc](jsi::Runtime& rt) mutable { + if (*readyResolveFunc) { + (*readyResolveFunc)->call(rt); + } + }); + }, + [finishedResolveFunc = std::move(finishedResolveFunc), + uiManager]() { + uiManager->runtimeExecutor_( + [finishedResolveFunc = + finishedResolveFunc](jsi::Runtime& rt) mutable { + if (*finishedResolveFunc) { + (*finishedResolveFunc)->call(rt); + } + }); + }); + + return jsi::Value(runtime, result); + }); + } + return jsi::Value::undefined(); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h new file mode 100644 index 00000000000..3a9615e25e5 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h @@ -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 +#include + +namespace facebook::react { + +class UIManagerViewTransitionDelegate { + public: + virtual ~UIManagerViewTransitionDelegate() = default; + + virtual void applyViewTransitionName( + const std::shared_ptr &shadowNode, + const std::string &name, + const std::string &className) + { + } + + virtual void cancelViewTransitionName(const std::shared_ptr &shadowNode, const std::string &name) {} + + virtual void restoreViewTransitionName(const std::shared_ptr &shadowNode) {} + + virtual void captureLayoutMetricsFromRoot(const std::shared_ptr &shadowNode) {} + + virtual void startViewTransition( + std::function mutationCallback, + std::function onReadyCallback, + std::function onCompleteCallback) + { + } +}; + +} // namespace facebook::react