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;
}