diff --git a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx index c43af88d0db1..756948e5cc24 100644 --- a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Button, StyleSheet, + Text, TouchableWithoutFeedback, View, } from 'react-native'; @@ -42,8 +43,11 @@ export default function BBExample() { entering={FadeInUp} layout={LinearTransition.duration(2000)} exiting={RotateOutDownLeft} - style={[styles.refresher, { width: show ? 200 : 100 }]} - /> + style={[styles.refresher, { width: show ? 200 : 100 }]}> + + This item should be on top of the red one + + )} ); @@ -74,4 +78,10 @@ const styles = StyleSheet.create({ height: 100, backgroundColor: 'blue', }, + label: { + color: 'white', + padding: 4, + width: 100, + height: 100 + }, }); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index a43711a59083..a8dbb3785984 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -25,17 +25,15 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans ReanimatedSystraceSection d("pullTransaction"); auto lock = std::unique_lock(mutex); const PropsParserContext propsParserContext{surfaceId, *contextContainer_}; - ShadowViewMutationList filteredMutations; + ShadowViewMutationList outputMutations; auto rootChildCount = static_cast(lightNodes_[surfaceId]->children.size()); - const std::vector> roots; - const bool isInTransition = static_cast(transitionState_); - if (isInTransition) { - updateLightTree(propsParserContext, mutations, filteredMutations); - handleProgressTransition(filteredMutations, mutations, propsParserContext, surfaceId); + if (transitionState_ != TransitionState::NONE) { + updateLightTree(propsParserContext, mutations, outputMutations); + handleProgressTransition(outputMutations, mutations, propsParserContext, surfaceId); } else if (!synchronized_) { auto actualTop = topScreen[surfaceId]; - updateLightTree(propsParserContext, mutations, filteredMutations); + updateLightTree(propsParserContext, mutations, outputMutations); auto reactTop = findTopScreen(lightNodes_[surfaceId]); if (reactTop == actualTop) { synchronized_ = true; @@ -49,7 +47,7 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans findSharedElementsOnScreen(beforeTopScreen, BEFORE, propsParserContext); } - updateLightTree(propsParserContext, mutations, filteredMutations); + updateLightTree(propsParserContext, mutations, outputMutations); auto afterTopScreen = findTopScreen(root); topScreen[surfaceId] = afterTopScreen; @@ -68,13 +66,13 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans // and mutations to hide target views after all mutations. std::vector mergedMutations; hideTransitioningViews(BEFORE, mergedMutations, propsParserContext); - mergedMutations.insert(mergedMutations.end(), filteredMutations.begin(), filteredMutations.end()); + mergedMutations.insert(mergedMutations.end(), outputMutations.begin(), outputMutations.end()); hideTransitioningViews(AFTER, mergedMutations, propsParserContext); - std::swap(filteredMutations, mergedMutations); + std::swap(outputMutations, mergedMutations); } handleSharedTransitionsStart( - afterTopScreen, beforeTopScreen, filteredMutations, mutations, propsParserContext, surfaceId); + afterTopScreen, beforeTopScreen, outputMutations, mutations, propsParserContext, surfaceId); } for (auto &node : entering_) { @@ -86,19 +84,19 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans entering_.clear(); layout_.clear(); - handleRemovals(filteredMutations, exiting_); - exiting_.clear(); + handleRemovals(outputMutations, potentialExitingRoots_); + potentialExitingRoots_.clear(); - addOngoingAnimations(surfaceId, filteredMutations); + addOngoingAnimations(surfaceId, outputMutations); - cleanupAnimations(filteredMutations, propsParserContext, surfaceId); + cleanupAnimations(outputMutations, propsParserContext, surfaceId); transitionMap_.clear(); transitions_.clear(); - insertContainers(filteredMutations, rootChildCount, surfaceId); + insertContainers(outputMutations, rootChildCount, surfaceId); - return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry}; + return MountingTransaction{surfaceId, transactionNumber, std::move(outputMutations), telemetry}; } bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { @@ -111,7 +109,7 @@ bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { void LayoutAnimationsProxy_Experimental::updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &filteredMutations) const { + ShadowViewMutationList &outputMutations) const { ReanimatedSystraceSection s("updateLightTree"); std::unordered_set inserted, moved, deleted; for (auto it = mutations.rbegin(); it != mutations.rend(); it++) { @@ -168,7 +166,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { layout_.push_back(node); } else { - filteredMutations.push_back(mutation); + outputMutations.push_back(mutation); } break; } @@ -178,28 +176,39 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( react_native_assert(!lightNodes_.contains(mutation.newChildShadowView.tag) && "LightNode already exists"); lightNodes_[mutation.newChildShadowView.tag] = node; - filteredMutations.push_back(mutation); + outputMutations.push_back(mutation); break; } case ShadowViewMutation::Delete: { - lightNodes_.erase(mutation.oldChildShadowView.tag); + const auto deletedTag = mutation.oldChildShadowView.tag; + const auto it = lightNodes_.find(deletedTag); + react_native_assert(it != lightNodes_.end() && "Delete mutation for unknown tag"); + if (it == lightNodes_.end()) { + break; + } + if (it->second->exitingState != ExitingState::TRIAGE) { + lightNodes_.erase(it); + } break; } case ShadowViewMutation::Insert: { transferConfigFromNativeID(mutation.newChildShadowView.props->nativeId, mutation.newChildShadowView.tag); auto &node = lightNodes_[mutation.newChildShadowView.tag]; auto &parent = lightNodes_[mutation.parentTag]; - parent->children.insert(parent->children.begin() + mutation.index, node); + const auto actualIndex = mutation.index + parent->countExitingChildrenAffectingIndex(mutation.index); + parent->children.insert(parent->children.begin() + actualIndex, node); node->parent = parent; const auto tag = mutation.newChildShadowView.tag; if (moved.contains(tag) && layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { - filteredMutations.push_back( - ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, mutation.index)); + outputMutations.push_back( + ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, actualIndex)); } else if (layoutAnimationsManager_->hasLayoutAnimation(tag, ENTERING)) { entering_.push_back(node); - filteredMutations.push_back(mutation); + outputMutations.push_back( + ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } else { - filteredMutations.push_back(mutation); + outputMutations.push_back( + ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } break; } @@ -208,17 +217,36 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( const auto tag = node->current.tag; const auto parentTag = mutation.parentTag; const auto &parent = lightNodes_[parentTag]; + const auto actualIndex = mutation.index + parent->countExitingChildrenAffectingIndex(mutation.index); + + if (parent->children[actualIndex]->current.tag != mutation.oldChildShadowView.tag) { + std::string childTags; + for (std::size_t i = 0; i < parent->children.size(); i++) { + if (i > 0) { + childTags += ", "; + } + childTags += std::to_string(parent->children[i]->current.tag); + if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { + childTags += "(exiting)"; + } + } + LOG(WARNING) << "Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag + << " at actualIndex " << actualIndex << " under parent tag " << parentTag << ", but found tag " + << parent->children[actualIndex]->current.tag << " (rnIndex=" << mutation.index + << "); children=[" << childTags << "]" + << " count=" << parent->countExitingChildrenAffectingIndex(mutation.index); + } react_native_assert( - parent->children[mutation.index]->current.tag == mutation.oldChildShadowView.tag && + parent->children[actualIndex]->current.tag == mutation.oldChildShadowView.tag && "Indicies are wrong in Remove mutation"); if (deleted.contains(tag) && !deleted.contains(parentTag)) { - exiting_.push_back(node); - filteredMutations.push_back(mutation); - parent->children.erase(parent->children.begin() + mutation.index); + potentialExitingRoots_.push_back(node); + node->exitingState = ExitingState::TRIAGE; } else if (!deleted.contains(tag)) { - filteredMutations.push_back(mutation); - parent->children.erase(parent->children.begin() + mutation.index); + // reparenting + outputMutations.push_back(ShadowViewMutation::RemoveMutation(parentTag, node->current, actualIndex)); + parent->children.erase(parent->children.begin() + actualIndex); } break; } @@ -298,7 +326,7 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( auto node = lightNodes_[tag]; react_native_assert(node && "LightNode not found"); - node->state = DEAD; + node->exitingState = DEAD; lightNodes_.erase(tag); deadNodes.insert(node); @@ -306,13 +334,12 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( } void LayoutAnimationsProxy_Experimental::handleRemovals( - ShadowViewMutationList &filteredMutations, - std::vector> &roots) const { + ShadowViewMutationList &outputMutations, + std::vector> &potentialExitingRoots) const { ReanimatedSystraceSection s("handleRemovals"); - // iterate from the end, so that children - // with higher indices appear first in the mutations list - for (auto it = roots.rbegin(); it != roots.rend(); it++) { - auto &node = *it; + // iterate from the end, so that children with higher indices appear first in the mutations list + for (auto it = potentialExitingRoots.rbegin(); it != potentialExitingRoots.rend(); it++) { + auto &potentialExitingRoot = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -320,41 +347,35 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(node, filteredMutations, config)) { - auto parent = node->parent.lock(); - react_native_assert(parent && "Parent node is nullptr"); - // TODO (future): figure out a better way to handle this - // Currently we remove each view, and then if we want to animate it, reinsert it at the end. - // This is nice, but introduces extra mutations (which could have some side effects, like making a snapshot in - // RNScreens), and it changes the zIndex of animated views, which is different from what've had. The biggest - // convenience of this approach is that it is much easier to maintain indices of animated views, and handle - // reparentings. - - auto current = node->current; - if (layoutAnimations_.contains(node->current.tag)) { - current = layoutAnimations_.at(node->current.tag).currentView; - } - filteredMutations.push_back( - ShadowViewMutation::InsertMutation(parent->current.tag, current, static_cast(parent->children.size()))); - parent->children.push_back(node); - if (node->state == UNDEFINED) { - node->state = WAITING; + if (startExitingAnimationsRecursively(potentialExitingRoot, outputMutations, config)) { + auto parentOfPotentialExitingRoot = potentialExitingRoot->parent.lock(); + react_native_assert(parentOfPotentialExitingRoot && "Parent node is nullptr"); + if (potentialExitingRoot->exitingState == UNDEFINED || + potentialExitingRoot->exitingState == ExitingState::TRIAGE) { + potentialExitingRoot->exitingState = WAITING; } } else { - maybeCancelAnimation(node->current.tag); - filteredMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); + maybeCancelAnimation(potentialExitingRoot->current.tag); + auto parent = potentialExitingRoot->parent.lock(); + react_native_assert(parent && "Parent node is nullptr"); + auto actualIndex = parent->removeChild(potentialExitingRoot); + react_native_assert(actualIndex != -1 && "actualIndex == -1"); + lightNodes_.erase(potentialExitingRoot->current.tag); + outputMutations.push_back( + ShadowViewMutation::RemoveMutation(parent->current.tag, potentialExitingRoot->current, actualIndex)); + outputMutations.push_back(ShadowViewMutation::DeleteMutation(potentialExitingRoot->current)); } } - for (const auto &node : deadNodes) { - if (node->state != DELETED) { - auto parent = node->parent.lock(); - react_native_assert(parent && "Parent node is nullptr"); - auto index = parent->removeChild(node); - react_native_assert(index != -1 && "Dead node not found"); + for (const auto &deadNode : deadNodes) { + if (deadNode->exitingState != DELETED) { + auto parentOfDeadNode = deadNode->parent.lock(); + react_native_assert(parentOfDeadNode && "Parent node is nullptr"); + auto deadNodeIndex = parentOfDeadNode->removeChild(deadNode); + react_native_assert(deadNodeIndex != -1 && "Dead node not found"); - endAnimationsRecursively(node, index, filteredMutations); - maybeDropAncestors(parent, filteredMutations); + endExitingAnimationsRecursively(deadNode, deadNodeIndex, outputMutations); + maybeDropAncestors(parentOfDeadNode, outputMutations); } } deadNodes.clear(); @@ -416,34 +437,34 @@ void LayoutAnimationsProxy_Experimental::addOngoingAnimations(SurfaceId surfaceI updateMap.clear(); } -void LayoutAnimationsProxy_Experimental::endAnimationsRecursively( +void LayoutAnimationsProxy_Experimental::endExitingAnimationsRecursively( const std::shared_ptr &node, int index, - ShadowViewMutationList &mutations) const { + ShadowViewMutationList &outputMutations) const { maybeCancelAnimation(node->current.tag); - node->state = DELETED; + node->exitingState = DELETED; // iterate from the end, so that children // with higher indices appear first in the mutations list const int childrenSize = static_cast(node->children.size()); for (int i = childrenSize - 1; i >= 0; i--) { auto &subNode = node->children[i]; - if (subNode->state != DELETED) { - endAnimationsRecursively(subNode, i, mutations); + if (subNode->exitingState != DELETED) { + endExitingAnimationsRecursively(subNode, i, outputMutations); } } node->children.clear(); const auto &parent = node->parent.lock(); react_native_assert(parent && "Parent node is nullptr"); - mutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); - mutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); + outputMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); + outputMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); } void LayoutAnimationsProxy_Experimental::maybeDropAncestors( const std::shared_ptr &node, ShadowViewMutationList &cleanupMutations) const { - if (node->children.size() != 0 || node->state == ANIMATING || node->state == UNDEFINED) { + if (node->children.size() != 0 || node->exitingState == ANIMATING || node->exitingState == UNDEFINED) { return; } @@ -452,7 +473,7 @@ void LayoutAnimationsProxy_Experimental::maybeDropAncestors( auto index = parent->removeChild(node); react_native_assert(index != -1 && "Child node not found"); - node->state = DELETED; + node->exitingState = DELETED; maybeCancelAnimation(node->current.tag); cleanupMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); cleanupMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); @@ -464,7 +485,7 @@ const ComponentDescriptor &LayoutAnimationsProxy_Experimental::getComponentDescr return componentDescriptorRegistry_->at(shadowView.componentHandle); } -bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( +bool LayoutAnimationsProxy_Experimental::startExitingAnimationsRecursively( const std::shared_ptr &node, ShadowViewMutationList &mutations, StartAnimationsRecursivelyConfig config) const { @@ -488,23 +509,23 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( for (auto it = node->children.rbegin(); it != node->children.rend(); it++) { index--; auto &subNode = *it; - if (subNode->state != UNDEFINED) { - if (shouldAnimate && subNode->state != DEAD) { + if (subNode->exitingState != UNDEFINED) { + if (shouldAnimate && subNode->exitingState != DEAD) { hasAnimatedChildren = true; } else { - endAnimationsRecursively(subNode, index, mutations); + endExitingAnimationsRecursively(subNode, index, mutations); toBeRemoved.push_back(subNode); } - } else if (startAnimationsRecursively(subNode, mutations, config)) { + } else if (startExitingAnimationsRecursively(subNode, mutations, config)) { hasAnimatedChildren = true; } else if (shouldRemoveSubviewsWithoutAnimations) { maybeCancelAnimation(subNode->current.tag); mutations.push_back(ShadowViewMutation::RemoveMutation(node->current.tag, subNode->current, index)); toBeRemoved.push_back(subNode); - subNode->state = DELETED; + subNode->exitingState = DELETED; mutations.push_back(ShadowViewMutation::DeleteMutation(subNode->current)); } else { - subNode->state = WAITING; + subNode->exitingState = WAITING; } } @@ -515,7 +536,7 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( const bool wantAnimateExit = hasExitAnimation || hasAnimatedChildren; if (hasExitAnimation) { - node->state = ANIMATING; + node->exitingState = ANIMATING; startExitingAnimation(node); lightNodes_[node->current.tag] = node; } else { @@ -616,11 +637,11 @@ void LayoutAnimationsProxy_Experimental::maybeUpdateWindowDimensions( } void LayoutAnimationsProxy_Experimental::cleanupAnimations( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const { ReanimatedSystraceSection s("cleanupAnimations"); - cleanupSharedTransitions(filteredMutations, propsParserContext, surfaceId); + cleanupSharedTransitions(outputMutations, propsParserContext, surfaceId); #ifdef ANDROID restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index eca44e4cf651..ec5eca7a5687 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -53,7 +53,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, mutable TransitionMap transitionMap_; mutable Transitions transitions_; mutable bool synchronized_ = true; - mutable std::vector> entering_, layout_, exiting_; + mutable std::vector> entering_, layout_, potentialExitingRoots_; std::shared_ptr sharedTransitionManager_; mutable std::unordered_map> lightNodes_; mutable std::vector> containersToInsert_; @@ -98,7 +98,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void startProgressTransition(const int tag, const ShadowView &before, const ShadowView &after, SurfaceId surfaceId) const; void handleProgressTransition( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -106,22 +106,22 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &filteredMutations) const; + ShadowViewMutationList &outputMutations) const; void handleSharedTransitionsStart( const std::shared_ptr &afterTopScreen, const std::shared_ptr &beforeTopScreen, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupAnimations( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupSharedTransitions( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -133,7 +133,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void hideTransitioningViews( BeforeOrAfter index, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext) const; void transferConfigFromNativeID(const std::string &nativeId, const int tag) const; @@ -152,7 +152,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, BeforeOrAfter index, const PropsParserContext &propsParserContext) const; - void insertContainers(ShadowViewMutationList &filteredMutations, int &rootChildCount, SurfaceId surfaceId) const; + void insertContainers(ShadowViewMutationList &outputMutations, int &rootChildCount, SurfaceId surfaceId) const; std::vector getAbsolutePositionsForRootPathView(const std::shared_ptr &node) const; @@ -161,7 +161,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, Tag getOrCreateContainer( const ShadowView &before, const SharedTag &sharedTag, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &outputMutations, SurfaceId surfaceId) const; void overrideTransform( @@ -179,7 +179,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, std::array getTranslateForTransformOrigin(float viewWidth, float viewHeight, const TransformOrigin &transformOrigin) const; - void handleRemovals(ShadowViewMutationList &filteredMutations, std::vector> &roots) const; + void handleRemovals(ShadowViewMutationList &outputMutations, std::vector> &roots) const; void addOngoingAnimations(SurfaceId surfaceId, ShadowViewMutationList &mutations) const; void updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation) const; @@ -190,12 +190,14 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void maybeUpdateWindowDimensions(const facebook::react::ShadowViewMutation &mutation) const; ShadowView maybeCreateLayoutAnimation(ShadowView &before, const ShadowView &after, const Tag parentTag) const; - bool startAnimationsRecursively( + bool startExitingAnimationsRecursively( const std::shared_ptr &node, ShadowViewMutationList &mutations, StartAnimationsRecursivelyConfig config) const; - void endAnimationsRecursively(const std::shared_ptr &node, int index, ShadowViewMutationList &mutations) - const; + void endExitingAnimationsRecursively( + const std::shared_ptr &node, + int index, + ShadowViewMutationList &mutations) const; void maybeDropAncestors(const std::shared_ptr &node, ShadowViewMutationList &cleanupMutations) const; const ComponentDescriptor &getComponentDescriptorForShadowView(const ShadowView &shadowView) const; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index a5c860f72689..39453fa66d64 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -58,10 +58,15 @@ struct Snapshot { typedef enum class ExitingState : std::uint8_t { UNDEFINED = 1, - WAITING = 2, - ANIMATING = 3, - DEAD = 4, - DELETED = 5, + /** + * RN requested removal but Reanimated suppressed the Remove mutation. Reanimated needs to decide whether to + * play an exit animation or remove and delete the node. + */ + TRIAGE = 2, + WAITING = 3, + ANIMATING = 4, + DEAD = 5, + DELETED = 6, } ExitingState; struct MutationNode; @@ -83,11 +88,12 @@ enum class Intent : std::uint8_t { struct LightNode { ShadowView previous; ShadowView current; - ExitingState state = ExitingState::UNDEFINED; + ExitingState exitingState = ExitingState::UNDEFINED; std::weak_ptr parent; std::vector> children; + int removeChild(const std::shared_ptr &child) { - for (int i = children.size() - 1; i >= 0; i--) { + for (int i = static_cast(children.size()) - 1; i >= 0; i--) { if (children[i]->current.tag == child->current.tag) { children.erase(children.begin() + i); return i; @@ -95,6 +101,32 @@ struct LightNode { } return -1; } + + int findChildIndexByTag(Tag tag) const { + for (std::size_t i = 0; i < children.size(); i++) { + if (children[i]->current.tag == tag) { + return static_cast(i); + } + } + return -1; + } + + int countExitingChildrenAffectingIndex(int index) const { + react_native_assert(index >= 0 && "index must be non-negative"); + int remainingNonExitingChildrenToCheck = index; + int exitingCount = 0; + for (std::size_t i = 0; i < children.size(); i++) { + if (children[i]->exitingState != ExitingState::UNDEFINED) { + exitingCount++; + continue; + } + if (remainingNonExitingChildrenToCheck == 0) { + return exitingCount; + } + remainingNonExitingChildrenToCheck--; + } + return exitingCount; + } }; struct SurfaceManager {