ComponentKit/Core/CKComponentAnimations.mm (128 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import "CKComponentAnimations.h" #import <ComponentKit/CKCasting.h> #import <ComponentKit/CKCollection.h> #import <ComponentKit/CKInternalHelpers.h> #import "CKComponentEvents.h" #import "CKComponentScopeRoot.h" #import "CKComponentInternal.h" #import "CKComponentSubclass.h" namespace CK { static auto getScopeHandle(id<CKMountable> const c) { const auto scopeHandle = objCForceCast<CKComponent>(c).treeNode.scopeHandle; RCCAssertNotNil(scopeHandle, @"Scope must be provided for component animation"); return scopeHandle; } static auto isSameHandle(CKComponentScopeHandle *const h1, CKComponentScopeHandle *const &h2) { return h1.globalIdentifier == h2.globalIdentifier; }; static auto acquiredComponent(CKComponentScopeHandle *const h) { return objCForceCast<CKComponent>(h.acquiredComponent); }; static auto animatedAppearedComponentsBetweenLayouts(const CKComponentRootLayout &newLayout, const CKComponentRootLayout &previousLayout) -> std::vector<CKComponent *> { const auto newHandlesWithInitialAnimations = map(newLayout.componentsMatchingPredicate(CKComponentHasAnimationsOnInitialMountPredicate), getScopeHandle); const auto oldHandlesWithInitialAnimations = map(previousLayout.componentsMatchingPredicate(CKComponentHasAnimationsOnInitialMountPredicate), getScopeHandle); const auto handlesForAppearedComponentsWithInitialAnimations = Collection::difference(newHandlesWithInitialAnimations, oldHandlesWithInitialAnimations, isSameHandle); return map(handlesForAppearedComponentsWithInitialAnimations, acquiredComponent); } static auto animatedUpdatedComponentsBetweenLayouts(const CKComponentRootLayout &newLayout, const CKComponentRootLayout &previousLayout) -> std::vector<CK::ComponentTreeDiff::Pair> { const auto newHandlesWithAnimationsFromPreviousComponent = map(newLayout.componentsMatchingPredicate(CKComponentHasAnimationsFromPreviousComponentPredicate), getScopeHandle); const auto oldHandlesWithAnimationsFromPreviousComponent = map(previousLayout.componentsMatchingPredicate(CKComponentHasAnimationsFromPreviousComponentPredicate), getScopeHandle); const auto handlesForUpdatedComponents = Collection::intersection(newHandlesWithAnimationsFromPreviousComponent, oldHandlesWithAnimationsFromPreviousComponent, [](const auto &h1, const auto &h2){ return isSameHandle(h1, h2) && h1.acquiredComponent != h2.acquiredComponent; }); return map(handlesForUpdatedComponents, [&](const auto &h){ const auto prevHandle = find_if(oldHandlesWithAnimationsFromPreviousComponent, [&](const auto &oldHandle) { return oldHandle.globalIdentifier == h.globalIdentifier; }); return ComponentTreeDiff::Pair { acquiredComponent(*prevHandle), acquiredComponent(h) }; }); } static auto animatedDisappearedComponentsBetweenLayouts(const CKComponentRootLayout &newLayout, const CKComponentRootLayout &previousLayout) -> std::vector<CKComponent *> { const auto newHandlesWithAnimationsOnDisappear = map(newLayout.componentsMatchingPredicate(CKComponentHasAnimationsOnFinalUnmountPredicate), getScopeHandle); const auto oldHandlesWithAnimationsOnDisappear = map(previousLayout.componentsMatchingPredicate(CKComponentHasAnimationsOnFinalUnmountPredicate), getScopeHandle); const auto handlesForDisappearedComponentsWithAnimationsOnDisappear = Collection::difference(oldHandlesWithAnimationsOnDisappear, newHandlesWithAnimationsOnDisappear, isSameHandle); return map(handlesForDisappearedComponentsWithAnimationsOnDisappear, acquiredComponent); } auto animatedComponentsBetweenLayouts(const CKComponentRootLayout &newLayout, const CKComponentRootLayout &previousLayout) -> ComponentTreeDiff { return { .appearedComponents = animatedAppearedComponentsBetweenLayouts(newLayout, previousLayout), .updatedComponents = animatedUpdatedComponentsBetweenLayouts(newLayout, previousLayout), .disappearedComponents = animatedDisappearedComponentsBetweenLayouts(newLayout, previousLayout), }; } auto animationsForComponents(const ComponentTreeDiff& animatedComponents, UIView *const hostView) -> CKComponentAnimations { if (animatedComponents.appearedComponents.empty() && animatedComponents.updatedComponents.empty() && animatedComponents.disappearedComponents.empty()) { return {}; } const auto animationsOnInitialMountPairs = filter(map(animatedComponents.appearedComponents, [](const auto &c) { return std::make_pair(c, c.animationsOnInitialMount); }), [](const auto &pair) { return !pair.second.empty(); }); const auto animationsOnInitialMount = CKComponentAnimations::AnimationsByComponentMap(animationsOnInitialMountPairs.begin(), animationsOnInitialMountPairs.end()); const auto animationsFromPreviousComponentPairs = filter(map(animatedComponents.updatedComponents, [](const auto &pair) { return std::make_pair(pair.current, [pair.current animationsFromPreviousComponent:pair.prev]); }), [](const auto &pair) { return !pair.second.empty(); }); const auto animationsFromPrevComponent = CKComponentAnimations::AnimationsByComponentMap(animationsFromPreviousComponentPairs.begin(), animationsFromPreviousComponentPairs.end()); const auto animationsOnFinalUnmountPairs = filter(map(animatedComponents.disappearedComponents, [hostView](const auto &c) { return std::make_pair(c, map(c.animationsOnFinalUnmount, [hostView](const auto &a) { return CKComponentAnimation {a, hostView}; })); }), [](const auto &pair) { return !pair.second.empty(); }); const auto animationsOnFinalUnmount = CKComponentAnimations::AnimationsByComponentMap(animationsOnFinalUnmountPairs.begin(), animationsOnFinalUnmountPairs.end()); return {animationsOnInitialMount, animationsFromPrevComponent, animationsOnFinalUnmount}; } } static auto descriptionForAnimationsByComponentMap(const CKComponentAnimations::AnimationsByComponentMap &map) { auto pairStrs = static_cast<NSMutableArray<NSString *> *>([NSMutableArray array]); for (const auto &p : map) { for (const auto &a : p.second) { [pairStrs addObject:[NSString stringWithFormat:@"\t%@: %p", p.first, &a]]; } } return [pairStrs componentsJoinedByString:@",\n"]; } auto CKComponentAnimations::description() const -> NSString * { auto description = [NSMutableString new]; if (!_animationsOnInitialMount.empty()) { [description appendString:@"Animations on initial mount: {\n"]; [description appendString:descriptionForAnimationsByComponentMap(_animationsOnInitialMount)]; [description appendString:@"\n}\n"]; } if (!_animationsFromPreviousComponent.empty()) { [description appendString:@"Animations from previous component: {\n"]; [description appendString:descriptionForAnimationsByComponentMap(_animationsFromPreviousComponent)]; [description appendString:@"\n}\n"]; } if (!_animationsOnFinalUnmount.empty()) { [description appendString:@"Final unmount animations from component: {\n"]; [description appendString:descriptionForAnimationsByComponentMap(_animationsOnFinalUnmount)]; [description appendString:@"\n}\n"]; } return description; }