Skip to content

Commit 5a3edbc

Browse files
zeyapfacebook-github-bot
authored andcommitted
Add UIManagerViewTransitionDelegate interface and View Transition APIs
Summary: Changelog: [General] [Added] - Add UIManagerViewTransitionDelegate interface and View Transition APIs Adds `UIManagerViewTransitionDelegate` interface and View Transition APIs to UIManager, enabling integration with React reconciler's `<ViewTransition/>` component. Exposes JSI bindings in UIManagerBinding, which will be consumed by react fabric renderer (facebook/react#35764) - `measureInstance` - returns layout metrics and calls `captureLayoutMetricsFromRoot` to capture snapshot - `applyViewTransitionName` / `cancelViewTransitionName` / `restoreViewTransitionName` - manage transition name registration - `startViewTransition` - orchestrates transition lifecycle with mutation/ready/complete callbacks The delegate methods are gated by the `viewTransitionEnabled` feature flag. Reviewed By: sammy-SC Differential Revision: D92537193
1 parent 117f3c9 commit 5a3edbc

File tree

4 files changed

+355
-0
lines changed

4 files changed

+355
-0
lines changed

packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,52 @@ void UIManager::sendAccessibilityEvent(
486486
}
487487
}
488488

489+
void UIManager::captureLayoutMetricsFromRoot(
490+
const std::shared_ptr<const ShadowNode>& shadowNode) const {
491+
if (viewTransitionDelegate_ != nullptr) {
492+
viewTransitionDelegate_->captureLayoutMetricsFromRoot(shadowNode);
493+
}
494+
}
495+
496+
void UIManager::applyViewTransitionName(
497+
const std::shared_ptr<const ShadowNode>& shadowNode,
498+
const std::string& name,
499+
const std::string& className) const {
500+
if (viewTransitionDelegate_ != nullptr) {
501+
viewTransitionDelegate_->applyViewTransitionName(
502+
shadowNode, name, className);
503+
}
504+
}
505+
506+
void UIManager::cancelViewTransitionName(
507+
const std::shared_ptr<const ShadowNode>& shadowNode,
508+
const std::string& name) const {
509+
if (viewTransitionDelegate_ != nullptr) {
510+
viewTransitionDelegate_->cancelViewTransitionName(shadowNode, name);
511+
}
512+
}
513+
514+
void UIManager::restoreViewTransitionName(
515+
const std::shared_ptr<const ShadowNode>& shadowNode) const {
516+
if (viewTransitionDelegate_ != nullptr) {
517+
viewTransitionDelegate_->restoreViewTransitionName(shadowNode);
518+
}
519+
}
520+
521+
bool UIManager::startViewTransition(
522+
std::function<void()> mutationCallback,
523+
std::function<void()> onReadyCallback,
524+
std::function<void()> onCompleteCallback) const {
525+
if (viewTransitionDelegate_ != nullptr) {
526+
viewTransitionDelegate_->startViewTransition(
527+
std::move(mutationCallback),
528+
std::move(onReadyCallback),
529+
std::move(onCompleteCallback));
530+
return true;
531+
}
532+
return false;
533+
}
534+
489535
void UIManager::configureNextLayoutAnimation(
490536
jsi::Runtime& runtime,
491537
const RawValue& config,
@@ -696,6 +742,13 @@ void UIManager::setNativeAnimatedDelegate(
696742
nativeAnimatedDelegate_ = delegate;
697743
}
698744

745+
void UIManager::setViewTransitionDelegate(
746+
UIManagerViewTransitionDelegate* delegate) {
747+
if (ReactNativeFeatureFlags::viewTransitionEnabled()) {
748+
viewTransitionDelegate_ = delegate;
749+
}
750+
}
751+
699752
void UIManager::unstable_setAnimationBackend(
700753
std::shared_ptr<UIManagerAnimationBackend> animationBackend) {
701754
animationBackend_ = animationBackend;

packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
2828
#include <react/renderer/uimanager/UIManagerDelegate.h>
2929
#include <react/renderer/uimanager/UIManagerNativeAnimatedDelegate.h>
30+
#include <react/renderer/uimanager/UIManagerViewTransitionDelegate.h>
3031
#include <react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h>
3132
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
3233
#include <react/renderer/uimanager/primitives.h>
@@ -74,6 +75,8 @@ class UIManager final : public ShadowTreeDelegate {
7475

7576
void setNativeAnimatedDelegate(std::weak_ptr<UIManagerNativeAnimatedDelegate> delegate);
7677

78+
void setViewTransitionDelegate(UIManagerViewTransitionDelegate *delegate);
79+
7780
void animationTick() const;
7881

7982
void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props);
@@ -191,6 +194,25 @@ class UIManager final : public ShadowTreeDelegate {
191194

192195
void sendAccessibilityEvent(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &eventType);
193196

197+
/*
198+
* View Transition APIs
199+
*/
200+
void captureLayoutMetricsFromRoot(const std::shared_ptr<const ShadowNode> &shadowNode) const;
201+
202+
void applyViewTransitionName(
203+
const std::shared_ptr<const ShadowNode> &shadowNode,
204+
const std::string &name,
205+
const std::string &className) const;
206+
207+
void cancelViewTransitionName(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &name) const;
208+
209+
void restoreViewTransitionName(const std::shared_ptr<const ShadowNode> &shadowNode) const;
210+
211+
bool startViewTransition(
212+
std::function<void()> mutationCallback,
213+
std::function<void()> onReadyCallback,
214+
std::function<void()> onCompleteCallback) const;
215+
194216
/*
195217
* Iterates over all shadow nodes which are parts of all registered surfaces
196218
* and find the one that has given `tag`. Returns `nullptr` if the node
@@ -240,6 +262,7 @@ class UIManager final : public ShadowTreeDelegate {
240262
UIManagerDelegate *delegate_{};
241263
UIManagerAnimationDelegate *animationDelegate_{nullptr};
242264
std::weak_ptr<UIManagerNativeAnimatedDelegate> nativeAnimatedDelegate_;
265+
UIManagerViewTransitionDelegate *viewTransitionDelegate_{nullptr};
243266

244267
const RuntimeExecutor runtimeExecutor_{};
245268
ShadowTreeRegistry shadowTreeRegistry_{};

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,248 @@ jsi::Value UIManagerBinding::get(
876876
});
877877
}
878878

879+
if (methodName == "measureInstance") {
880+
auto paramCount = 1;
881+
return jsi::Function::createFromHostFunction(
882+
runtime,
883+
name,
884+
paramCount,
885+
[uiManager, methodName, paramCount](
886+
jsi::Runtime& runtime,
887+
const jsi::Value& /*thisValue*/,
888+
const jsi::Value* arguments,
889+
size_t count) -> jsi::Value {
890+
validateArgumentCount(runtime, methodName, paramCount, count);
891+
892+
if (!arguments[0].isObject()) {
893+
auto result = jsi::Object(runtime);
894+
result.setProperty(runtime, "x", 0);
895+
result.setProperty(runtime, "y", 0);
896+
result.setProperty(runtime, "width", 0);
897+
result.setProperty(runtime, "height", 0);
898+
return result;
899+
}
900+
901+
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
902+
runtime, arguments[0]);
903+
904+
auto currentRevision =
905+
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
906+
shadowNode->getSurfaceId());
907+
908+
if (currentRevision == nullptr) {
909+
auto result = jsi::Object(runtime);
910+
result.setProperty(runtime, "x", 0);
911+
result.setProperty(runtime, "y", 0);
912+
result.setProperty(runtime, "width", 0);
913+
result.setProperty(runtime, "height", 0);
914+
return result;
915+
}
916+
917+
auto domRect = dom::getBoundingClientRect(
918+
currentRevision, *shadowNode, true /* includeTransform */);
919+
920+
auto result = jsi::Object(runtime);
921+
result.setProperty(runtime, "x", domRect.x);
922+
result.setProperty(runtime, "y", domRect.y);
923+
result.setProperty(runtime, "width", domRect.width);
924+
result.setProperty(runtime, "height", domRect.height);
925+
926+
uiManager->captureLayoutMetricsFromRoot(shadowNode);
927+
928+
return result;
929+
});
930+
}
931+
932+
if (methodName == "applyViewTransitionName") {
933+
auto paramCount = 3;
934+
return jsi::Function::createFromHostFunction(
935+
runtime,
936+
name,
937+
paramCount,
938+
[uiManager, methodName, paramCount](
939+
jsi::Runtime& runtime,
940+
const jsi::Value& /*thisValue*/,
941+
const jsi::Value* arguments,
942+
size_t count) -> jsi::Value {
943+
validateArgumentCount(runtime, methodName, paramCount, count);
944+
945+
if (arguments[0].isObject()) {
946+
auto shadowNode =
947+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
948+
runtime, arguments[0]);
949+
auto transitionName = arguments[1].isString()
950+
? stringFromValue(runtime, arguments[1])
951+
: "";
952+
auto className = arguments[2].isString()
953+
? stringFromValue(runtime, arguments[2])
954+
: "";
955+
if (!transitionName.empty()) {
956+
uiManager->applyViewTransitionName(
957+
shadowNode, transitionName, className);
958+
}
959+
}
960+
961+
return jsi::Value::undefined();
962+
});
963+
}
964+
965+
if (methodName == "cancelViewTransitionName") {
966+
auto paramCount = 2;
967+
return jsi::Function::createFromHostFunction(
968+
runtime,
969+
name,
970+
paramCount,
971+
[uiManager, methodName, paramCount](
972+
jsi::Runtime& runtime,
973+
const jsi::Value& /*thisValue*/,
974+
const jsi::Value* arguments,
975+
size_t count) -> jsi::Value {
976+
validateArgumentCount(runtime, methodName, paramCount, count);
977+
978+
if (arguments[0].isObject()) {
979+
auto shadowNode =
980+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
981+
runtime, arguments[0]);
982+
auto transitionName = arguments[1].isString()
983+
? stringFromValue(runtime, arguments[1])
984+
: "";
985+
if (!transitionName.empty()) {
986+
uiManager->cancelViewTransitionName(shadowNode, transitionName);
987+
}
988+
}
989+
990+
return jsi::Value::undefined();
991+
});
992+
}
993+
994+
if (methodName == "restoreViewTransitionName") {
995+
auto paramCount = 1;
996+
return jsi::Function::createFromHostFunction(
997+
runtime,
998+
name,
999+
paramCount,
1000+
[uiManager, methodName, paramCount](
1001+
jsi::Runtime& runtime,
1002+
const jsi::Value& /*thisValue*/,
1003+
const jsi::Value* arguments,
1004+
size_t count) -> jsi::Value {
1005+
validateArgumentCount(runtime, methodName, paramCount, count);
1006+
1007+
if (arguments[0].isObject()) {
1008+
auto shadowNode =
1009+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
1010+
runtime, arguments[0]);
1011+
uiManager->restoreViewTransitionName(shadowNode);
1012+
}
1013+
1014+
return jsi::Value::undefined();
1015+
});
1016+
}
1017+
1018+
if (methodName == "startViewTransition") {
1019+
return jsi::Function::createFromHostFunction(
1020+
runtime,
1021+
name,
1022+
3,
1023+
[uiManager](
1024+
jsi::Runtime& runtime,
1025+
const jsi::Value& /*thisValue*/,
1026+
const jsi::Value* arguments,
1027+
size_t count) -> jsi::Value {
1028+
if (count < 3) {
1029+
throw std::invalid_argument(
1030+
"startViewTransition requires 3 callback arguments");
1031+
}
1032+
1033+
auto promiseConstructor =
1034+
runtime.global().getPropertyAsFunction(runtime, "Promise");
1035+
1036+
auto readyResolveFunc =
1037+
std::make_shared<std::shared_ptr<jsi::Function>>();
1038+
auto finishedResolveFunc =
1039+
std::make_shared<std::shared_ptr<jsi::Function>>();
1040+
1041+
auto readyPromise = promiseConstructor.callAsConstructor(
1042+
runtime,
1043+
jsi::Function::createFromHostFunction(
1044+
runtime,
1045+
jsi::PropNameID::forAscii(runtime, "readyExecutor"),
1046+
2,
1047+
[readyResolveFunc](
1048+
jsi::Runtime& rt,
1049+
const jsi::Value& /*thisValue*/,
1050+
const jsi::Value* args,
1051+
size_t /*count*/) -> jsi::Value {
1052+
*readyResolveFunc = std::make_shared<jsi::Function>(
1053+
args[0].asObject(rt).asFunction(rt));
1054+
return jsi::Value::undefined();
1055+
}));
1056+
1057+
auto finishedPromise = promiseConstructor.callAsConstructor(
1058+
runtime,
1059+
jsi::Function::createFromHostFunction(
1060+
runtime,
1061+
jsi::PropNameID::forAscii(runtime, "finishedExecutor"),
1062+
2,
1063+
[finishedResolveFunc](
1064+
jsi::Runtime& rt,
1065+
const jsi::Value& /*thisValue*/,
1066+
const jsi::Value* args,
1067+
size_t /*count*/) -> jsi::Value {
1068+
*finishedResolveFunc = std::make_shared<jsi::Function>(
1069+
args[0].asObject(rt).asFunction(rt));
1070+
return jsi::Value::undefined();
1071+
}));
1072+
1073+
auto result = jsi::Object(runtime);
1074+
result.setProperty(runtime, "ready", std::move(readyPromise));
1075+
result.setProperty(runtime, "finished", std::move(finishedPromise));
1076+
1077+
auto mutationFunc = std::make_shared<jsi::Function>(
1078+
arguments[0].asObject(runtime).asFunction(runtime));
1079+
auto onReadyFunc = std::make_shared<jsi::Function>(
1080+
arguments[1].asObject(runtime).asFunction(runtime));
1081+
auto onCompleteFunc = std::make_shared<jsi::Function>(
1082+
arguments[2].asObject(runtime).asFunction(runtime));
1083+
1084+
return uiManager->startViewTransition(
1085+
[&runtime, mutationFunc = std::move(mutationFunc)]() {
1086+
mutationFunc->call(runtime);
1087+
},
1088+
[&runtime,
1089+
onReadyFunc = std::move(onReadyFunc),
1090+
readyResolveFunc = std::move(readyResolveFunc),
1091+
uiManager]() {
1092+
uiManager->runtimeExecutor_(
1093+
[onReadyFunc = std::move(onReadyFunc),
1094+
readyResolveFunc = std::move(readyResolveFunc)](
1095+
jsi::Runtime& runtime) mutable {
1096+
onReadyFunc->call(runtime);
1097+
if (*readyResolveFunc) {
1098+
(*readyResolveFunc)->call(runtime);
1099+
}
1100+
});
1101+
},
1102+
[&runtime,
1103+
onCompleteFunc = std::move(onCompleteFunc),
1104+
finishedResolveFunc = std::move(finishedResolveFunc),
1105+
uiManager]() {
1106+
uiManager->runtimeExecutor_(
1107+
[onCompleteFunc = std::move(onCompleteFunc),
1108+
finishedResolveFunc = std::move(finishedResolveFunc)](
1109+
jsi::Runtime& runtime) mutable {
1110+
onCompleteFunc->call(runtime);
1111+
if (*finishedResolveFunc) {
1112+
(*finishedResolveFunc)->call(runtime);
1113+
}
1114+
});
1115+
});
1116+
1117+
return jsi::Value(runtime, result);
1118+
});
1119+
}
1120+
8791121
return jsi::Value::undefined();
8801122
}
8811123

0 commit comments

Comments
 (0)