Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,8 @@ Scheduler::Scheduler(

// Initialize ViewTransitionModule
if (ReactNativeFeatureFlags::viewTransitionEnabled()) {
viewTransitionModule_ = std::make_unique<ViewTransitionModule>();
viewTransitionModule_->setUIManager(uiManager_.get());
uiManager_->setViewTransitionDelegate(viewTransitionModule_.get());
viewTransitionModule_ = std::make_shared<ViewTransitionModule>();
viewTransitionModule_->initialize(uiManager_.get(), viewTransitionModule_);
}

uiManager->registerMountHook(*eventPerformanceLogger_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class Scheduler final : public UIManagerDelegate {

RuntimeScheduler *runtimeScheduler_{nullptr};

std::unique_ptr<ViewTransitionModule> viewTransitionModule_;
std::shared_ptr<ViewTransitionModule> viewTransitionModule_;

mutable std::shared_mutex onSurfaceStartCallbackMutex_;
OnSurfaceStartCallback onSurfaceStartCallback_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,37 @@ jsi::Value UIManagerBinding::get(
});
}

if (methodName == "createViewTransitionInstance") {
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);

auto transitionName = arguments[0].isString()
? stringFromValue(runtime, arguments[0])
: "";
auto pseudoElementTag = tagFromValue(arguments[1]);

if (!transitionName.empty()) {
auto* viewTransitionDelegate =
uiManager->getViewTransitionDelegate();
if (viewTransitionDelegate != nullptr) {
viewTransitionDelegate->createViewTransitionInstance(
transitionName, pseudoElementTag);
}
}

return jsi::Value::undefined();
});
}

if (methodName == "cancelViewTransitionName") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class UIManagerViewTransitionDelegate {
{
}

virtual void createViewTransitionInstance(const std::string & /*name*/, Tag /*pseudoElementTag*/) {}

virtual void cancelViewTransitionName(const ShadowNode &shadowNode, const std::string &name) {}

virtual void restoreViewTransitionName(const ShadowNode &shadowNode) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,51 @@

#include "ViewTransitionModule.h"

#include <glog/logging.h>

#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowTree.h>
#include <react/renderer/uimanager/UIManager.h>

namespace facebook::react {

void ViewTransitionModule::setUIManager(UIManager* uiManager) {
ViewTransitionModule::~ViewTransitionModule() {
if (uiManager_ != nullptr) {
if (uiManager_->getViewTransitionDelegate() == this) {
uiManager_->setViewTransitionDelegate(nullptr);
}
uiManager_->unregisterCommitHook(*this);
uiManager_ = nullptr;
}
}

void ViewTransitionModule::initialize(
UIManager* uiManager,
std::weak_ptr<ViewTransitionModule> weakThis) {
if (uiManager_ != nullptr) {
uiManager_->unregisterCommitHook(*this);
}
uiManager_ = uiManager;
if (uiManager_ != nullptr) {
uiManager_->registerCommitHook(*this);

// Register as MountingOverrideDelegate on existing surfaces
uiManager_->getShadowTreeRegistry().enumerate(
[weakThis](const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
weakThis);
});

// Register on surfaces started in the future
uiManager_->setOnSurfaceStartCallback(
[weakThis](const ShadowTree& shadowTree) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
weakThis);
});

uiManager_->setViewTransitionDelegate(this);
}
}

void ViewTransitionModule::applyViewTransitionName(
Expand Down Expand Up @@ -45,13 +81,152 @@ void ViewTransitionModule::applyViewTransitionName(
AnimationKeyFrameView oldView{
.layoutMetrics = keyframeMetrics, .tag = tag, .surfaceId = surfaceId};
oldLayout_[name] = oldView;

// TODO: capture bitmap snapshot of old view via platform

if (auto it = oldPseudoElementNodesRepository_.find(name);
it != oldPseudoElementNodesRepository_.end()) {
oldPseudoElementNodes_[name] = it->second.node;
}

} else {
AnimationKeyFrameView newView{
.layoutMetrics = keyframeMetrics, .tag = tag, .surfaceId = surfaceId};
newLayout_[name] = newView;
}
}

void ViewTransitionModule::createViewTransitionInstance(
const std::string& name,
Tag pseudoElementTag) {
if (uiManager_ == nullptr) {
return;
}

// if createViewTransitionInstance is called before transition started, it
// creates the old pseudo elements for exiting nodes that potentially
// participate in current transition that's about to happen; if called after
// transition started, it creates old pseudo elements for entering nodes, and
// will be used in next transition when these node are exiting
bool forNextTransition = false;
AnimationKeyFrameView view = {};
auto it = oldLayout_.find(name);
if (it == oldLayout_.end()) {
forNextTransition = true;
if (auto newIt = newLayout_.find(name); newIt != newLayout_.end()) {
view = newIt->second;
}
} else {
view = it->second;
}

// Build props: absolute position matching old element, non-interactive
if (pseudoElementTag > 0 && view.tag > 0) {
// Create a base node with layout props via createNode
// TODO: T262559684 created dedicated shadow node type for old pseudo
// element
auto rawProps = RawProps(
folly::dynamic::object("position", "absolute")(
"left", view.layoutMetrics.originFromRoot.x)(
"top", view.layoutMetrics.originFromRoot.y)(
"width", view.layoutMetrics.size.width)(
"height", view.layoutMetrics.size.height)("pointerEvents", "none")(
"opacity", 0)("collapsable", false));

auto baseNode = uiManager_->createNode(
pseudoElementTag,
"View",
view.surfaceId,
std::move(rawProps),
nullptr /* instanceHandle */);

if (baseNode == nullptr) {
return;
}

// Clone the shadow node — bitmap will be set by platform
auto pseudoElementNode = baseNode->clone({});

if (pseudoElementNode != nullptr) {
if (!forNextTransition) {
oldPseudoElementNodes_[name] = pseudoElementNode;
}
oldPseudoElementNodesRepository_[name] = InactivePseudoElement{
.node = pseudoElementNode, .sourceTag = view.tag};
}
}
}

RootShadowNode::Unshared ViewTransitionModule::shadowTreeWillCommit(
const ShadowTree& shadowTree,
const RootShadowNode::Shared& /*oldRootShadowNode*/,
const RootShadowNode::Unshared& newRootShadowNode,
const ShadowTreeCommitOptions& /*commitOptions*/) noexcept {
if (oldPseudoElementNodes_.empty()) {
return newRootShadowNode;
}

auto surfaceId = shadowTree.getSurfaceId();

// Collect pseudo-element nodes for this surface, skipping any that are
// already present in the children list (from a previous commit hook run).
const auto& existingChildren = newRootShadowNode->getChildren();
std::unordered_set<Tag> existingTags;
existingTags.reserve(existingChildren.size());
for (const auto& child : existingChildren) {
existingTags.insert(child->getTag());
}

auto newChildren =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
existingChildren);
bool appended = false;
for (const auto& [name, node] : oldPseudoElementNodes_) {
if (node->getSurfaceId() == surfaceId &&
existingTags.find(node->getTag()) == existingTags.end()) {
newChildren->push_back(node);
appended = true;
}
}

if (!appended) {
return newRootShadowNode;
}

return std::make_shared<RootShadowNode>(
*newRootShadowNode,
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = newChildren,
});
}

bool ViewTransitionModule::shouldOverridePullTransaction() const {
return !oldPseudoElementNodesRepository_.empty();
}

std::optional<MountingTransaction> ViewTransitionModule::pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number number,
const TransactionTelemetry& telemetry,
ShadowViewMutationList mutations) const {
for (const auto& mutation : mutations) {
if (mutation.type == ShadowViewMutation::Delete) {
auto tag = mutation.oldChildShadowView.tag;
for (auto it = oldPseudoElementNodesRepository_.begin();
it != oldPseudoElementNodesRepository_.end();) {
if (it->second.sourceTag == tag) {
it = oldPseudoElementNodesRepository_.erase(it);
} else {
++it;
}
}
}
}
return MountingTransaction{
surfaceId, number, std::move(mutations), telemetry};
}

void ViewTransitionModule::cancelViewTransitionName(
const ShadowNode& shadowNode,
const std::string& name) {
Expand All @@ -67,6 +242,14 @@ void ViewTransitionModule::restoreViewTransitionName(
cancelledNameRegistry_.erase(shadowNode.getTag());
}

void ViewTransitionModule::applySnapshotsOnPseudoElementShadowNodes() {
if (oldPseudoElementNodes_.empty() || uiManager_ == nullptr) {
return;
}

// TODO: set bitmap snapshots on pseudo-element views via platform
}

LayoutMetrics ViewTransitionModule::captureLayoutMetricsFromRoot(
const ShadowNode& shadowNode) {
if (uiManager_ == nullptr) {
Expand Down Expand Up @@ -100,13 +283,13 @@ void ViewTransitionModule::startViewTransition(
// Mark transition as started
transitionStarted_ = true;

// Call mutation callback (including commitRoot, measureInstance
// applyViewTransitionName for old & new)
// Call mutation callback (including commitRoot, measureInstance,
// applyViewTransitionName, createViewTransitionInstance for old & new)
if (mutationCallback) {
mutationCallback();
}

// TODO: capture pseudo elements
applySnapshotsOnPseudoElementShadowNodes();

if (onReadyCallback) {
onReadyCallback();
Expand All @@ -128,6 +311,7 @@ void ViewTransitionModule::startViewTransitionEnd() {
}
}
nameRegistry_.clear();
oldPseudoElementNodes_.clear();

transitionStarted_ = false;
}
Expand All @@ -152,12 +336,16 @@ ViewTransitionModule::getViewTransitionInstance(
auto it = oldLayout_.find(name);
if (it != oldLayout_.end()) {
const auto& view = it->second;
auto pseudoElementIt = oldPseudoElementNodes_.find(name);
auto nativeTag = pseudoElementIt != oldPseudoElementNodes_.end()
? pseudoElementIt->second->getTag()
: view.tag;
return ViewTransitionInstance{
.x = view.layoutMetrics.originFromRoot.x,
.y = view.layoutMetrics.originFromRoot.y,
.width = view.layoutMetrics.size.width,
.height = view.layoutMetrics.size.height,
.nativeTag = view.tag};
.nativeTag = nativeTag};
}
}

Expand Down
Loading
Loading