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 @@ -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,9 +7,8 @@

#include "ViewTransitionModule.h"

#include <glog/logging.h>

#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/uimanager/UIManager.h>

namespace facebook::react {
Expand Down Expand Up @@ -45,13 +44,84 @@ 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 = oldPseudoElementNodesForNextTransition_.find(name);
it != oldPseudoElementNodesForNextTransition_.end()) {
auto pseudoElementNode = it->second;
oldPseudoElementNodes_[name] = pseudoElementNode;
oldPseudoElementNodesForNextTransition_.erase(it);
}

} 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;
} else {
oldPseudoElementNodesForNextTransition_[name] = pseudoElementNode;
}
}
}
}

void ViewTransitionModule::cancelViewTransitionName(
const ShadowNode& shadowNode,
const std::string& name) {
Expand All @@ -67,6 +137,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 +178,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 +206,7 @@ void ViewTransitionModule::startViewTransitionEnd() {
}
}
nameRegistry_.clear();
oldPseudoElementNodes_.clear();

transitionStarted_ = false;
}
Expand All @@ -152,12 +231,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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class ViewTransitionModule : public UIManagerViewTransitionDelegate {
void applyViewTransitionName(const ShadowNode &shadowNode, const std::string &name, const std::string &className)
override;

// creates a pseudo-element shadow node for a given transition name using the
// captured old layout metrics
void createViewTransitionInstance(const std::string &name, Tag pseudoElementTag) override;

// if a viewTransitionName is cancelled, the element doesn't have view-transition-name and browser won't be taking
// snapshot
void cancelViewTransitionName(const ShadowNode &shadowNode, const std::string &name) override;
Expand Down Expand Up @@ -72,8 +76,15 @@ class ViewTransitionModule : public UIManagerViewTransitionDelegate {
// used for cancel/restore viewTransitionName
std::unordered_map<Tag, std::unordered_set<std::string>> cancelledNameRegistry_{};

// pseudo-element nodes keyed by transition name
std::unordered_map<std::string, std::shared_ptr<const ShadowNode>> oldPseudoElementNodes_{};
// will be restored into oldPseudoElementNodes_ in next transition
std::unordered_map<std::string, std::shared_ptr<const ShadowNode>> oldPseudoElementNodesForNextTransition_{};

LayoutMetrics captureLayoutMetricsFromRoot(const ShadowNode &shadowNode);

void applySnapshotsOnPseudoElementShadowNodes();

UIManager *uiManager_{nullptr};

bool transitionStarted_{false};
Expand Down
Loading